Diesen Spiele-Klassiker wollte ich schon länger auf dem Arduino nachbauen.
Ziel des Spiels ist es, alle "Mauersteine" abzuräumen.
Der Spieler steuert einen Schläger (Paddle), mit welchem er auf der Unterseite des Spielfeldes hin- und
herfahren kann. Ein Ball springt zwischen dem Schläger und einer Wand aus "Mauersteinen", die beim
Kontakt mit dem Ball zerspringen. Hat der Spieler alle "Mauersteine" abgeräumt, beginnt ein neues
Level. Verliert der Spieler den Ball, so ist das Spiel vorüber.
/**
* Breakout game for Arduino and a 8x8 WS2812 LED matrix
* Hint: XY(0,0) = top left
*/
#define PIN_LED_MATRIX 9
#define PIN_PIEZO 5
#define PIN_BUTTON_LEFT 13
#define PIN_BUTTON_RIGHT 12
#define DEBOUNCE_TIME 100 // in ms
#define X_MAX 8
#define Y_MAX 8
#define BRICK_AMOUNT X_MAX * 3
#define BALL_DELAY_MAX 350 // in ms
#define BALL_DELAY_MIN 100 // in ms
#define BALL_DELAY_STEP 5 // in ms
#define PADDLE_WIDTH 3
#define DIRECTION_NONE 0
#define DIRECTION_LEFT 1
#define DIRECTION_RIGHT 2
#define LED_TYPE_OFF 1
#define LED_TYPE_PADDLE 2
#define LED_TYPE_BALL 3
#define LED_TYPE_BALL_RED 4
#define LED_TYPE_BRICK 5
#define TONE_PADDLE 1
#define TONE_BRICK 2
#define TONE_END_GAME 3
#define TONE_NEW_LEVEL 4
#define GAME_STATE_RUNNING 1
#define GAME_STATE_END 2
#define GAME_STATE_LEVEL 3
#include <Adafruit_NeoPixel.h>
struct Coords {
byte x;
byte y;
};
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(X_MAX * Y_MAX, PIN_LED_MATRIX, NEO_GRB + NEO_KHZ800);
byte gameState;
byte level;
byte destroyedBricks;
Coords paddle[PADDLE_WIDTH];
Coords bricks[BRICK_AMOUNT];
Coords ball;
int ballMovement[2];
unsigned int ballDelay;
unsigned int score;
unsigned long lastBallUpdate = 0;
unsigned long lastButtonClick = 0;
void setup()
{
Serial.begin(9600);
pinMode(PIN_BUTTON_LEFT, INPUT_PULLUP);
pinMode(PIN_BUTTON_RIGHT, INPUT_PULLUP);
pinMode(PIN_PIEZO, OUTPUT);
pixels.begin();
resetLEDs();
gameState = GAME_STATE_END;
}
void loop()
{
switch(gameState) {
case GAME_STATE_LEVEL:
if (buttonPressed() != DIRECTION_NONE) {
newLevel();
}
break;
case GAME_STATE_RUNNING:
updateBall();
updatePaddle();
break;
case GAME_STATE_END:
if (buttonPressed() != DIRECTION_NONE) {
initGame();
}
break;
}
}
void initGame()
{
resetLEDs();
lastButtonClick = millis();
ballDelay = BALL_DELAY_MAX;
score = 0;
level = 0;
newLevel();
}
void initBricks()
{
destroyedBricks = 0;
for(byte i=0; i<BRICK_AMOUNT; i++) {
bricks[i].x = i % X_MAX;
bricks[i].y = i / X_MAX;
toggleLed(bricks[i].x, bricks[i].y, LED_TYPE_BRICK);
delay(25);
pixels.show();
}
}
void newLevel()
{
playTone(TONE_NEW_LEVEL);
initBricks();
for(byte i=0; i<PADDLE_WIDTH; i++) {
paddle[i].x = (X_MAX/2) - (PADDLE_WIDTH/2) + i;
paddle[i].y = Y_MAX - 1;
toggleLed(paddle[i].x, paddle[i].y, LED_TYPE_PADDLE);
}
ball.x = paddle[1].x;
ball.y = paddle[1].y - 1;
toggleLed(ball.x, ball.y, LED_TYPE_BALL);
ballMovement[0] = 1;
ballMovement[1] = -1;
lastBallUpdate = 0;
pixels.show();
level++;
gameState = GAME_STATE_RUNNING;
}
void updateBall()
{
if ((millis() - lastBallUpdate) < ballDelay) {
return;
}
lastBallUpdate = millis();
toggleLed(ball.x, ball.y, LED_TYPE_OFF);
if (ballMovement[1] == 1) {
// collision with bottom
if (ball.y == (Y_MAX - 1)) {
endGame();
return;
}
checkPaddleCollision();
}
// collision detection with bricks
for(byte i=0; i<BRICK_AMOUNT; i++) {
if (bricks[i].x == ball.x && bricks[i].y == ball.y) {
hitBrick(i);
break;
}
}
if (destroyedBricks >= BRICK_AMOUNT) {
gameState = GAME_STATE_LEVEL;
return;
}
// collision detection with wall
if (ball.x <= 0 || ball.x >= (X_MAX - 1)) {
ballMovement[0] *= -1;
}
if (ball.y <= 0) {
ballMovement[1] *= -1;
}
ball.x += ballMovement[0];
ball.y += ballMovement[1];
toggleLed(ball.x, ball.y, LED_TYPE_BALL);
pixels.show();
}
void hitBrick(byte i)
{
bricks[i].x = -1;
bricks[i].y = -1;
//ballMovement[1] *= -1;
score++;
destroyedBricks++;
if (ballDelay > BALL_DELAY_MIN) {
ballDelay -= BALL_DELAY_STEP;
}
toggleLed(bricks[i].x, bricks[i].y, LED_TYPE_OFF);
playTone(TONE_BRICK);
}
void checkPaddleCollision()
{
if ((paddle[0].y-1) != ball.y) {
return;
}
// reverse movement direction on the edges
if (ballMovement[0] == 1 && (paddle[0].x-1) == ball.x ||
ballMovement[0] == -1 && (paddle[PADDLE_WIDTH-1].x+1) == ball.x) {
ballMovement[0] *= -1;
ballMovement[1] *= -1;
playTone(TONE_PADDLE);
return;
}
if (paddle[PADDLE_WIDTH/2].x == ball.x) {
ballMovement[0] = 0;
ballMovement[1] *= -1;
playTone(TONE_PADDLE);
return;
}
for(byte i=0; i<PADDLE_WIDTH; i++) {
if (paddle[i].x == ball.x) {
ballMovement[1] *= -1;
if (random(2) == 0) {
ballMovement[0] = 1;
} else {
ballMovement[0] =-1;
}
playTone(TONE_PADDLE);
break;
}
}
}
void updatePaddle()
{
byte buttonState = buttonPressed();
if(buttonState == DIRECTION_NONE) {
return;
}
unsigned int moveDirection = 0;
if (buttonState == DIRECTION_LEFT && paddle[0].x > 0) {
moveDirection = -1;
}
if (buttonState == DIRECTION_RIGHT && paddle[PADDLE_WIDTH-1].x < (X_MAX-1)) {
moveDirection = 1;
}
if (moveDirection != 0) {
// turn off paddle LEDs
for(byte i=0; i<PADDLE_WIDTH; i++) {
toggleLed(paddle[i].x, paddle[i].y, LED_TYPE_OFF);
}
for(byte i=0; i<PADDLE_WIDTH; i++) {
paddle[i].x += moveDirection;
}
for(byte i=0; i<PADDLE_WIDTH; i++) {
toggleLed(paddle[i].x, paddle[i].y, LED_TYPE_PADDLE);
}
}
pixels.show();
}
void resetLEDs()
{
for(byte i=0; i<X_MAX*Y_MAX; i++) {
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
}
pixels.show();
}
byte buttonPressed()
{
if ((millis() - lastButtonClick) < DEBOUNCE_TIME) {
return DIRECTION_NONE;
}
lastButtonClick = millis();
if (digitalRead(PIN_BUTTON_LEFT) == LOW) {
return DIRECTION_LEFT;
}
if (digitalRead(PIN_BUTTON_RIGHT) == LOW) {
return DIRECTION_RIGHT;
}
return DIRECTION_NONE;
}
void endGame()
{
Serial.println("GAME OVER!");
gameState = GAME_STATE_END;
toggleLed(ball.x, ball.y, LED_TYPE_BALL_RED);
pixels.show();
Serial.println("Final score: " + String(score) + " in level " + String(level));
playTone(TONE_END_GAME);
}
void toggleLed(byte x, byte y, byte type)
{
byte ledIndex = y * X_MAX + x;
uint32_t color;
switch(type) {
case LED_TYPE_PADDLE:
color = pixels.Color(0, 8, 8);
break;
case LED_TYPE_BRICK:
color = pixels.Color(8, 8, 0);
break;
case LED_TYPE_BALL:
color = pixels.Color(0, 10, 0);
break;
case LED_TYPE_BALL_RED:
color = pixels.Color(12, 0, 0);
break;
case LED_TYPE_OFF:
color = pixels.Color(0, 0, 0);
break;
}
pixels.setPixelColor(ledIndex, color);
}
void playTone(byte type)
{
switch(type) {
case TONE_PADDLE:
tone(PIN_PIEZO, 440, 50);
break;
case TONE_BRICK:
tone(PIN_PIEZO, 550, 50);
break;
case TONE_NEW_LEVEL:
tone(PIN_PIEZO, 350, 80);
delay(200);
tone(PIN_PIEZO, 350, 80);
delay(200);
tone(PIN_PIEZO, 350, 80);
delay(200);
tone(PIN_PIEZO, 280, 300);
break;
case TONE_END_GAME:
for(byte i=0; i<20; i++) {
tone(PIN_PIEZO, 220-i*10, 50);
delay(50);
}
break;
}
}
zurück