Mirko Pavleski
Published © GPL3+

Colorful Arduino Tetris Game - WS2812B LED Matrix Tutorial

Minimal version of Tetris with probably the lowest possible display resolution of only 64 pixels.

BeginnerFull instructions provided3 hours580
Colorful Arduino Tetris Game - WS2812B LED Matrix Tutorial

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
8x8 Matrix with WS2812B Leds
×1
Pushbutton Switch, Momentary
Pushbutton Switch, Momentary
×3
Buzzer
Buzzer
×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 TETRIS on 8x8 Matrix WS2812b
by mircemk, April 2025
*/

#include <FastLED.h>

// LED Matrix configuration
#define LED_PIN     6
#define NUM_LEDS    64
#define BRIGHTNESS  50
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define MATRIX_WIDTH 8
#define MATRIX_HEIGHT 8
#define BUZZER_PIN 2

// Button pins
#define LEFT_BUTTON_PIN  9
#define RIGHT_BUTTON_PIN 10
#define ROTATE_BUTTON_PIN 8

// Game parameters
#define INITIAL_GAME_SPEED 500  // Milliseconds
#define SPEED_INCREASE 10       // ms to decrease after each piece
#define MIN_GAME_SPEED 150      // Fastest game speed in milliseconds

#define NOTE_B0  31
#define NOTE_C1  33
#define NOTE_CS1 35
#define NOTE_D1  37
#define NOTE_DS1 39
#define NOTE_E1  41
#define NOTE_F1  44
#define NOTE_FS1 46
#define NOTE_G1  49
#define NOTE_GS1 52
#define NOTE_A1  55
#define NOTE_AS1 58
#define NOTE_B1  62
#define NOTE_C2  65
#define NOTE_CS2 69
#define NOTE_D2  73
#define NOTE_DS2 78
#define NOTE_E2  82
#define NOTE_F2  87
#define NOTE_FS2 93
#define NOTE_G2  98
#define NOTE_GS2 104
#define NOTE_A2  110
#define NOTE_AS2 117
#define NOTE_B2  123
#define NOTE_C3  131
#define NOTE_CS3 139
#define NOTE_D3  147
#define NOTE_DS3 156
#define NOTE_E3  165
#define NOTE_F3  175
#define NOTE_FS3 185
#define NOTE_G3  196
#define NOTE_GS3 208
#define NOTE_A3  220
#define NOTE_AS3 233
#define NOTE_B3  247
#define NOTE_C4  262

#define MODE_NORMAL 0
#define MODE_KIDS 1
byte gameMode = MODE_NORMAL;

bool gameOverScreenShown = false;

// Colors
CRGB leds[NUM_LEDS];
#define BLACK     CRGB(0, 0, 0)
#define RED       CRGB(255, 0, 0)
#define GREEN     CRGB(0, 255, 0)
#define BLUE      CRGB(0, 0, 255)
#define YELLOW    CRGB(255, 255, 0)
#define CYAN      CRGB(0, 255, 255)
#define MAGENTA   CRGB(255, 0, 255)
#define ORANGE    CRGB(255, 165, 0)

// Tetromino shapes
// Each tetromino is defined as 4 cells, each cell having x and y coordinates
typedef struct {
  byte shapes[4][4][2];  // [rotation][cell][x,y]
  CRGB color;
} Tetromino;

