Tetris

03.01.2026

E un proiect foarte fain și simplu de realizat. Și pe lângă asta este și distractiv. Cred că a durat mai mult de 2 ore ca să fac tot codul (m-am inspirat de pe site-uri am căutat pe net etc.) dar vouă vă las codul mai jos ca să vă fie mult mai ușor. Am folosit librăria led control fiindcă MD_MAX72XX îmi dădea erori. Pentru a mișca piesele miști joystick-ul stânga dreapta. Pentru a le roti apasă pe joystick (butonul acela). . Pentru a accelera coborârea miști joystick-ul în jos. Când piesele vor ajunge sus ecranul se face roșu apoi va scrie: ,,GAME OVER,,. Pentru a ieși de aici apasă pe joystick (butonul acela). Hai acum să vă explic cum îl realizăm:

Componente necesare:

  • Arduino Nano (sau alt microcontoler compatibil cu arduino)

  • 4× LED Matrix 8×8 cu MAX7219

  • Joystick analogic 5 pini

  • Jumper wires

  • Breadboard (opțional dar recomand să folosești)


  • Conexiuni:

    • MAX7219 LED Matrix modules (4 module în serie)

      • DIN → D11

      • CLK → D12

      • CS → D10

      • VCC → 5V

      • GND → GND

    • Joystick analogic 5 pini

      • VRx → A0 (stânga/dreapta)

      • VRy → A1 (sus/jos, opțional)

      • SW → D2 (buton pentru rotire)

      • VCC → 5V

      • GND → GND

    Cod:

    #include <LedControl.h>  // Library for driving MAX7219 LED matrices

    // MAX7219 wiring pins
    #define DIN_PIN     6    // Data input
    #define CLK_PIN     5    // Clock pin
    #define CS_PIN      3    // Chip select
    #define NUM_MODULES 4    // Number of 8×8 modules in cascade

    LedControl lc(DIN_PIN, CLK_PIN, CS_PIN, NUM_MODULES);

    // Joystick and button pins
    #define VRx A0  // Joystick X-axis (left/right)
    #define VRy A1  // Joystick Y-axis (up/down)
    #define SW  2   // Push-button switch for rotation

    // Display dimensions
    const int SCREEN_W = 8;                     // Width of one module
    const int SCREEN_H = SCREEN_W * NUM_MODULES; // Total height (32 rows)

    // Playfield buffer: each byte is one row of 8 bits
    uint8_t field[SCREEN_H];

    // Timing control
    unsigned long lastDrop     = 0;    // Time of last automatic drop
    unsigned long dropInterval = 500;  // Drop interval in ms (adjusted by joystick)
    unsigned long lastMove     = 0;    // Time of last horizontal move
    const unsigned long moveInterval    = 200; // Min ms between moves
    const unsigned long refreshInterval = 33;  // ~30 FPS
    unsigned long lastRefresh  = 0;    // Time of last screen refresh

    // Buffer to track previous frame for diff updates
    uint8_t prevBuf[NUM_MODULES][SCREEN_W];

    // Structure for current falling block
    struct Block {
      const int (*shape)[2];  // Pointer to array of {x,y} offsets
      int len;                // Number of cells (always 4)
      int x, y;               // Top-left origin position
      int rotation;           // Rotation index
      char type;              // Block type identifier
    } current;

    // Definitions of the seven Tetris shapes and their rotations
    const int I_SHAPE[2][4][2] = {
      {{0,0},{0,1},{0,2},{0,3}},   // Vertical
      {{-1,1},{0,1},{1,1},{2,1}}   // Horizontal
    };
    const int O_SHAPE[1][4][2] = {
      {{0,0},{1,0},{0,1},{1,1}}    // Square (no rotation)
    };
    const int T_SHAPE[4][4][2] = {
      {{1,0},{0,1},{1,1},{2,1}},
      {{1,0},{1,1},{1,2},{0,1}},
      {{0,1},{1,1},{2,1},{1,2}},
      {{1,0},{1,1},{1,2},{2,1}}
    };
    const int L_SHAPE[4][4][2] = {
      {{0,0},{0,1},{0,2},{1,2}},
      {{0,0},{1,0},{2,0},{0,1}},
      {{0,0},{1,0},{1,1},{1,2}},
      {{2,0},{0,1},{1,1},{2,1}}
    };
    const int J_SHAPE[4][4][2] = {
      {{1,0},{1,1},{1,2},{0,2}},
      {{0,0},{0,1},{1,1},{2,1}},
      {{0,0},{1,0},{0,1},{0,2}},
      {{0,0},{1,0},{2,0},{2,1}}
    };
    const int S_SHAPE[2][4][2] = {
      {{1,0},{2,0},{0,1},{1,1}},
      {{1,0},{1,1},{2,1},{2,2}}
    };
    const int Z_SHAPE[2][4][2] = {
      {{0,0},{1,0},{1,1},{2,1}},
      {{2,0},{1,1},{2,1},{1,2}}
    };

    // 8×8 bitmaps for letters in the Game Over screen
    static const uint8_t PAT_G[8] = {0x3C,0x42,0x40,0x4E,0x42,0x42,0x3C,0x00};
    static const uint8_t PAT_A[8] = {0x18,0x24,0x42,0x7E,0x42,0x42,0x42,0x00};
    static const uint8_t PAT_M[8] = {0x42,0x66,0x5A,0x5A,0x42,0x42,0x42,0x00};
    static const uint8_t PAT_E[8] = {0x7E,0x40,0x5C,0x40,0x40,0x40,0x7E,0x00};
    static const uint8_t PAT_O[8] = {0x3C,0x42,0x42,0x42,0x42,0x42,0x3C,0x00};
    static const uint8_t PAT_V[8] = {0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00};
    static const uint8_t PAT_R[8] = {0x7C,0x42,0x42,0x7C,0x48,0x44,0x42,0x00};

    // Clear all LEDs on every module
    void clearAll() {
      for (int m = 0; m < NUM_MODULES; m++) {
        lc.clearDisplay(m);
      }
    }

    // Read and debounce the push-button switch
    bool readButton() {
      if (digitalRead(SW) == LOW) {
        delay(20);
        if (digitalRead(SW) == LOW) {
          while (digitalRead(SW) == LOW); // Wait for release
          return true;
        }
      }
      return false;
    }

    // Return the bitmap for a given character
    const uint8_t* letterPattern(char c) {
      switch (c) {
        case 'G': return PAT_G;
        case 'A': return PAT_A;
        case 'M': return PAT_M;
        case 'E': return PAT_E;
        case 'O': return PAT_O;
        case 'V': return PAT_V;
        case 'R': return PAT_R;
        default:  return PAT_E;
      }
    }

    // Game Over animation: flash, display "GAME", wait 1s, then display "OVER"
    void gameOverSequence() {
      // 1) Flash all LEDs three times
      for (int i = 0; i < 3; i++) {
        clearAll();
        delay(500);
        for (int m = 0; m < NUM_MODULES; m++)
          for (int r = 0; r < SCREEN_W; r++)
            lc.setRow(m, r, 0xFF);
        delay(500);
      }

      // 2) Display "GAME" rotated 90° CW
      const char* w1 = "GAME";
      for (int seg = 0; seg < 4; seg++) {
        const uint8_t* pat = letterPattern(w1[seg]);
        uint8_t rot[8] = {};
        // Rotate 90° CW: (x,y) → (7-y, x)
        for (int y = 0; y < 8; y++) {
          for (int x = 0; x < 8; x++) {
            if (pat[y] & (1 << x)) {
              int nx = 7 - y;
              int ny = x;
              rot[ny] |= (1 << nx);
            }
          }
        }
        int module = NUM_MODULES - 1 - seg;
        for (int row = 0; row < 8; row++) {
          lc.setRow(module, row, rot[row]);
        }
      }
      delay(1000);  // Wait 1 second before showing OVER

      // 3) Display "OVER" rotated 90° CW
      const char* w2 = "OVER";
      for (int seg = 0; seg < 4; seg++) {
        const uint8_t* pat = letterPattern(w2[seg]);
        uint8_t rot[8] = {};
        for (int y = 0; y < 8; y++) {
          for (int x = 0; x < 8; x++) {
            if (pat[y] & (1 << x)) {
              int nx = 7 - y;
              int ny = x;
              rot[ny] |= (1 << nx);
            }
          }
        }
        int module = NUM_MODULES - 1 - seg;
        for (int row = 0; row < 8; row++) {
          lc.setRow(module, row, rot[row]);
        }
      }
      delay(1000);  // Hold OVER for 1 second

      // 4) Wait for button press to restart
      while (digitalRead(SW) != LOW) delay(10);
      while (digitalRead(SW) == LOW) delay(10);
    }

    // Spawn a new random Tetris block at the top center
    void spawnBlock() {
      int r = random(7);
      int sx = SCREEN_W / 2 - 2;  // Center X
      current.rotation = 0;
      switch (r) {
        case 0: current = {I_SHAPE[0],4,sx,0,0,'I'}; break;
        case 1: current = {O_SHAPE[0],4,sx,0,0,'O'}; break;
        case 2: current = {T_SHAPE[0],4,sx,0,0,'T'}; break;
        case 3: current = {L_SHAPE[0],4,sx,0,0,'L'}; break;
        case 4: current = {J_SHAPE[0],4,sx,0,0,'J'}; break;
        case 5: current = {S_SHAPE[0],4,sx,0,0,'S'}; break;
        case 6: current = {Z_SHAPE[0],4,sx,0,0,'Z'}; break;
      }
    }

    // Reset game state: clear playfield and display
    void resetGame() {
      memset(field, 0, sizeof(field));
      clearAll();
      for (int m = 0; m < NUM_MODULES; m++)
        for (int r = 0; r < SCREEN_W; r++)
          prevBuf[m][r] = 0;
      spawnBlock();
      lastDrop    = millis();
      lastRefresh = millis();
    }

    // Draw playfield and current block with diff updates
    void writeBuffer() {
      uint8_t buf[NUM_MODULES][SCREEN_W] = {};

      // Draw fixed blocks
      for (int y = 0; y < SCREEN_H; y++) {
        uint8_t row = field[y];
        if (!row) continue;
        int mod = NUM_MODULES - 1 - (y / SCREEN_W);
        int bit = 1 << (7 - (y % SCREEN_W));
        for (int x = 0; x < SCREEN_W; x++) {
          if (row & (1 << x)) buf[mod][x] |= bit;
        }
      }

      // Draw current falling block
      for (int i = 0; i < current.len; i++) {
        int xx = current.x + current.shape[i][0];
        int yy = current.y + current.shape[i][1];
        if (xx < 0 || xx >= SCREEN_W || yy < 0 || yy >= SCREEN_H) continue;
        int mod = NUM_MODULES - 1 - (yy / SCREEN_W);
        int bit = 1 << (7 - (yy % SCREEN_W));
        buf[mod][xx] |= bit;
      }

      // Update only changed rows
      for (int m = 0; m < NUM_MODULES; m++) {
        for (int r = 0; r < SCREEN_W; r++) {
          if (buf[m][r] != prevBuf[m][r]) {
            lc.setRow(m, r, buf[m][r]);
            prevBuf[m][r] = buf[m][r];
          }
        }
      }
    }

    // Check for collision at position (nx, ny)
    bool checkCollision(int nx, int ny) {
      for (int i = 0; i < current.len; i++) {
        int xx = nx + current.shape[i][0];
        int yy = ny + current.shape[i][1];
        if (xx < 0 || xx >= SCREEN_W || yy >= SCREEN_H) return true;
        if (yy >= 0 && (field[yy] & (1 << xx))) return true;
      }
      return false;
    }

    // Fix current block into field and clear full lines
    void placeBlock() {
      for (int i = 0; i < current.len; i++) {
        int xx = current.x + current.shape[i][0];
        int yy = current.y + current.shape[i][1];
        if (yy >= 0 && yy < SCREEN_H) field[yy] |= (1 << xx);
      }
      // Clear any full rows
      for (int y = 0; y < SCREEN_H; y++) {
        if (field[y] == 0xFF) {
          for (int j = y; j > 0; j--) field[j] = field[j - 1];
          field[0] = 0;
        }
      }
    }

    // Rotate block with rollback on collision
    void rotateBlock() {
      int limit = (current.type=='I'||current.type=='S'||current.type=='Z') ? 2
                : (current.type=='O' ? 1 : 4);
      int nr = (current.rotation + 1) % limit;
      const int (*ns)[2] = nullptr;
      if      (current.type=='I') ns = I_SHAPE[nr];
      else if (current.type=='O') ns = O_SHAPE[0];
      else if (current.type=='T') ns = T_SHAPE[nr];
      else if (current.type=='L') ns = L_SHAPE[nr];
      else if (current.type=='J') ns = J_SHAPE[nr];
      else if (current.type=='S') ns = S_SHAPE[nr];
      else if (current.type=='Z') ns = Z_SHAPE[nr];

      Block bak = current;
      current.shape    = ns;
      current.rotation = nr;
      if (checkCollision(current.x, current.y)) current = bak;
    }

    void setup() {
      pinMode(SW, INPUT_PULLUP);     // Button input with pull-up
      randomSeed(analogRead(0));     // Seed RNG
      for (int m = 0; m < NUM_MODULES; m++) {
        lc.shutdown(m, false);
        lc.setIntensity(m, 8);
        lc.clearDisplay(m);
        for (int r = 0; r < SCREEN_W; r++) prevBuf[m][r] = 0;
      }
      resetGame();  // Start the game
    }

    void loop() {
      unsigned long now = millis();

      // Horizontal movement via joystick X-axis
      int ax = analogRead(VRx);
      if (now - lastMove > moveInterval) {
        if (ax < 400 && !checkCollision(current.x + 1, current.y)) {
          current.x++; lastMove = now;
        } else if (ax > 600 && !checkCollision(current.x - 1, current.y)) {
          current.x--; lastMove = now;
        }
      }

      // Rotate on button press
      if (readButton()) rotateBlock();

      // Adjust drop speed via joystick Y-axis (down = faster)
      int ay = analogRead(VRy);
      dropInterval = 700 - constrain(map(ay,512,1023,0,690),0,690);

      // Automatic drop & top-hit detection
      if (now - lastDrop > dropInterval) {
        lastDrop = now;
        if (!checkCollision(current.x, current.y + 1)) {
          current.y++;
        } else {
          // Check for game over (block at top)
          bool hitTop = false;
          for (int i = 0; i < current.len; i++) {
            if (current.y + current.shape[i][1] == 0) {
              hitTop = true; break;
            }
          }
          if (hitTop) {
            gameOverSequence();
            resetGame();
            return;
          } else {
            placeBlock();
            spawnBlock();
          }
        }
      }

      // Refresh display at ~30 FPS
      if (now - lastRefresh >= refreshInterval) {
        writeBuffer();
        lastRefresh = now;
      }
    }