Components and supplies
1
Arduino Nano
3
Push Button
1
LED Matrix Module 8x8
1
Grove - Buzzer - Piezo
Tools and machines
1
Soldering kit
Apps and platforms
1
Arduino IDE
Project description
Code
Code
js
...
1/*Arduino TETRIS on 8x8 Matrix WS2812b 2by mircemk, April 2025 3*/ 4 5#include <FastLED.h> 6 7// LED Matrix configuration 8#define LED_PIN 6 9#define NUM_LEDS 64 10#define BRIGHTNESS 50 11#define LED_TYPE WS2812B 12#define COLOR_ORDER GRB 13#define MATRIX_WIDTH 8 14#define MATRIX_HEIGHT 8 15#define BUZZER_PIN 2 16 17// Button pins 18#define LEFT_BUTTON_PIN 9 19#define RIGHT_BUTTON_PIN 10 20#define ROTATE_BUTTON_PIN 8 21 22// Game parameters 23#define INITIAL_GAME_SPEED 500 // Milliseconds 24#define SPEED_INCREASE 10 // ms to decrease after each piece 25#define MIN_GAME_SPEED 150 // Fastest game speed in milliseconds 26 27#define NOTE_B0 31 28#define NOTE_C1 33 29#define NOTE_CS1 35 30#define NOTE_D1 37 31#define NOTE_DS1 39 32#define NOTE_E1 41 33#define NOTE_F1 44 34#define NOTE_FS1 46 35#define NOTE_G1 49 36#define NOTE_GS1 52 37#define NOTE_A1 55 38#define NOTE_AS1 58 39#define NOTE_B1 62 40#define NOTE_C2 65 41#define NOTE_CS2 69 42#define NOTE_D2 73 43#define NOTE_DS2 78 44#define NOTE_E2 82 45#define NOTE_F2 87 46#define NOTE_FS2 93 47#define NOTE_G2 98 48#define NOTE_GS2 104 49#define NOTE_A2 110 50#define NOTE_AS2 117 51#define NOTE_B2 123 52#define NOTE_C3 131 53#define NOTE_CS3 139 54#define NOTE_D3 147 55#define NOTE_DS3 156 56#define NOTE_E3 165 57#define NOTE_F3 175 58#define NOTE_FS3 185 59#define NOTE_G3 196 60#define NOTE_GS3 208 61#define NOTE_A3 220 62#define NOTE_AS3 233 63#define NOTE_B3 247 64#define NOTE_C4 262 65 66#define MODE_NORMAL 0 67#define MODE_KIDS 1 68byte gameMode = MODE_NORMAL; 69 70bool gameOverScreenShown = false; 71 72// Colors 73CRGB leds[NUM_LEDS]; 74#define BLACK CRGB(0, 0, 0) 75#define RED CRGB(255, 0, 0) 76#define GREEN CRGB(0, 255, 0) 77#define BLUE CRGB(0, 0, 255) 78#define YELLOW CRGB(255, 255, 0) 79#define CYAN CRGB(0, 255, 255) 80#define MAGENTA CRGB(255, 0, 255) 81#define ORANGE CRGB(255, 165, 0) 82 83// Tetromino shapes 84// Each tetromino is defined as 4 cells, each cell having x and y coordinates 85typedef struct { 86 byte shapes[4][4][2]; // [rotation][cell][x,y] 87 CRGB color; 88} Tetromino; 89 90// Tetromino types (I, O, T, S, Z, J, L) 91Tetromino tetrominos[7] = { 92 // I-piece 93 { 94 {{{0,0}, {1,0}, {2,0}, {3,0}}, 95 {{0,0}, {0,1}, {0,2}, {0,3}}, 96 {{0,0}, {1,0}, {2,0}, {3,0}}, 97 {{0,0}, {0,1}, {0,2}, {0,3}}}, 98 CYAN 99 }, 100 // O-piece 101 { 102 {{{0,0}, {1,0}, {0,1}, {1,1}}, 103 {{0,0}, {1,0}, {0,1}, {1,1}}, 104 {{0,0}, {1,0}, {0,1}, {1,1}}, 105 {{0,0}, {1,0}, {0,1}, {1,1}}}, 106 YELLOW 107 }, 108 // T-piece 109 { 110 {{{0,0}, {1,0}, {2,0}, {1,1}}, 111 {{1,0}, {0,1}, {1,1}, {1,2}}, 112 {{1,0}, {0,1}, {1,1}, {2,1}}, 113 {{0,0}, {0,1}, {0,2}, {1,1}}}, 114 MAGENTA 115 }, 116 // S-piece 117 { 118 {{{1,0}, {2,0}, {0,1}, {1,1}}, 119 {{0,0}, {0,1}, {1,1}, {1,2}}, 120 {{1,0}, {2,0}, {0,1}, {1,1}}, 121 {{0,0}, {0,1}, {1,1}, {1,2}}}, 122 GREEN 123 }, 124 // Z-piece 125 { 126 {{{0,0}, {1,0}, {1,1}, {2,1}}, 127 {{1,0}, {0,1}, {1,1}, {0,2}}, 128 {{0,0}, {1,0}, {1,1}, {2,1}}, 129 {{1,0}, {0,1}, {1,1}, {0,2}}}, 130 RED 131 }, 132 // J-piece 133 { 134 {{{0,0}, {0,1}, {1,1}, {2,1}}, 135 {{1,0}, {2,0}, {1,1}, {1,2}}, 136 {{0,0}, {1,0}, {2,0}, {2,1}}, 137 {{0,0}, {0,1}, {0,2}, {1,0}}}, 138 BLUE 139 }, 140 // L-piece 141 { 142 {{{2,0}, {0,1}, {1,1}, {2,1}}, 143 {{0,0}, {1,0}, {1,1}, {1,2}}, 144 {{0,0}, {1,0}, {2,0}, {0,1}}, 145 {{0,0}, {0,1}, {0,2}, {1,2}}}, 146 ORANGE 147 } 148}; 149 150// simple tetrominos 151Tetromino kidstetrominos[7] = { 152 // Single pixel (red) 153 { 154 {{{0,0}, {0,0}, {0,0}, {0,0}}, 155 {{0,0}, {0,0}, {0,0}, {0,0}}, 156 {{0,0}, {0,0}, {0,0}, {0,0}}, 157 {{0,0}, {0,0}, {0,0}, {0,0}}}, 158 RED 159 }, 160 // Two horizontal pixels (yellow) 161 { 162 {{{0,0}, {1,0}, {0,0}, {0,0}}, 163 {{0,0}, {1,0}, {0,0}, {0,0}}, 164 {{0,0}, {1,0}, {0,0}, {0,0}}, 165 {{0,0}, {1,0}, {0,0}, {0,0}}}, 166 YELLOW 167 }, 168 // Two vertical pixels (blue) 169 { 170 {{{0,0}, {0,1}, {0,0}, {0,0}}, 171 {{0,0}, {0,1}, {0,0}, {0,0}}, 172 {{0,0}, {0,1}, {0,0}, {0,0}}, 173 {{0,0}, {0,1}, {0,0}, {0,0}}}, 174 BLUE 175 }, 176 // Small L shape (green) 177 { 178 {{{0,0}, {0,1}, {1,1}, {0,0}}, 179 {{0,0}, {0,1}, {1,1}, {0,0}}, 180 {{0,0}, {0,1}, {1,1}, {0,0}}, 181 {{0,0}, {0,1}, {1,1}, {0,0}}}, 182 GREEN 183 }, 184 // Small square (magenta) 185 { 186 {{{0,0}, {1,0}, {0,1}, {1,1}}, 187 {{0,0}, {1,0}, {0,1}, {1,1}}, 188 {{0,0}, {1,0}, {0,1}, {1,1}}, 189 {{0,0}, {1,0}, {0,1}, {1,1}}}, 190 MAGENTA 191 }, 192 // Three horizontal pixels (cyan) 193 { 194 {{{0,0}, {1,0}, {2,0}, {0,0}}, 195 {{0,0}, {1,0}, {2,0}, {0,0}}, 196 {{0,0}, {1,0}, {2,0}, {0,0}}, 197 {{0,0}, {1,0}, {2,0}, {0,0}}}, 198 CYAN 199 }, 200 // Diagonal two pixels (orange) 201 { 202 {{{0,0}, {1,1}, {0,0}, {0,0}}, 203 {{0,0}, {1,1}, {0,0}, {0,0}}, 204 {{0,0}, {1,1}, {0,0}, {0,0}}, 205 {{0,0}, {1,1}, {0,0}, {0,0}}}, 206 ORANGE 207 } 208}; 209 210const byte letters[][8] = { 211 // M 212 {B11011, 213 B11011, 214 B10101, 215 B10001, 216 B10001, 217 B10001, 218 B10001, 219 B00000}, 220 // I 221 {B11111, 222 B00100, 223 B00100, 224 B00100, 225 B00100, 226 B00100, 227 B11111, 228 B00000}, 229 // N 230 {B10001, 231 B11001, 232 B11101, 233 B10111, 234 B10011, 235 B10001, 236 B10001, 237 B00000}, 238 // T 239 {B11111, 240 B00100, 241 B00100, 242 B00100, 243 B00100, 244 B00100, 245 B00100, 246 B00000}, 247 // E 248 {B11111, 249 B10000, 250 B10000, 251 B11110, 252 B10000, 253 B10000, 254 B11111, 255 B00000}, 256 // R 257 {B11110, 258 B10001, 259 B10001, 260 B11110, 261 B10100, 262 B10010, 263 B10001, 264 B00000}, 265 // S 266 {B01111, 267 B10000, 268 B10000, 269 B01110, 270 B00001, 271 B00001, 272 B11110, 273 B00000} 274}; 275 276const byte digits[10][8] = { 277 // 0 278 {B00000000, 279 B00111000, 280 B01000100, 281 B01000100, 282 B01000100, 283 B01000100, 284 B00111000, 285 B00000000}, 286 // 1 287 {B00000000, 288 B00010000, 289 B00110000, 290 B00010000, 291 B00010000, 292 B00010000, 293 B00111000, 294 B00000000}, 295 // 2 296 {B00000000, 297 B00111000, 298 B01000100, 299 B00001000, 300 B00010000, 301 B00100000, 302 B01111100, 303 B00000000}, 304 // 3 305 {B00000000, 306 B00111000, 307 B01000100, 308 B00001000, 309 B00001100, 310 B01000100, 311 B00111000, 312 B00000000}, 313 // 4 314 {B00000000, 315 B00001000, 316 B00011000, 317 B00101000, 318 B01001000, 319 B01111100, 320 B00001000, 321 B00000000}, 322 // 5 323 {B00000000, 324 B01111100, 325 B01000000, 326 B01111000, 327 B00000100, 328 B01000100, 329 B00111000, 330 B00000000}, 331 // 6 332 {B00000000, 333 B00111000, 334 B01000000, 335 B01111000, 336 B01000100, 337 B01000100, 338 B00111000, 339 B00000000}, 340 // 7 341 {B00000000, 342 B01111100, 343 B00000100, 344 B00001000, 345 B00010000, 346 B00100000, 347 B00100000, 348 B00000000}, 349 // 8 350 {B00000000, 351 B00111000, 352 B01000100, 353 B00111000, 354 B01000100, 355 B01000100, 356 B00111000, 357 B00000000}, 358 // 9 359 {B00000000, 360 B00111000, 361 B01000100, 362 B01000100, 363 B00111100, 364 B00000100, 365 B00111000, 366 B00000000} 367}; 368 369const byte SMILEY[8] = { 370 B00111100, 371 B01000010, 372 B10100101, 373 B10000001, 374 B10100101, 375 B10011001, 376 B01000010, 377 B00111100 378}; 379 380 381const Tetromino* currentTetrominoSet; 382 383void displayEndAnimation() { 384 // Display static smiley once 385 clearDisplay(); 386 for (int row = 0; row < 8; row++) { 387 for (int col = 0; col < 8; col++) { 388 if (SMILEY[row] & (1 << (7 - col))) { 389 leds[getPixelIndex(col, row)] = CRGB::Yellow; 390 } 391 } 392 } 393 FastLED.show(); 394 395 // Just wait for button press without redrawing 396 while (true) { 397 if (digitalRead(LEFT_BUTTON_PIN) == LOW || digitalRead(RIGHT_BUTTON_PIN) == LOW) { 398 break; 399 } 400 delay(100); // Small delay to check buttons 401 } 402} 403 404void displayScrollingScore(long score) { 405 // Convert score to string 406 char scoreStr[7]; 407 sprintf(scoreStr, "%ld", score); 408 int scoreLen = strlen(scoreStr); 409 410 // Display each digit scrolling from right to left 411 for (int pos = 8; pos >= -scoreLen * 6; pos--) { 412 clearDisplay(); 413 414 // Display each digit in its current position 415 for (int i = 0; i < scoreLen; i++) { 416 int digitPos = pos + (i * 6); // 6 pixels spacing between digits 417 if (digitPos < 8 && digitPos > -6) { // Only display if digit is visible 418 int digit = scoreStr[i] - '0'; 419 displayLetter(digits[digit], digitPos, CRGB(255, 255, 0)); // Orange color 420 } 421 } 422 423 FastLED.show(); 424 delay(100); // Scroll speed 425 } 426 427 // Pause at the end 428 delay(500); 429} 430 431void playMoveSound() { 432 // Quick, high-pitched blip (1200 Hz) 433 tone(BUZZER_PIN, 1200, 30); // Short duration for quick response 434} 435 436void playRotateSound() { 437 // Two-tone ascending sound 438 tone(BUZZER_PIN, 1000, 25); 439 delay(25); 440 tone(BUZZER_PIN, 1500, 25); // Higher pitch for rotation 441} 442 443void playLandSound() { 444 // Descending "bounce" effect 445 tone(BUZZER_PIN, 800, 100); 446 delay(50); 447 tone(BUZZER_PIN, 1200, 80); 448 delay(30); 449 tone(BUZZER_PIN, 1500, 100); 450} 451 452void playClearLineSound() { 453 // Cheerful ascending arpeggio 454 tone(BUZZER_PIN, 800, 50); 455 delay(50); 456 tone(BUZZER_PIN, 1000, 50); 457 delay(50); 458 tone(BUZZER_PIN, 1200, 50); 459 delay(50); 460 tone(BUZZER_PIN, 1500, 100); 461} 462 463void playClearLineSound(int linesCleared) { 464 switch(linesCleared) { 465 case 1: 466 // Simple two-tone 467 tone(BUZZER_PIN, 1000, 50); 468 delay(50); 469 tone(BUZZER_PIN, 1500, 100); 470 break; 471 472 case 2: 473 // Triple ascending 474 tone(BUZZER_PIN, 1000, 50); 475 delay(50); 476 tone(BUZZER_PIN, 1200, 50); 477 delay(50); 478 tone(BUZZER_PIN, 1500, 100); 479 break; 480 481 case 3: 482 // Four-note ascending 483 tone(BUZZER_PIN, 1000, 50); 484 delay(50); 485 tone(BUZZER_PIN, 1200, 50); 486 delay(50); 487 tone(BUZZER_PIN, 1500, 50); 488 delay(50); 489 tone(BUZZER_PIN, 1800, 100); 490 break; 491 492 case 4: 493 // Special Tetris fanfare 494 tone(BUZZER_PIN, 1500, 80); 495 delay(80); 496 tone(BUZZER_PIN, 1800, 80); 497 delay(80); 498 tone(BUZZER_PIN, 2000, 80); 499 delay(80); 500 tone(BUZZER_PIN, 2500, 300); // Final triumphant note 501 break; 502 } 503} 504 505void playGameOverSound() { 506 // Playful "game over" tune 507 tone(BUZZER_PIN, 1500, 100); 508 delay(100); 509 tone(BUZZER_PIN, 1200, 100); 510 delay(100); 511 tone(BUZZER_PIN, 1000, 100); 512 delay(100); 513 tone(BUZZER_PIN, 800, 300); 514} 515 516void playStartSound() { 517 // Cheerful startup fanfare 518 tone(BUZZER_PIN, 1000, 80); 519 delay(80); 520 tone(BUZZER_PIN, 1200, 80); 521 delay(80); 522 tone(BUZZER_PIN, 1500, 80); 523 // delay(80); 524 // tone(BUZZER_PIN, 2000, 200); // Final triumphant note 525} 526 527void playModeSelectorSound() { 528 // Quick two-tone acknowledgment 529 tone(BUZZER_PIN, 1200, 50); 530 delay(50); 531 tone(BUZZER_PIN, 1500, 100); 532} 533// Add this function to select game mode 534void selectGameMode() { 535 playStartSound(); 536 bool modeSelected = false; 537 538 while (!modeSelected) { 539 // Split screen in two colors 540 for (int y = 0; y < 8; y++) { 541 for (int x = 0; x < 8; x++) { 542 if (x < 4) { 543 // Left half - Normal mode 544 leds[getPixelIndex(x, y)] = CRGB(0, 150, 255); // Sky blue 545 } else { 546 // Right half - Kids mode 547 leds[getPixelIndex(x, y)] = CRGB(255, 0, 255); // Magenta 548 } 549 } 550 } 551 FastLED.show(); 552 553 // Check buttons 554 if (digitalRead(LEFT_BUTTON_PIN) == LOW) { 555 playModeSelectorSound(); 556 gameMode = MODE_NORMAL; 557 modeSelected = true; 558 currentTetrominoSet = tetrominos; // Set normal tetrominos 559 560 // Clear screen first 561 clearDisplay(); 562 FastLED.show(); 563 delay(300); 564 565 // Smaller 5x6 "N" letter centered on the display 566 const byte letterN[8] = { 567 B00000000, 568 B01001000, 569 B01101000, 570 B01011000, 571 B01001000, 572 B01001000, 573 B00000000, 574 B00000000 575 }; 576 577 // Display N in the middle (starting at x=1) 578 for (int i = 0; i < 3; i++) { 579 clearDisplay(); 580 displayLetter(letterN, 1, CRGB(0, 150, 255)); // Sky blue 581 FastLED.show(); 582 delay(200); 583 clearDisplay(); 584 FastLED.show(); 585 delay(200); 586 } 587 } 588 else if (digitalRead(RIGHT_BUTTON_PIN) == LOW) { 589 playModeSelectorSound(); 590 gameMode = MODE_KIDS; 591 modeSelected = true; 592 currentTetrominoSet = kidstetrominos; // Set kids tetrominos 593 594 // Clear screen first 595 clearDisplay(); 596 FastLED.show(); 597 delay(300); 598 599 // Smaller 5x6 "K" letter centered on the display 600 const byte letterK[8] = { 601 B00000000, 602 B01001000, 603 B01010000, 604 B01100000, 605 B01010000, 606 B01001000, 607 B00000000, 608 B00000000 609 }; 610 611 // Display K in the middle (starting at x=1) 612 for (int i = 0; i < 3; i++) { 613 clearDisplay(); 614 displayLetter(letterK, 1, CRGB(255, 0, 255)); // Magenta 615 FastLED.show(); 616 delay(200); 617 clearDisplay(); 618 FastLED.show(); 619 delay(200); 620 } 621 } 622 } 623 624 // Clear screen and add delay before starting game 625 clearDisplay(); 626 FastLED.show(); 627 delay(500); 628} 629 630void displayLetter(const byte* letter, int xOffset, CRGB color) { 631 for (int y = 0; y < 8; y++) { 632 for (int x = 0; x < 8; x++) { 633 if (xOffset + x >= 0 && xOffset + x < 8) { // Only draw if within display bounds 634 if (letter[y] & (1 << (7-x))) { 635 leds[getPixelIndex(xOffset + x, y)] = color; 636 } 637 } 638 } 639 } 640} 641 642 643 644// Game state 645bool gameBoard[MATRIX_WIDTH][MATRIX_HEIGHT] = {0}; // True if a cell is occupied 646CRGB boardColors[MATRIX_WIDTH][MATRIX_HEIGHT]; // Color of each cell 647 648// Current tetromino state 649byte currentPiece = 0; // Index of current tetromino 650byte currentRotation = 0; // Current rotation (0-3) 651int currentX = 3; // X position of top-left corner 652int currentY = 0; // Y position of top-left corner 653unsigned long lastFallTime = 0; 654unsigned long gameSpeed = INITIAL_GAME_SPEED; 655boolean gameOver = false; 656unsigned int score = 0; 657 658// Button state variables 659bool leftPressed = false; 660bool rightPressed = false; 661bool rotatePressed = false; 662unsigned long lastButtonCheckTime = 0; 663#define DEBOUNCE_TIME 200 // Debounce time in milliseconds 664 665void setup() { 666 667 randomSeed(analogRead(A0) * analogRead(A1)); // Using multiple readings for better randomness 668 669 pinMode(BUZZER_PIN, OUTPUT); 670 671 // Initialize LED strip 672 FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip); 673 FastLED.setBrightness(BRIGHTNESS); 674 clearDisplay(); 675 676 // Initialize button pins 677 pinMode(LEFT_BUTTON_PIN, INPUT_PULLUP); 678 pinMode(RIGHT_BUTTON_PIN, INPUT_PULLUP); 679 pinMode(ROTATE_BUTTON_PIN, INPUT_PULLUP); 680 681 Serial.begin(9600); 682 Serial.println("Tetris initialized!"); 683 684 displaySplashScreen(); 685 selectGameMode(); // Add this line after splash screen 686 spawnNewPiece(); 687} 688 689void loop() { 690 if (gameOver) { 691 if (!gameOverScreenShown) { 692 displayGameOver(); 693 gameOverScreenShown = true; 694 } else if (checkAnyButtonPressed()) { 695 // Wait for button release to prevent immediate restart 696 delay(200); 697 resetGame(); 698 gameOverScreenShown = false; 699 } 700 return; 701 } 702 703 checkButtons(); 704 705 // Move the piece down at regular intervals 706 if (millis() - lastFallTime > gameSpeed) { 707 if (!movePieceDown()) { 708 // Piece has landed 709 placePiece(); 710 clearLines(); 711 if (!spawnNewPiece()) { 712 gameOver = true; 713 } 714 715 // Increase game speed 716 if (gameSpeed > MIN_GAME_SPEED) { 717 gameSpeed -= SPEED_INCREASE; 718 } 719 } 720 lastFallTime = millis(); 721 } 722 723 updateDisplay(); 724} 725 726void checkButtons() { 727 // Check buttons with debounce 728 if (millis() - lastButtonCheckTime > DEBOUNCE_TIME) { 729 // Check left button 730 if (digitalRead(LEFT_BUTTON_PIN) == LOW && !leftPressed) { 731 leftPressed = true; 732 movePieceLeft(); 733 lastButtonCheckTime = millis(); 734 } else if (digitalRead(LEFT_BUTTON_PIN) == HIGH) { 735 leftPressed = false; 736 } 737 738 // Check right button 739 if (digitalRead(RIGHT_BUTTON_PIN) == LOW && !rightPressed) { 740 rightPressed = true; 741 movePieceRight(); 742 lastButtonCheckTime = millis(); 743 } else if (digitalRead(RIGHT_BUTTON_PIN) == HIGH) { 744 rightPressed = false; 745 } 746 747 // Check rotate button 748 if (digitalRead(ROTATE_BUTTON_PIN) == LOW && !rotatePressed) { 749 rotatePressed = true; 750 rotatePiece(); 751 lastButtonCheckTime = millis(); 752 } else if (digitalRead(ROTATE_BUTTON_PIN) == HIGH) { 753 rotatePressed = false; 754 } 755 } 756} 757 758bool checkAnyButtonPressed() { 759 return (digitalRead(LEFT_BUTTON_PIN) == LOW || 760 digitalRead(RIGHT_BUTTON_PIN) == LOW || 761 digitalRead(ROTATE_BUTTON_PIN) == LOW); 762} 763 764// Helper functions for the LED matrix Type 765 766int getPixelIndex(int x, int y) { 767 768// Simple row-major pattern (no zigzag): 769 return y * MATRIX_WIDTH + x; 770 771// Simple column-major pattern (no zigzag): 772// return x * MATRIX_HEIGHT + y; 773 774// Column-major zigzag pattern: 775// if (x % 2 == 0) { 776 // Even columns go top to bottom 777// return x * MATRIX_HEIGHT + y; 778// } else { 779 // Odd columns go bottom to top 780// return x * MATRIX_HEIGHT + (MATRIX_HEIGHT - 1 - y); 781// } 782 783// Flipped row-major zigzag pattern: 784// if (y % 2 == 0) { 785 // Even rows go right to left 786// return y * MATRIX_WIDTH + (MATRIX_WIDTH - 1 - x); 787// } else { 788 // Odd rows go left to right 789// return y * MATRIX_WIDTH + x; 790// } 791} 792 793void clearDisplay() { 794 fill_solid(leds, NUM_LEDS, BLACK); 795 FastLED.show(); 796} 797 798void updateDisplay() { 799 fill_solid(leds, NUM_LEDS, BLACK); 800 801 // Draw the fixed blocks 802 for (int x = 0; x < MATRIX_WIDTH; x++) { 803 for (int y = 0; y < MATRIX_HEIGHT; y++) { 804 if (gameBoard[x][y]) { 805 leds[getPixelIndex(x, y)] = boardColors[x][y]; 806 } 807 } 808 } 809 810 // Draw the current piece 811 for (int i = 0; i < 4; i++) { 812 int x = currentX + currentTetrominoSet[currentPiece].shapes[currentRotation][i][0]; 813 int y = currentY + currentTetrominoSet[currentPiece].shapes[currentRotation][i][1]; 814 815 if (x >= 0 && x < MATRIX_WIDTH && y >= 0 && y < MATRIX_HEIGHT) { 816 leds[getPixelIndex(x, y)] = currentTetrominoSet[currentPiece].color; 817 } 818 } 819 820 FastLED.show(); 821} 822 823// Game mechanics 824bool isValidPosition(int pieceIndex, int rotation, int posX, int posY) { 825 for (int i = 0; i < 4; i++) { 826 int x = posX + currentTetrominoSet[pieceIndex].shapes[rotation][i][0]; 827 int y = posY + currentTetrominoSet[pieceIndex].shapes[rotation][i][1]; 828 829 if (x < 0 || x >= MATRIX_WIDTH || y < 0 || y >= MATRIX_HEIGHT) { 830 return false; 831 } 832 833 if (y >= 0 && gameBoard[x][y]) { 834 return false; 835 } 836 } 837 return true; 838} 839 840bool movePieceLeft() { 841 if (isValidPosition(currentPiece, currentRotation, currentX - 1, currentY)) { 842 currentX--; 843 playMoveSound(); 844 return true; 845 } 846 return false; 847} 848 849bool movePieceRight() { 850 if (isValidPosition(currentPiece, currentRotation, currentX + 1, currentY)) { 851 currentX++; 852 playMoveSound(); 853 return true; 854 } 855 return false; 856} 857 858bool movePieceDown() { 859 if (isValidPosition(currentPiece, currentRotation, currentX, currentY + 1)) { 860 currentY++; 861 return true; 862 } 863 return false; 864} 865 866bool rotatePiece() { 867 byte nextRotation = (currentRotation + 1) % 4; 868 if (isValidPosition(currentPiece, nextRotation, currentX, currentY)) { 869 currentRotation = nextRotation; 870 playRotateSound(); 871 return true; 872 } 873 // Try wall kick (adjust the position if rotation is blocked by a wall) 874 // Try moving left 875 if (isValidPosition(currentPiece, nextRotation, currentX - 1, currentY)) { 876 currentX--; 877 currentRotation = nextRotation; 878 playRotateSound(); 879 return true; 880 } 881 // Try moving right 882 if (isValidPosition(currentPiece, nextRotation, currentX + 1, currentY)) { 883 currentX++; 884 currentRotation = nextRotation; 885 playRotateSound(); 886 return true; 887 } 888 return false; 889} 890 891void placePiece() { 892 for (int i = 0; i < 4; i++) { 893 int x = currentX + currentTetrominoSet[currentPiece].shapes[currentRotation][i][0]; 894 int y = currentY + currentTetrominoSet[currentPiece].shapes[currentRotation][i][1]; 895 896 if (x >= 0 && x < MATRIX_WIDTH && y >= 0 && y < MATRIX_HEIGHT) { 897 gameBoard[x][y] = true; 898 boardColors[x][y] = currentTetrominoSet[currentPiece].color; 899 playLandSound(); 900 } 901 } 902} 903 904 905 906bool spawnNewPiece() { 907 static byte lastPiece = random(0, 7); 908 byte newPiece; 909 910 do { 911 newPiece = random(0, 7); 912 } while (newPiece == lastPiece && random(0, 100) < 70); 913 914 lastPiece = newPiece; 915 currentPiece = newPiece; 916 917 // Use different rotation options based on game mode 918 if (gameMode == MODE_KIDS) { 919 currentRotation = 0; // Kids mode pieces don't need rotation 920 } else { 921 currentRotation = random(0, 4); 922 } 923 924 currentX = (MATRIX_WIDTH / 2) - 1; 925 currentY = 0; 926 927 if (!isValidPosition(currentPiece, currentRotation, currentX, currentY)) { 928 return false; 929 } 930 return true; 931} 932 933void clearLines() { 934 int linesCleared = 0; 935 936 for (int y = MATRIX_HEIGHT - 1; y >= 0; y--) { 937 bool lineIsFull = true; 938 939 // Check if the line is full 940 for (int x = 0; x < MATRIX_WIDTH; x++) { 941 if (!gameBoard[x][y]) { 942 lineIsFull = false; 943 break; 944 } 945 } 946 947 if (lineIsFull) { 948 linesCleared++; 949 950 // Flash the line 951 for (int i = 0; i < 3; i++) { 952 // Flash white 953 for (int x = 0; x < MATRIX_WIDTH; x++) { 954 leds[getPixelIndex(x, y)] = CRGB::White; 955 } 956 FastLED.show(); 957 delay(50); 958 959 // Flash black 960 for (int x = 0; x < MATRIX_WIDTH; x++) { 961 leds[getPixelIndex(x, y)] = CRGB::Black; 962 } 963 FastLED.show(); 964 delay(50); 965 } 966 967 // Move all lines above this one down 968 for (int moveY = y; moveY > 0; moveY--) { 969 for (int x = 0; x < MATRIX_WIDTH; x++) { 970 gameBoard[x][moveY] = gameBoard[x][moveY - 1]; 971 boardColors[x][moveY] = boardColors[x][moveY - 1]; 972 } 973 } 974 975 // Clear the top line 976 for (int x = 0; x < MATRIX_WIDTH; x++) { 977 gameBoard[x][0] = false; 978 } 979 980 // Since the lines have moved down, we need to check this row again 981 y++; 982 } 983 } 984 985 // Update score 986 if (linesCleared > 0) { 987 988 playClearLineSound(); 989 // More points for clearing multiple lines at once 990 score += (linesCleared * linesCleared) * 100; 991 } 992} 993 994void resetGame() { 995 playStartSound(); 996 // Set the appropriate tetromino set based on game mode 997 currentTetrominoSet = (gameMode == MODE_KIDS) ? kidstetrominos : tetrominos; 998 999 // Clear the display first 1000 clearDisplay(); 1001 1002 // Reset game board 1003 for (int x = 0; x < MATRIX_WIDTH; x++) { 1004 for (int y = 0; y < MATRIX_HEIGHT; y++) { 1005 gameBoard[x][y] = false; 1006 } 1007 } 1008 1009 // Reset game parameters 1010 gameSpeed = INITIAL_GAME_SPEED; 1011 gameOver = false; 1012 score = 0; 1013 gameOverScreenShown = false; 1014 1015 // Show a quick start animation 1016 for (int i = 0; i < NUM_LEDS; i++) { 1017 leds[i] = CRGB::Green; 1018 FastLED.show(); 1019 delay(20); 1020 } 1021 clearDisplay(); 1022 delay(500); 1023 1024 // Spawn a new piece 1025 spawnNewPiece(); 1026} 1027 1028void displaySplashScreen() { 1029 playStartSound(); 1030 const char text[] = "MINI TETRIS"; 1031 const int textLength = strlen(text); 1032 const int totalWidth = textLength * 8; // Each letter is 8 pixels wide 1033 const CRGB colors[] = {CRGB::Red, CRGB::Green, CRGB::Blue, CRGB::Yellow, 1034 CRGB::Cyan, CRGB::Magenta, CRGB::Orange}; 1035 const int numColors = sizeof(colors) / sizeof(colors[0]); 1036 1037 // Scroll the entire text from right to left 1038 for (int scroll = 8; scroll >= -totalWidth; scroll--) { 1039 clearDisplay(); 1040 1041 int letterPos = 0; 1042 for (int i = 0; i < textLength; i++) { 1043 char c = text[i]; 1044 int xPos = scroll + (i * 8); 1045 1046 // Skip spaces 1047 if (c == ' ') { 1048 continue; 1049 } 1050 1051 // Map characters to array indices 1052 int letterIndex; 1053 switch (c) { 1054 case 'M': letterIndex = 0; break; 1055 case 'I': letterIndex = 1; break; 1056 case 'N': letterIndex = 2; break; 1057 case 'T': letterIndex = 3; break; 1058 case 'E': letterIndex = 4; break; 1059 case 'R': letterIndex = 5; break; 1060 case 'S': letterIndex = 6; break; 1061 default: continue; 1062 } 1063 1064 // Display letter with color cycling 1065 displayLetter(letters[letterIndex], xPos, colors[letterPos % numColors]); 1066 letterPos++; 1067 } 1068 1069 FastLED.show(); 1070 delay(60); // Adjust speed of scrolling 1071 } 1072 1073 // Final flash effect 1074 for (int i = 0; i < 3; i++) { 1075 fill_solid(leds, NUM_LEDS, CRGB::White); 1076 FastLED.show(); 1077 delay(100); 1078 clearDisplay(); 1079 delay(100); 1080 } 1081} 1082 1083 1084 1085void displayGameOver() { 1086 playGameOverSound(); 1087 1088 // Flash "Game Over" effect 1089 for (int i = 0; i < 3; i++) { 1090 fill_solid(leds, NUM_LEDS, CRGB::Red); 1091 FastLED.show(); 1092 delay(500); 1093 clearDisplay(); 1094 FastLED.show(); 1095 delay(500); 1096 } 1097 1098 // Display final score 1099 delay(500); 1100 displayScrollingScore(score); 1101 1102 // Show stable smiley and wait for restart 1103 displayEndAnimation(); 1104 1105 gameOverScreenShown = true; 1106}
Downloadable files
Schematic
...
Schematic.jpg

Comments
Only logged in users can leave comments