// Tetromino types (I, O, T, S, Z, J, L)
Tetromino tetrominos[7] = {
  // I-piece
  {
    {{{0,0}, {1,0}, {2,0}, {3,0}}, 
     {{0,0}, {0,1}, {0,2}, {0,3}},
     {{0,0}, {1,0}, {2,0}, {3,0}},
     {{0,0}, {0,1}, {0,2}, {0,3}}},
    CYAN
  },
  // O-piece
  {
    {{{0,0}, {1,0}, {0,1}, {1,1}},
     {{0,0}, {1,0}, {0,1}, {1,1}},
     {{0,0}, {1,0}, {0,1}, {1,1}},
     {{0,0}, {1,0}, {0,1}, {1,1}}},
    YELLOW
  },
  // T-piece
  {
    {{{0,0}, {1,0}, {2,0}, {1,1}},
     {{1,0}, {0,1}, {1,1}, {1,2}},
     {{1,0}, {0,1}, {1,1}, {2,1}},
     {{0,0}, {0,1}, {0,2}, {1,1}}},
    MAGENTA
  },
  // S-piece
  {
    {{{1,0}, {2,0}, {0,1}, {1,1}},
     {{0,0}, {0,1}, {1,1}, {1,2}},
     {{1,0}, {2,0}, {0,1}, {1,1}},
     {{0,0}, {0,1}, {1,1}, {1,2}}},
    GREEN
  },
  // Z-piece
  {
    {{{0,0}, {1,0}, {1,1}, {2,1}},
     {{1,0}, {0,1}, {1,1}, {0,2}},
     {{0,0}, {1,0}, {1,1}, {2,1}},
     {{1,0}, {0,1}, {1,1}, {0,2}}},
    RED
  },
  // J-piece
  {
    {{{0,0}, {0,1}, {1,1}, {2,1}},
     {{1,0}, {2,0}, {1,1}, {1,2}},
     {{0,0}, {1,0}, {2,0}, {2,1}},
     {{0,0}, {0,1}, {0,2}, {1,0}}},
    BLUE
  },
  // L-piece
  {
    {{{2,0}, {0,1}, {1,1}, {2,1}},
     {{0,0}, {1,0}, {1,1}, {1,2}},
     {{0,0}, {1,0}, {2,0}, {0,1}},
     {{0,0}, {0,1}, {0,2}, {1,2}}},
    ORANGE
  }
};

// simple tetrominos
Tetromino kidstetrominos[7] = {
  // Single pixel (red)
  {
    {{{0,0}, {0,0}, {0,0}, {0,0}},
     {{0,0}, {0,0}, {0,0}, {0,0}},
     {{0,0}, {0,0}, {0,0}, {0,0}},
     {{0,0}, {0,0}, {0,0}, {0,0}}},
    RED
  },
  // Two horizontal pixels (yellow)
  {
    {{{0,0}, {1,0}, {0,0}, {0,0}},
     {{0,0}, {1,0}, {0,0}, {0,0}},
     {{0,0}, {1,0}, {0,0}, {0,0}},
     {{0,0}, {1,0}, {0,0}, {0,0}}},
    YELLOW
  },
  // Two vertical pixels (blue)
  {
    {{{0,0}, {0,1}, {0,0}, {0,0}},
     {{0,0}, {0,1}, {0,0}, {0,0}},
     {{0,0}, {0,1}, {0,0}, {0,0}},
     {{0,0}, {0,1}, {0,0}, {0,0}}},
    BLUE
  },
  // Small L shape (green)
  {
    {{{0,0}, {0,1}, {1,1}, {0,0}},
     {{0,0}, {0,1}, {1,1}, {0,0}},
     {{0,0}, {0,1}, {1,1}, {0,0}},
     {{0,0}, {0,1}, {1,1}, {0,0}}},
    GREEN
  },
  // Small square (magenta)
  {
    {{{0,0}, {1,0}, {0,1}, {1,1}},
     {{0,0}, {1,0}, {0,1}, {1,1}},
     {{0,0}, {1,0}, {0,1}, {1,1}},
     {{0,0}, {1,0}, {0,1}, {1,1}}},
    MAGENTA
  },
  // Three horizontal pixels (cyan)
  {
    {{{0,0}, {1,0}, {2,0}, {0,0}},
     {{0,0}, {1,0}, {2,0}, {0,0}},
     {{0,0}, {1,0}, {2,0}, {0,0}},
     {{0,0}, {1,0}, {2,0}, {0,0}}},
    CYAN
  },
  // Diagonal two pixels (orange)
  {
    {{{0,0}, {1,1}, {0,0}, {0,0}},
     {{0,0}, {1,1}, {0,0}, {0,0}},
     {{0,0}, {1,1}, {0,0}, {0,0}},
     {{0,0}, {1,1}, {0,0}, {0,0}}},
    ORANGE
  }
};

