Table of Contents

Tetris

Vlad Andra, 331CC

Introducere

În acest proiect voi implementa un joc de Tetris controlat de un joystick. Jocul va fi afișat pe o matrice de LED-uri 8×8.
Detaliile jocului (scor, highscore, game over etc.) se vor afișa pe un display LCD 20×4.
Buzzer-ul va cânta melodia Tetris. De fiecare dată când se va face un rând în jocul de Tetris, buzzer-ul va întrerupe melodia și va emite un sunet de confirmare, după care rândul va fi eliminat de pe matricea de LED-uri.
Am ales acest proiect nu numai pentru că este un joc retro foarte drăguț, ci și pentru că am putut include multe dintre informațiile învățate la laborator, precum lucrul cu GPIO, SPI, I2C, EEPROM, întreruperi și multe altele. :D

Descriere generală

Jocul funcționează astfel:

Pentru înțelegerea interacțiunii dintre joc și jucător, am realizat o schemă simplificată. Schema bloc este următoarea:

Hardware Design

Piese utilizate:

Modulul de comunicație MAX7219 mi s-a părut interesant, deoarece acesta folosește protocolul SPI pentru comenzi ca și MOSI, fără MISO. Acesta facilitează controlul unei matrice de LED-uri folosind un număr mic de pini. Interesanți sunt pinii DIN și CS:

  • DIN = Data In, transmite date în mod serial (câte 1 bit) către MAX7219, comenzi precum ce LED-uri să aprindă.
  • CS = Chip Select, însă nu funcționează chiar ca Slave Select-ul cu care eram obișnuiți. Pinul este folosit să marcheze când ar trebui începute citirea datelor. Este setat la 0 ca să fie începută sesiunea de comunicare (să accepte date pe DIN) și la 1 ca să se încheie comunicarea.

Pinul DOUT al modulului este folosit doar dacă dorim să conectăm mai multe matrice de LED-uri între ele.

Pentru a vedea cum conectez pinii analogici și digitali am ales să realizez schematicul în Fusion. Majoritatea simbolurilor și footprint-urilor sunt făcute de mine, însă am plecat la câteva piese de la unele deja existente [0]. Schema electrică este următoarea:

Inițial, am conectat și testat toate componentele pentru a mă asigura că funcționează corespunzător. Am considerat că ar fi mai potrivit pentru un joc să conectez piesele pe o bucată de plexiglas, pentru a avea tot ce am nevoie într-un mod accesibil pentru utilizator. De asemenea, plănuiesc să conectez proiectul la o baterie, pentru a putea fi ținut în mână de jucător. Pentru a testa faptul că funcționează componentele, am folosit următoarele resurse: [1], [2], [3]. Am folosit bibliotecile LiquidCrystal_I2c și LedControl, însă voi detalia mai mult despre acestea la partea de software.


Software Design

Logica jocului

Mediul de dezvoltare al proiectului este PlatformIO.
Stadiul jocului de pe matricea de LED-uri 8×8 este reprezentat de o matrice 12×8 în cod. Motivul pentru care mai avem câteva linii în plus este pentru că o piesă 'cade' progresiv în joc, nu apare din start pe matricea de LED-uri. Matricea conține valori de 0, 1, 2 și 3:

Pentru înțelegerea jocului, am realizat un schelet cu mai puține detalii, pe care îl voi aprofunda în următoarele secțiuni.

void loop() {
    // Daca butonul de SWITCH de pe JoyStick e apasat de mai mult de 3 secunde, resetam jocul.
    if (restartGame) {
        // Aprinde LED-urile, reseteaza variabilele...
        delay(5000);
        // Restart game
    }
    if (!gameOver) {
        if (intervalJoystick) {
            // ... Citim input-urile de la Joystick ...
            if (X_val < CENTRU - 200) {
                moveLeft();
            } else if (X_val > CENTRU + 200) {
                moveRight();
            } else if (Y_val < CENTRU - 200) {
                rotateLeft();
            } else if (Y_val > 840) {
                forceTetrominoDown();
                selectRandomTetromino();
            }
        }
        // Daca a trecut un anumit interval de timp, se actualizeaza jocul.
        if (intervalUpdate) {
            // Daca ajugem jos, adaugam scor si trecem la o piesa noua.
            if (pieceSettled) {
                int score = addScore();
                selectRandomTetromino();
            }
            // Altfel, mutam piesa in jos.
            else {
                moveDown();
            }
        }
    }
    else {
        // Opreste melodia, afiseaza 'Game Over', reseteaza variabilele...
    }
}

Interacțiunea joc-jucător

const int SEED_PIN = A2;
 
void setup() {
    randomSeed(analogRead(SEED_PIN) + micros());
}
 
void selectRandomTetromino() {
    int probabilitate[5] = {100, 100, 100, 100, 100};
    if (pieceId != -1) {
    // Reducem probabilitatea unei piese de a aparea de doua ori
        probabilitate[pieceId] = max(probabilitate[pieceId] - 50, 0);
    }
    // Daca apare o piesa de 2 ori, ii scadem din nou probabilitatea de a aparea.
    if (freqPiece >= 2) {
        probabilitate[pieceId] = max(probabilitate[pieceId] - 30, 0);
    }
 
    // .... Calcul probabilitati dupa intervale pe baza greutatilor ...
    int randomPiece = selectedPiece;
    if (pieceId != randomPiece) {
        freqPiece = 0;
    }
    else {
        freqPiece++;
    }
    pieceId = randomPiece;
}

