Mirko Pavleski
Published © GPL3+

Arduino Breakout game on 8X8 Led Matrix with WS2812B Leds

This extremely simple project is actually the minimum possible version of the Breakout Arcade game made on a 64 pixel display.

BeginnerFull instructions provided3 hours311
Arduino Breakout game on 8X8 Led Matrix with WS2812B Leds

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
Buzzer
Buzzer
×1
Pushbutton Switch, Momentary
Pushbutton Switch, Momentary
×2
8x8 Led Matrix with WS2812B Leds
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Schematics

Schematic

...

Code

Code

C/C++
...
/*Arduino BREAKOUT Game on 8x8 Matrix WS2812b
by mircemk, June 2025
*/

#include <FastLED.h>

#define LED_PIN        6
#define NUM_LEDS      64
#define MATRIX_WIDTH   8
#define MATRIX_HEIGHT  8
#define LEFT_BUTTON_PIN  9
#define RIGHT_BUTTON_PIN 10
#define BUZZER_PIN     2

// Game constants
#define MIN_X_SPEED 0.2
#define MAX_X_SPEED 0.35
#define MIN_Y_SPEED 0.25
#define MAX_Y_SPEED 0.4
#define SPEED_DECAY 0.98

// Sound settings
#define TONE_HIT    1200
#define TONE_PADDLE 800
#define TONE_WALL   1000
#define TONE_GAMEOVER 400
#define TONE_DURATION 15
#define TONE_COOLDOWN 50

CRGB leds[NUM_LEDS];

// Game elements
int paddlePos = 3;
int paddleWidth = 3;
float ballX = 4;
float ballY = 6;
float ballSpeedX = 0.2;
float ballSpeedY = -0.25;
bool bricks[3][8];
CRGB brickColors[3][8];
int score = 0;
int level = 1;
bool gameOver = false;
bool levelCompleted = false;
bool newGame = false;
bool displayScoreDone = false;

// Timing control
unsigned long lastFrameTime = 0;
unsigned long lastSoundTime = 0;
const unsigned long FRAME_TIME = 50;

const byte SMILEY[8] = {
  B00111100,
  B01000010,
  B10100101,
  B10000001,
  B10100101,
  B10011001,
  B01000010,
  B00111100
};