const byte letters[][8] = {
  // M
  {B11011,
   B11011,
   B10101,
   B10001,
   B10001,
   B10001,
   B10001,
   B00000},
  // I
  {B11111,
   B00100,
   B00100,
   B00100,
   B00100,
   B00100,
   B11111,
   B00000},
  // N
  {B10001,
   B11001,
   B11101,
   B10111,
   B10011,
   B10001,
   B10001,
   B00000},
  // T
  {B11111,
   B00100,
   B00100,
   B00100,
   B00100,
   B00100,
   B00100,
   B00000},
  // E
  {B11111,
   B10000,
   B10000,
   B11110,
   B10000,
   B10000,
   B11111,
   B00000},
  // R
  {B11110,
   B10001,
   B10001,
   B11110,
   B10100,
   B10010,
   B10001,
   B00000},
  // S
  {B01111,
   B10000,
   B10000,
   B01110,
   B00001,
   B00001,
   B11110,
   B00000}
};

const byte digits[10][8] = {
  // 0
  {B00000000,
   B00111000,
   B01000100,
   B01000100,
   B01000100,
   B01000100,
   B00111000,
   B00000000},
  // 1
  {B00000000,
   B00010000,
   B00110000,
   B00010000,
   B00010000,
   B00010000,
   B00111000,
   B00000000},
  // 2
  {B00000000,
   B00111000,
   B01000100,
   B00001000,
   B00010000,
   B00100000,
   B01111100,
   B00000000},
  // 3
  {B00000000,
   B00111000,
   B01000100,
   B00001000,
   B00001100,
   B01000100,
   B00111000,
   B00000000},
  // 4
  {B00000000,
   B00001000,
   B00011000,
   B00101000,
   B01001000,
   B01111100,
   B00001000,
   B00000000},
  // 5
  {B00000000,
   B01111100,
   B01000000,
   B01111000,
   B00000100,
   B01000100,
   B00111000,
   B00000000},
  // 6
  {B00000000,
   B00111000,
   B01000000,
   B01111000,
   B01000100,
   B01000100,
   B00111000,
   B00000000},
  // 7
  {B00000000,
   B01111100,
   B00000100,
   B00001000,
   B00010000,
   B00100000,
   B00100000,
   B00000000},
  // 8
  {B00000000,
   B00111000,
   B01000100,
   B00111000,
   B01000100,
   B01000100,
   B00111000,
   B00000000},
  // 9
  {B00000000,
   B00111000,
   B01000100,
   B01000100,
   B00111100,
   B00000100,
   B00111000,
   B00000000}
};

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


const Tetromino* currentTetrominoSet;

void displayEndAnimation() {
  // Display static smiley once
  clearDisplay();
  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();
  
  // Just wait for button press without redrawing
  while (true) {
    if (digitalRead(LEFT_BUTTON_PIN) == LOW || digitalRead(RIGHT_BUTTON_PIN) == LOW) {
      break;
    }
    delay(100);  // Small delay to check buttons
  }
}

void displayScrollingScore(long score) {
  // Convert score to string
  char scoreStr[7];
  sprintf(scoreStr, "%ld", score);
  int scoreLen = strlen(scoreStr);
  
  // Display each digit scrolling from right to left
  for (int pos = 8; pos >= -scoreLen * 6; pos--) {
    clearDisplay();
    
    // Display each digit in its current position
    for (int i = 0; i < scoreLen; i++) {
      int digitPos = pos + (i * 6);  // 6 pixels spacing between digits
      if (digitPos < 8 && digitPos > -6) {  // Only display if digit is visible
        int digit = scoreStr[i] - '0';
        displayLetter(digits[digit], digitPos, CRGB(255, 255, 0));  // Orange color
      }
    }
    
    FastLED.show();
    delay(100);  // Scroll speed
  }
  
  // Pause at the end
  delay(500);
}

void playMoveSound() {
  // Quick, high-pitched blip (1200 Hz)
  tone(BUZZER_PIN, 1200, 30);  // Short duration for quick response
}

void playRotateSound() {
  // Two-tone ascending sound
  tone(BUZZER_PIN, 1000, 25);
  delay(25);
  tone(BUZZER_PIN, 1500, 25);  // Higher pitch for rotation
}

