#include <FastLED.h>
#define LED_PIN 6
#define MATRIX_WIDTH 8
#define MATRIX_HEIGHT 8
#define NUM_LEDS MATRIX_WIDTH * MATRIX_HEIGHT
#define BRIGHTNESS 255
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#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 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();
// Reset game board
for (int x = 0; x < MATRIX_WIDTH; x++) {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
gameBoard[x][y] = false;
}
}
// Reset game parameters
gameSpeed = INITIAL_GAME_SPEED;
gameOver = false;
score = 0;
gameOverScreenShown = false;
// Show a quick start animation
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB::Green;
FastLED.show();
delay(20);
}
clearDisplay();
delay(500);
// Spawn a new piece
spawnNewPiece();
}
void displaySplashScreen() {
playStartSound();
const char text[] = "MINI TETRIS";
const int textLength = strlen(text);
const int totalWidth = textLength * 8; // Each letter is 8 pixels wide
const CRGB colors[] = {CRGB::Red, CRGB::Green, CRGB::Blue, CRGB::Yellow,
CRGB::Cyan, CRGB::Magenta, CRGB::Orange
};
const int numColors = sizeof(colors) / sizeof(colors[0]);
// Scroll the entire text from right to left
for (int scroll = 8; scroll >= -totalWidth; scroll--) {
clearDisplay();
int letterPos = 0;
for (int i = 0; i < textLength; i++) {
char c = text[i];
int xPos = scroll + (i * 8);
// Skip spaces
if (c == ' ') {
continue;
}
// Map characters to array indices
int letterIndex;
switch (c) {
case 'M': letterIndex = 0; break;
case 'I': letterIndex = 1; break;
case 'N': letterIndex = 2; break;
case 'T': letterIndex = 3; break;
case 'E': letterIndex = 4; break;
case 'R': letterIndex = 5; break;
case 'S': letterIndex = 6; break;
default: continue;
}
// Display letter with color cycling
displayLetter(letters[letterIndex], xPos, colors[letterPos % numColors]);
letterPos++;
}
FastLED.show();
delay(60); // Adjust speed of scrolling
}
// Final flash effect
for (int i = 0; i < 3; i++) {
fill_solid(leds, NUM_LEDS, CRGB::White);
FastLED.show();
delay(100);
clearDisplay();
delay(100);
}
}
void displayGameOver() {
playGameOverSound();
// Flash "Game Over" effect
for (int i = 0; i < 3; i++) {
fill_solid(leds, NUM_LEDS, CRGB::Red);
FastLED.show();
delay(500);
clearDisplay();
FastLED.show();
delay(500);
}
// Display final score
delay(500);
displayScrollingScore(score);
// Show stable smiley and wait for restart
displayEndAnimation();
gameOverScreenShown = true;
}