// Font definition for letters and numbers (5x7 font)
const byte font[37][5] = {
  {0x7C, 0x22, 0x22, 0x22, 0x7C}, // A
  {0x7E, 0x4A, 0x4A, 0x4A, 0x34}, // B
  {0x3C, 0x42, 0x42, 0x42, 0x24}, // C
  {0x7E, 0x42, 0x42, 0x42, 0x3C}, // D
  {0x7E, 0x4A, 0x4A, 0x4A, 0x42}, // E
  {0x7E, 0x0A, 0x0A, 0x0A, 0x02}, // F
  {0x3C, 0x42, 0x52, 0x52, 0x34}, // G
  {0x7E, 0x08, 0x08, 0x08, 0x7E}, // H
  {0x00, 0x42, 0x7E, 0x42, 0x00}, // I
  {0x20, 0x40, 0x42, 0x3E, 0x02}, // J
  {0x7E, 0x08, 0x14, 0x22, 0x42}, // K
  {0x7E, 0x40, 0x40, 0x40, 0x40}, // L
  {0x7E, 0x04, 0x18, 0x04, 0x7E}, // M
  {0x7E, 0x04, 0x08, 0x10, 0x7E}, // N
  {0x3C, 0x42, 0x42, 0x42, 0x3C}, // O
  {0x7E, 0x12, 0x12, 0x12, 0x0C}, // P
  {0x3C, 0x42, 0x52, 0x22, 0x5C}, // Q
  {0x7E, 0x12, 0x32, 0x52, 0x0C}, // R
  {0x24, 0x4A, 0x4A, 0x4A, 0x30}, // S
  {0x02, 0x02, 0x7E, 0x02, 0x02}, // T
  {0x3E, 0x40, 0x40, 0x40, 0x3E}, // U
  {0x1E, 0x20, 0x40, 0x20, 0x1E}, // V
  {0x3E, 0x40, 0x30, 0x40, 0x3E}, // W
  {0x66, 0x18, 0x18, 0x18, 0x66}, // X
  {0x06, 0x08, 0x70, 0x08, 0x06}, // Y
  {0x62, 0x52, 0x4A, 0x46, 0x42}, // Z
  {0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0
  {0x00, 0x42, 0x7F, 0x40, 0x00}, // 1
  {0x72, 0x49, 0x49, 0x49, 0x46}, // 2
  {0x21, 0x41, 0x45, 0x4B, 0x31}, // 3
  {0x18, 0x14, 0x12, 0x7F, 0x10}, // 4
  {0x27, 0x45, 0x45, 0x45, 0x39}, // 5
  {0x3E, 0x49, 0x49, 0x49, 0x32}, // 6
  {0x01, 0x71, 0x09, 0x05, 0x03}, // 7
  {0x36, 0x49, 0x49, 0x49, 0x36}, // 8
  {0x26, 0x49, 0x49, 0x49, 0x3E}, // 9
  {0x00, 0x00, 0x00, 0x00, 0x00}  // Space
};

// Function to get the index of a character in the font array
int getCharIndex(char c) {
  if (c >= 'A' && c <= 'Z') {
    return c - 'A';
  } else if (c >= '0' && c <= '9') {
    return c - '0' + 26;
  } else {
    return 36; // Space
  }
}

// Function to scroll text on the LED matrix
void scrollText(const char* text, int delayTime) {
  int length = strlen(text);
  for (int offset = 0; offset < length * 6 + MATRIX_WIDTH; offset++) {
    fill_solid(leds, NUM_LEDS, CRGB::Black);
    for (int i = 0; i < length; i++) {
      int charIndex = getCharIndex(text[i]);
      for (int col = 0; col < 5; col++) {
        if (i * 6 + col - offset >= 0 && i * 6 + col - offset < MATRIX_WIDTH) {
          byte column = font[charIndex][col];
          for (int row = 0; row < 7; row++) {
            if (column & (1 << row)) {
              leds[getPixelIndex(i * 6 + col - offset, row)] = CRGB::White;
            }
          }
        }
      }
    }
    FastLED.show();
    delay(delayTime);
  }
}

void setup() {
  FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(50);
  
  pinMode(LEFT_BUTTON_PIN, INPUT_PULLUP);
  pinMode(RIGHT_BUTTON_PIN, INPUT_PULLUP);
  pinMode(BUZZER_PIN, OUTPUT);
  delay (1000);
  scrollText(" MINI BREAKOUT", 80);
  initializeGame();
}

void playSound(int frequency, int duration) {
  noTone(BUZZER_PIN); // Ensure the buzzer is off before playing a new sound
  tone(BUZZER_PIN, frequency, duration);
  delay(duration);
  noTone(BUZZER_PIN); // Turn off the buzzer after the duration
}

void initializeGame() {
  FastLED.setBrightness(50); // Ensure consistent brightness
  for (int row = 0; row < 3; row++) {
    for (int col = 0; col < 8; col++) {
      bricks[row][col] = true;
      brickColors[row][col] = CHSV(random(0, 255), 255, 255);  // Random color
    }
  }
  
  paddlePos = 3;
  ballX = 4.0;
  ballY = 6.0;
  ballSpeedX = 0.2;
  ballSpeedY = -0.25;
  score = 0;
  level = 1;
  paddleWidth = 3;
  gameOver = false;
  levelCompleted = false;
  newGame = false;
  displayScoreDone = false;
  lastFrameTime = millis();
  lastSoundTime = 0;
}

void initializeLevel() {
  for (int row = 0; row < 3; row++) {
    for (int col = 0; col < 8; col++) {
      bricks[row][col] = true;
      brickColors[row][col] = CHSV(random(0, 255), 255, 255);  // Random color
    }
  }
  
  paddlePos = 3;
  ballX = 4.0;
  ballY = 6.0;
  ballSpeedX = 0.2;
  ballSpeedY = -0.25;
  levelCompleted = false;
  lastFrameTime = millis();
}

void loop() {
  unsigned long currentTime = millis();
  
  if (!gameOver && !newGame) {
    if (currentTime - lastFrameTime >= FRAME_TIME) {
      handleInput();
      updateGame();
      lastFrameTime = currentTime;
    }
    drawGame();
    FastLED.show();
  } else if (gameOver && !displayScoreDone) {
    char scoreText[20];
    sprintf(scoreText, "SCORE: %d", score);
    scrollText(scoreText, 100);
    displayScoreDone = true;
    displaySmiley();
  } else if (gameOver && displayScoreDone) {
    if (digitalRead(LEFT_BUTTON_PIN) == LOW || digitalRead(RIGHT_BUTTON_PIN) == LOW) {
      delay(300);
      initializeGame();
      newGame = false; // Reset newGame for the next game cycle
      displayScoreDone = false;
    }
  }
}

void handleInput() {
  if (digitalRead(LEFT_BUTTON_PIN) == LOW && paddlePos > 0) {
    paddlePos--;
  }
  if (digitalRead(RIGHT_BUTTON_PIN) == LOW && paddlePos < MATRIX_WIDTH - paddleWidth) {
    paddlePos++;
  }
}

void updateGame() {
  ballX += ballSpeedX;
  ballY += ballSpeedY;
  
  // Brick collisions
  bool allBricksDestroyed = true;
  for (int row = 0; row < 3; row++) {
    for (int col = 0; col < 8; col++) {
      if (bricks[row][col]) {
        allBricksDestroyed = false;
        if (ballY >= row - 0.1 && ballY < row + 1.1 && 
            ballX >= col - 0.1 && ballX < col + 1.1) {
          bricks[row][col] = false;
          
          float dx = ballX - (col + 0.5);
          float dy = ballY - (row + 0.5);
          
          if (abs(dx) > abs(dy)) {
            ballSpeedX = -ballSpeedX;
          } else {
            ballSpeedY = -ballSpeedY;
          }
          
          ballSpeedX += (random(-15, 16) / 100.0);
          
          ballSpeedX = constrain(ballSpeedX, -MAX_X_SPEED, MAX_X_SPEED);
          ballSpeedY = constrain(ballSpeedY, -MAX_Y_SPEED, MAX_Y_SPEED);
          
          score += 1;
          playSound(TONE_HIT, TONE_DURATION);
          break;
        }
      }
    }
  }

  // Check for level completion
  if (allBricksDestroyed) {
    levelCompleted = true;
    level++;
    if (level > 3) {
      gameOver = true;
      playSound(TONE_GAMEOVER, TONE_DURATION * 5);
    } else {
      if (level == 2) {
        paddleWidth = 2;
      } else if (level == 3) {
        paddleWidth = 1;
      }
      initializeLevel();
    }
  }

  // Wall collisions
  if (ballX <= 0 || ballX >= MATRIX_WIDTH - 1) {
    ballSpeedX = -ballSpeedX;
    ballX = (ballX <= 0) ? 0.1 : (MATRIX_WIDTH - 1.1);
    
    if (abs(ballSpeedY) < MIN_Y_SPEED) {
      ballSpeedY += (ballSpeedY > 0 ? MIN_Y_SPEED : -MIN_Y_SPEED) * 0.5;
    }
    
    playSound(TONE_WALL, TONE_DURATION);
  }
  
  if (ballY <= 0) {
    ballSpeedY = abs(ballSpeedY);
    ballY = 0.1;
    playSound(TONE_WALL, TONE_DURATION);
  }

  // Paddle collision
  if (ballY >= MATRIX_HEIGHT - 2 && ballY < MATRIX_HEIGHT - 1) {
    if (ballX >= paddlePos && ballX < paddlePos + paddleWidth) {
      ballY = MATRIX_HEIGHT - 2;
      
      float hitPos = (ballX - paddlePos) / paddleWidth;
      float angle = (hitPos - 0.5) * PI * 0.7;
      
      float speed = sqrt(ballSpeedX * ballSpeedX + ballSpeedY * ballSpeedY);
      ballSpeedX = sin(angle) * speed;
      ballSpeedY = -abs(cos(angle) * speed);
      
      if (abs(ballSpeedY) < MIN_Y_SPEED) {
        ballSpeedY = -MIN_Y_SPEED;
      }
      
      playSound(TONE_PADDLE, TONE_DURATION);
    }
  }

  // Check if the ball collapses next to paddle on the left or right side
  if (ballY >= MATRIX_HEIGHT - 1 && (ballX < paddlePos || ballX >= paddlePos + paddleWidth)) {
    gameOver = true;
    playSound(TONE_GAMEOVER, TONE_DURATION * 5);
  }

  ballSpeedX *= SPEED_DECAY;
  ballSpeedY *= SPEED_DECAY;
  
  if (abs(ballSpeedX) < MIN_X_SPEED) {
    ballSpeedX = (ballSpeedX < 0) ? -MIN_X_SPEED : MIN_X_SPEED;
  }
  if (abs(ballSpeedY) < MIN_Y_SPEED) {
    ballSpeedY = (ballSpeedY < 0) ? -MIN_Y_SPEED : MIN_Y_SPEED;
  }
  
  ballSpeedX = constrain(ballSpeedX, -MAX_X_SPEED, MAX_X_SPEED);
  ballSpeedY = constrain(ballSpeedY, -MAX_Y_SPEED, MAX_Y_SPEED);

  if (ballY >= MATRIX_HEIGHT) {
    gameOver = true;
    playSound(TONE_GAMEOVER, TONE_DURATION * 5);
  }
}

void drawGame() {
  fill_solid(leds, NUM_LEDS, CRGB::Black);
  
  // Draw bricks
  for (int row = 0; row < 3; row++) {
    for (int col = 0; col < 8; col++) {
      if (bricks[row][col]) {
        leds[getPixelIndex(col, row)] = brickColors[row][col];
      }
    }
  }
  
  // Draw paddle
  for (int i = 0; i < paddleWidth; i++) {
    leds[getPixelIndex(paddlePos + i, MATRIX_HEIGHT - 1)] = CRGB::Blue;
  }
  
  // Draw ball
  leds[getPixelIndex(int(ballX), int(ballY))] = CRGB::White;
}

void displaySmiley() {
  fill_solid(leds, NUM_LEDS, CRGB::Black);
  for (int row = 0; row < 8; row++) {
    for (int col = 0; col < 8; col++) {
      if (SMILEY[row] & (1 << (7 - col))) {
        leds[getPixelIndex(col, row)] = CRGB::Yellow;
      }
    }
  }
  FastLED.show();
}

int getPixelIndex(int x, int y) {
  return y * MATRIX_WIDTH + x;
}

Credits

Mirko Pavleski
194 projects • 1479 followers

Comments