void playLandSound() {
  // Descending "bounce" effect
  tone(BUZZER_PIN, 800, 100);
  delay(50);
  tone(BUZZER_PIN, 1200, 80);
  delay(30);
  tone(BUZZER_PIN, 1500, 100);
}

void playClearLineSound() {
  // Cheerful ascending arpeggio
  tone(BUZZER_PIN, 800, 50);
  delay(50);
  tone(BUZZER_PIN, 1000, 50);
  delay(50);
  tone(BUZZER_PIN, 1200, 50);
  delay(50);
  tone(BUZZER_PIN, 1500, 100);
}

void playClearLineSound(int linesCleared) {
  switch(linesCleared) {
    case 1:
      // Simple two-tone
      tone(BUZZER_PIN, 1000, 50);
      delay(50);
      tone(BUZZER_PIN, 1500, 100);
      break;
      
    case 2:
      // Triple ascending
      tone(BUZZER_PIN, 1000, 50);
      delay(50);
      tone(BUZZER_PIN, 1200, 50);
      delay(50);
      tone(BUZZER_PIN, 1500, 100);
      break;
      
    case 3:
      // Four-note ascending
      tone(BUZZER_PIN, 1000, 50);
      delay(50);
      tone(BUZZER_PIN, 1200, 50);
      delay(50);
      tone(BUZZER_PIN, 1500, 50);
      delay(50);
      tone(BUZZER_PIN, 1800, 100);
      break;
      
    case 4:
      // Special Tetris fanfare
      tone(BUZZER_PIN, 1500, 80);
      delay(80);
      tone(BUZZER_PIN, 1800, 80);
      delay(80);
      tone(BUZZER_PIN, 2000, 80);
      delay(80);
      tone(BUZZER_PIN, 2500, 300);  // Final triumphant note
      break;
  }
}

void playGameOverSound() {
  // Playful "game over" tune
  tone(BUZZER_PIN, 1500, 100);
  delay(100);
  tone(BUZZER_PIN, 1200, 100);
  delay(100);
  tone(BUZZER_PIN, 1000, 100);
  delay(100);
  tone(BUZZER_PIN, 800, 300);
}

void playStartSound() {
  // Cheerful startup fanfare
  tone(BUZZER_PIN, 1000, 80);
  delay(80);
  tone(BUZZER_PIN, 1200, 80);
  delay(80);
  tone(BUZZER_PIN, 1500, 80);
 // delay(80);
 // tone(BUZZER_PIN, 2000, 200);  // Final triumphant note
}

void playModeSelectorSound() {
  // Quick two-tone acknowledgment
  tone(BUZZER_PIN, 1200, 50);
  delay(50);
  tone(BUZZER_PIN, 1500, 100);
}
// Add this function to select game mode
void selectGameMode() {
  playStartSound();
  bool modeSelected = false;
  
  while (!modeSelected) {
    // Split screen in two colors
    for (int y = 0; y < 8; y++) {
      for (int x = 0; x < 8; x++) {
        if (x < 4) {
          // Left half - Normal mode
          leds[getPixelIndex(x, y)] = CRGB(0, 150, 255);  // Sky blue
        } else {
          // Right half - Kids mode
          leds[getPixelIndex(x, y)] = CRGB(255, 0, 255);  // Magenta
        }
      }
    }
    FastLED.show();
    
    // Check buttons
    if (digitalRead(LEFT_BUTTON_PIN) == LOW) {
      playModeSelectorSound();
      gameMode = MODE_NORMAL;
      modeSelected = true;
      currentTetrominoSet = tetrominos;  // Set normal tetrominos
      
      // Clear screen first
      clearDisplay();
      FastLED.show();
      delay(300);
      
      // Smaller 5x6 "N" letter centered on the display
      const byte letterN[8] = {
        B00000000,
        B01001000,
        B01101000,
        B01011000,
        B01001000,
        B01001000,
        B00000000,
        B00000000
      };
      
      // Display N in the middle (starting at x=1)
      for (int i = 0; i < 3; i++) {
        clearDisplay();
        displayLetter(letterN, 1, CRGB(0, 150, 255));  // Sky blue
        FastLED.show();
        delay(200);
        clearDisplay();
        FastLED.show();
        delay(200);
      }
    }
    else if (digitalRead(RIGHT_BUTTON_PIN) == LOW) {
      playModeSelectorSound();
      gameMode = MODE_KIDS;
      modeSelected = true;
      currentTetrominoSet = kidstetrominos;  // Set kids tetrominos
      
      // Clear screen first
      clearDisplay();
      FastLED.show();
      delay(300);
      
      // Smaller 5x6 "K" letter centered on the display
      const byte letterK[8] = {
        B00000000,
        B01001000,
        B01010000,
        B01100000,
        B01010000,
        B01001000,
        B00000000,
        B00000000
      };
      
      // Display K in the middle (starting at x=1)
      for (int i = 0; i < 3; i++) {
        clearDisplay();
        displayLetter(letterK, 1, CRGB(255, 0, 255));  // Magenta
        FastLED.show();
        delay(200);
        clearDisplay();
        FastLED.show();
        delay(200);
      }
    }
  }
  
  // Clear screen and add delay before starting game
  clearDisplay();
  FastLED.show();
  delay(500);
}