Biblioteci Arduino

Pentru implementarea codului, am folosit următoarele biblioteci și resurse:

// Argumente: adresa I2C a modulului LCD, numărul de coloane și numărul de rânduri al LCD-ului.
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 20, 4);
 
lcd.init();
lcd.backlight();
lcd.clear();
// setCursor = setează linia și coloana cursorului.
lcd.setCursor(0, 0);
lcd.print("Tetris Game");
lcd.setCursor(0, 1);
lcd.print("is Starting...");
// Pini folosiți de comunicarea SPI și numărul de module MAX7219.
LedControl lc = LedControl(DATA_IN_PIN, CLK_PIN, LOAD_PIN, NUM_MAX7219);
 
lc.shutdown(0, false);
lc.setIntensity(0, 1);
lc.clearDisplay(0);
 
for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 8; j++) {
        lc.setLed(0, i, j, gameGrid[i + 4][j] != 0);
    }
}
void setup() {
    Timer1.initialize(100000); // Initializam timerul cu 1 secunda.
    Timer1.attachInterrupt(urmatoareaNotaCantecel); 
}
 
void loop() {
 // .... Restul jocului ...
  int switch_state = digitalRead(SW_pin);
  if (switch_state == LOW) {
    if (buttonPressTime == 0) {
            buttonPressTime = millis(); 
        }
    // Daca butonul e apasat timp de 3 secunde, restartam jocul.
    else if (millis() - buttonPressTime >= 3000) {
        restartGame();
        buttonPressTime = 0;
    }
  }
 // ....
}
 
void restartGame() {
  Timer1.stop();
  // ... Resetare variabile ... 
  gameOver = false;
  playSong = true;
  currentNote = 0;
  Timer1.start();
}
MD_Parola lc_animatie = MD_Parola(HARDWARE_TYPE, DATA_IN_PIN, CLK_PIN, LOAD_PIN, NUM_MAX7219);
 
void setup() {
    lc_animatie.begin();
    // Setam brightness-ul.
    lc_animatie.setIntensity(0); 
    // Aliniem textul la centru, cu speed time 100 si pause time 1000, cu animatia de scroll left.
    lc_animatie.displayText("TETRIS", PA_CENTER, 100, 1000, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
    while (!lc_animatie.displayAnimate()) {
        // Asteptam sa se termine animatia.
    }
}
int address1 = 0;
 
void setup() {
    // Extragem valoarea highscore-ului din jocuri anterioare din memoria EEPROM.
    EEPROM.get(address1, retrievedValue1);
}
 
int addScore() {
    // ...
    if (new_score > highscore) {
        highscore = new_score;
        EEPROM.put(address1, highscore);
    }
    return new_score;
}

Optimizări

Rezultate Obţinute

Concluzii

Deși nu a fost unul foarte complex din punct de vedere al implementării hardware, consider că proiectul m-a ajutat, deoarece am reușit să interacționez cu multe dintre cunoștințele despre care am învățat la laborator și acum știu să lucrez cu ele atât la nivel de regiștri, cât și la nivelul bibliotecilor disponibile pentru lucrul cu Arduino. Mi-aș fi dorit să implementez proiectul pentru mai multe matrice de LED-uri legate între ele, deoarece nu mi-am dat seama la început că e un spațiu atât de mic pentru Tetris. Cu toate acestea, consider că a ieșit bine și mă bucur că am realizat acest proiect și că acum am o jucărie nouă. ^_^

Download

proiect_pm_andra_vlad.zip
schematic_pm_andra_vlad.zip

Jurnal

25.04.2024 - descrierea proiectului
03.05.2024 - adăugarea schemei bloc și a componentelor hardware
12.05.2024 - începere realizare hardware
15.05.2024 - realizarea schemei electrice și descrierea stadiului hardware
20.05.2024 - corectare schematic și adăugare updates hardware
23.05.2024 - adăugare implementare software
24.05.2024 - finalizare implementare software și adăugarea fișierelor
25.05.2024 - adăugare demo

Bibliografie/Resurse

[0] Simbol și footprint MAX7219: https://www.snapeda.com/parts/MAX7219/Analog%20Devices/view-part/
[1] Interfacing Buzzer to Arduino: https://www.instructables.com/Interfacing-Buzzer-to-Arduino/
[2] Arduino - LCD I2C: https://arduinogetstarted.com/tutorials/arduino-lcd-i2c
[3] How to control 8×8 dot Matrix with MAX7219 and Arduino: https://youtu.be/SGjQ-E3UD7A
[4] MD Parola library for the Max7219 & Arduino: https://www.youtube.com/watch?v=_H2v8uDgqps
[5] Tetris Song On Arduino: https://github.com/robsoncouto/arduino-songs/blob/master/tetris/tetris.ino

Export to PDF