void displayLetter(const byte* letter, int xOffset, CRGB color) {
  for (int y = 0; y < 8; y++) {
    for (int x = 0; x < 8; x++) {
      if (xOffset + x >= 0 && xOffset + x < 8) {  // Only draw if within display bounds
        if (letter[y] & (1 << (7-x))) {
          leds[getPixelIndex(xOffset + x, y)] = color;
        }
      }
    }
  }
}



// Game state
bool gameBoard[MATRIX_WIDTH][MATRIX_HEIGHT] = {0};  // True if a cell is occupied
CRGB boardColors[MATRIX_WIDTH][MATRIX_HEIGHT];      // Color of each cell

// Current tetromino state
byte currentPiece = 0;      // Index of current tetromino
byte currentRotation = 0;   // Current rotation (0-3)
int currentX = 3;           // X position of top-left corner
int currentY = 0;           // Y position of top-left corner
unsigned long lastFallTime = 0;
unsigned long gameSpeed = INITIAL_GAME_SPEED;
boolean gameOver = false;
unsigned int score = 0;

// Button state variables
bool leftPressed = false;
bool rightPressed = false;
bool rotatePressed = false;
unsigned long lastButtonCheckTime = 0;
#define DEBOUNCE_TIME 200  // Debounce time in milliseconds

void setup() {

 randomSeed(analogRead(A0) * analogRead(A1));  // Using multiple readings for better randomness

 pinMode(BUZZER_PIN, OUTPUT);
 
  // Initialize LED strip
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(BRIGHTNESS);
  clearDisplay();
  
  // Initialize button pins
  pinMode(LEFT_BUTTON_PIN, INPUT_PULLUP);
  pinMode(RIGHT_BUTTON_PIN, INPUT_PULLUP);
  pinMode(ROTATE_BUTTON_PIN, INPUT_PULLUP);
  
  Serial.begin(9600);
  Serial.println("Tetris initialized!");
  
  displaySplashScreen();
  selectGameMode();  // Add this line after splash screen
  spawnNewPiece();
}

void loop() {
   if (gameOver) {
    if (!gameOverScreenShown) {
      displayGameOver();
      gameOverScreenShown = true;
    } else if (checkAnyButtonPressed()) {
      // Wait for button release to prevent immediate restart
      delay(200);
      resetGame();
      gameOverScreenShown = false;
    }
    return;
  }
  
  checkButtons();
  
  // Move the piece down at regular intervals
  if (millis() - lastFallTime > gameSpeed) {
    if (!movePieceDown()) {
      // Piece has landed
      placePiece();
      clearLines();
      if (!spawnNewPiece()) {
        gameOver = true;
      }
      
      // Increase game speed 
      if (gameSpeed > MIN_GAME_SPEED) {
        gameSpeed -= SPEED_INCREASE;
      }
    }
    lastFallTime = millis();
  }
  
  updateDisplay();
}

void checkButtons() {
  // Check buttons with debounce
  if (millis() - lastButtonCheckTime > DEBOUNCE_TIME) {
    // Check left button
    if (digitalRead(LEFT_BUTTON_PIN) == LOW && !leftPressed) {
      leftPressed = true;
      movePieceLeft();
      lastButtonCheckTime = millis();
    } else if (digitalRead(LEFT_BUTTON_PIN) == HIGH) {
      leftPressed = false;
    }
    
    // Check right button
    if (digitalRead(RIGHT_BUTTON_PIN) == LOW && !rightPressed) {
      rightPressed = true;
      movePieceRight();
      lastButtonCheckTime = millis();
    } else if (digitalRead(RIGHT_BUTTON_PIN) == HIGH) {
      rightPressed = false;
    }
    
    // Check rotate button
    if (digitalRead(ROTATE_BUTTON_PIN) == LOW && !rotatePressed) {
      rotatePressed = true;
      rotatePiece();
      lastButtonCheckTime = millis();
    } else if (digitalRead(ROTATE_BUTTON_PIN) == HIGH) {
      rotatePressed = false;
    }
  }
}

bool checkAnyButtonPressed() {
  return (digitalRead(LEFT_BUTTON_PIN) == LOW || 
          digitalRead(RIGHT_BUTTON_PIN) == LOW || 
          digitalRead(ROTATE_BUTTON_PIN) == LOW);
}

// Helper functions for the LED matrix Type

int getPixelIndex(int x, int y) {
  
// Simple row-major pattern (no zigzag):
   return y * MATRIX_WIDTH + x;
  
//  Simple column-major pattern (no zigzag):
//  return x * MATRIX_HEIGHT + y;

//  Column-major zigzag pattern:
//    if (x % 2 == 0) {
    // Even columns go top to bottom
//   return x * MATRIX_HEIGHT + y;
//  } else {
    // Odd columns go bottom to top
//   return x * MATRIX_HEIGHT + (MATRIX_HEIGHT - 1 - y);
//  }

//  Flipped row-major zigzag pattern:
//  if (y % 2 == 0) {
    // Even rows go right to left
//    return y * MATRIX_WIDTH + (MATRIX_WIDTH - 1 - x);
//  } else {
    // Odd rows go left to right
//    return y * MATRIX_WIDTH + x;
//  }
}

void clearDisplay() {
  fill_solid(leds, NUM_LEDS, BLACK);
  FastLED.show();
}

void updateDisplay() {
  fill_solid(leds, NUM_LEDS, BLACK);
  
  // Draw the fixed blocks
  for (int x = 0; x < MATRIX_WIDTH; x++) {
    for (int y = 0; y < MATRIX_HEIGHT; y++) {
      if (gameBoard[x][y]) {
        leds[getPixelIndex(x, y)] = boardColors[x][y];
      }
    }
  }
  
  // Draw the current piece
  for (int i = 0; i < 4; i++) {
    int x = currentX + currentTetrominoSet[currentPiece].shapes[currentRotation][i][0];
    int y = currentY + currentTetrominoSet[currentPiece].shapes[currentRotation][i][1];
    
    if (x >= 0 && x < MATRIX_WIDTH && y >= 0 && y < MATRIX_HEIGHT) {
      leds[getPixelIndex(x, y)] = currentTetrominoSet[currentPiece].color;
    }
  }
  
  FastLED.show();
}

// Game mechanics
bool isValidPosition(int pieceIndex, int rotation, int posX, int posY) {
  for (int i = 0; i < 4; i++) {
    int x = posX + currentTetrominoSet[pieceIndex].shapes[rotation][i][0];
    int y = posY + currentTetrominoSet[pieceIndex].shapes[rotation][i][1];
    
    if (x < 0 || x >= MATRIX_WIDTH || y < 0 || y >= MATRIX_HEIGHT) {
      return false;
    }
    
    if (y >= 0 && gameBoard[x][y]) {
      return false;
    }
  }
  return true;
}

bool movePieceLeft() {
  if (isValidPosition(currentPiece, currentRotation, currentX - 1, currentY)) {
    currentX--;
    playMoveSound(); 
    return true;
  }
  return false;
}

bool movePieceRight() {
  if (isValidPosition(currentPiece, currentRotation, currentX + 1, currentY)) {
    currentX++;
    playMoveSound();  
    return true;
  }
  return false;
}

bool movePieceDown() {
  if (isValidPosition(currentPiece, currentRotation, currentX, currentY + 1)) {
    currentY++;
    return true;
  }
  return false;
}

bool rotatePiece() {
  byte nextRotation = (currentRotation + 1) % 4;
  if (isValidPosition(currentPiece, nextRotation, currentX, currentY)) {
    currentRotation = nextRotation;
    playRotateSound();
    return true;
  }
  // Try wall kick (adjust the position if rotation is blocked by a wall)
  // Try moving left
  if (isValidPosition(currentPiece, nextRotation, currentX - 1, currentY)) {
    currentX--;
    currentRotation = nextRotation;
    playRotateSound();
    return true;
  }
  // Try moving right
  if (isValidPosition(currentPiece, nextRotation, currentX + 1, currentY)) {
    currentX++;
    currentRotation = nextRotation;
    playRotateSound();
    return true;
  }
  return false;
}

void placePiece() {
  for (int i = 0; i < 4; i++) {
    int x = currentX + currentTetrominoSet[currentPiece].shapes[currentRotation][i][0];
    int y = currentY + currentTetrominoSet[currentPiece].shapes[currentRotation][i][1];
    
    if (x >= 0 && x < MATRIX_WIDTH && y >= 0 && y < MATRIX_HEIGHT) {
      gameBoard[x][y] = true;
      boardColors[x][y] = currentTetrominoSet[currentPiece].color;
      playLandSound();
    }
  }
}



bool spawnNewPiece() {
  static byte lastPiece = random(0, 7);
  byte newPiece;
  
  do {
    newPiece = random(0, 7);
  } while (newPiece == lastPiece && random(0, 100) < 70);
  
  lastPiece = newPiece;
  currentPiece = newPiece;
  
  // Use different rotation options based on game mode
  if (gameMode == MODE_KIDS) {
    currentRotation = 0;  // Kids mode pieces don't need rotation
  } else {
    currentRotation = random(0, 4);
  }
  
  currentX = (MATRIX_WIDTH / 2) - 1;
  currentY = 0;
  
  if (!isValidPosition(currentPiece, currentRotation, currentX, currentY)) {
    return false;
  }
  return true;
}

void clearLines() {
  int linesCleared = 0;
  
  for (int y = MATRIX_HEIGHT - 1; y >= 0; y--) {
    bool lineIsFull = true;
    
    // Check if the line is full
    for (int x = 0; x < MATRIX_WIDTH; x++) {
      if (!gameBoard[x][y]) {
        lineIsFull = false;
        break;
      }
    }
    
    if (lineIsFull) {
      linesCleared++;
      
      // Flash the line
      for (int i = 0; i < 3; i++) {
        // Flash white
        for (int x = 0; x < MATRIX_WIDTH; x++) {
          leds[getPixelIndex(x, y)] = CRGB::White;
        }
        FastLED.show();
        delay(50);
        
        // Flash black
        for (int x = 0; x < MATRIX_WIDTH; x++) {
          leds[getPixelIndex(x, y)] = CRGB::Black;
        }
        FastLED.show();
        delay(50);
      }
      
      // Move all lines above this one down
      for (int moveY = y; moveY > 0; moveY--) {
        for (int x = 0; x < MATRIX_WIDTH; x++) {
          gameBoard[x][moveY] = gameBoard[x][moveY - 1];
          boardColors[x][moveY] = boardColors[x][moveY - 1];
        }
      }
      
      // Clear the top line
      for (int x = 0; x < MATRIX_WIDTH; x++) {
        gameBoard[x][0] = false;
      }
      
      // Since the lines have moved down, we need to check this row again
      y++;
    }
  }
  
  // Update score
  if (linesCleared > 0) {

   playClearLineSound();
    // More points for clearing multiple lines at once
    score += (linesCleared * linesCleared) * 100;
  }
}

void resetGame() {
  playStartSound();
  // Set the appropriate tetromino set based on game mode
  currentTetrominoSet = (gameMode == MODE_KIDS) ? kidstetrominos : tetrominos;
  
  // Clear the display first
  clearDisplay();
...

This file has been truncated, please download it to see its full contents.

Credits

Mirko Pavleski
178 projects • 1418 followers
Contact

Comments

Please log in or sign up to comment.