This shows you the differences between two versions of the page.
pm:prj2025:fstancu:david_ionut.luca [2025/05/25 23:39] david_ionut.luca |
pm:prj2025:fstancu:david_ionut.luca [2025/05/28 09:53] (current) david_ionut.luca |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Snake Game - Luca David-Ionut 334CD ====== | ====== Snake Game - Luca David-Ionut 334CD ====== | ||
+ | |||
+ | **__MILESTONE 1 - TEMA PROIECT__** | ||
Acest proiect constă într-o implementare a jocului Snake clasic pe platforma Arduino Uno R3. O caracteristică importantă este controlul vitezei șarpelui prin intermediul unui potențiometru, iar scorul obținut de jucător este vizualizat pe un display LCD. | Acest proiect constă într-o implementare a jocului Snake clasic pe platforma Arduino Uno R3. O caracteristică importantă este controlul vitezei șarpelui prin intermediul unui potențiometru, iar scorul obținut de jucător este vizualizat pe un display LCD. | ||
- | Milestone 2 (HARDWARE): | + | **__MILESTONE 2 - HARDWARE__** |
Componente: LCD 12864 Albastru ST7920, Modul potentiometru rotativ RV09, 4 x Buton Mini 6x6x5 4 pini. | Componente: LCD 12864 Albastru ST7920, Modul potentiometru rotativ RV09, 4 x Buton Mini 6x6x5 4 pini. | ||
Line 26: | Line 28: | ||
Pinii LCD-ului ST7920 128x64 sunt conectati de placa Arduino in felul urmator: | Pinii LCD-ului ST7920 128x64 sunt conectati de placa Arduino in felul urmator: | ||
- | * GND -> GND (Pinul de masă al LCD-ului este conectat la masa Arduino. Acesta este punctul de referință pentru toate tensiunile din circuit.) | + | * GND -> GND (Acesta este punctul de referință pentru toate tensiunile din circuit) |
- | + | * Vcc -> 5V (Pinul de alimentare al LCD-ului este conectat la 5V de pe Arduino) | |
- | * Vcc -> 5V (Pinul de alimentare al LCD-ului este conectat la 5V de pe Arduino.) | + | * RS -> PIN 10 (Activează/dezactivează comunicarea SPI cu display-ul. Când e LOW, display-ul e gata să primească date) |
- | + | * R/W -> PIN 11 (Pinul prin care microcontrolerul trimite date seriale către display) | |
- | * RS -> PIN 10 (Pinul "Register Select" (RS) al LCD-ului este conectat la pinul 10 al Arduino. Acest pin este folosit pentru a diferenția între datele de comandă și datele de afișat trimise către LCD.) | + | * E -> PIN 13 (Generează semnalul de ceas pentru a sincroniza transferul de date seriale) |
- | + | * PSB -> GND (Setează display-ul în modul de comunicare serial (SPI), fiind conectat la masă) | |
- | * R/W -> PIN 11 (Pinul "Read/Write" (R/W) al LCD-ului este conectat la pinul 11 al Arduino. Acest pin controlează direcția de transfer al datelor.) | + | * RST -> PIN 8 (Resetează controlerul display-ului la o stare inițială, ștergând conținutul și reconfigurând setările) |
- | + | * BLA -> 3.3V (Acesta este pinul pozitiv de alimentare pentru iluminarea de fundal a LCD-ului) | |
- | * E -> PIN 13 (Pinul "Enable" (E) al LCD-ului este conectat la pinul 13 al Arduino. Acest pin este folosit pentru a sincroniza transferul de date între Arduino și LCD.) | + | * BLK -> GND (Acesta este pinul negativ de alimentare pentru iluminarea de fundal a LCD-ului) |
- | + | ||
- | * PSB -> GND (Pinul "Parallel/Serial Select" (PSB) al LCD-ului este conectat la GND. Acest pin selectează modul de comunicare cu LCD-ul.) | + | |
- | + | ||
- | * RST -> PIN 8 (Pinul "Reset" (RST) al LCD-ului este conectat la pinul 8 al Arduino. Acest pin este folosit pentru a reseta LCD-ul, aducându-l într-o stare inițială cunoscută.) | + | |
- | + | ||
- | * BLA -> 3.3V (Pinul "Backlight Anode" (BLA) al LCD-ului este conectat la 3.3V. Acesta este pinul pozitiv de alimentare pentru iluminarea de fundal a LCD-ului.) | + | |
- | + | ||
- | + | ||
- | * BLK -> GND (Pinul "Backlight Cathode" (BLK) al LCD-ului este conectat la GND. Acesta este pinul negativ de alimentare pentru iluminarea de fundal a LCD-ului.) | + | |
LCD-ul ST7920 comunică cu placa de dezvoltare Arduino prin protocolul SPI. | LCD-ul ST7920 comunică cu placa de dezvoltare Arduino prin protocolul SPI. | ||
Line 71: | Line 64: | ||
- | MILESTONE 3 - SOFTWARE | + | **__MILESTONE 3 - SOFTWARE__** |
**Stadiul proiectului** | **Stadiul proiectului** | ||
- | Proiect finalizat cu toate functiile precizate de mai sus complete. Sarpele poate fi controlat, creste, se poate schimba viteza si de asemenea se tine cont de scorul curent si recordul din sesiunea curenta din joc. | + | Proiectul este complet, toate funcțiile menționate fiind implementate. Șarpele poate fi controlat, crește în lungime, viteza este ajustabilă, iar scorul curent și recordul sesiunii de joc sunt înregistrate. |
**Biblioteci** | **Biblioteci** | ||
- | Biblioteca pe care am folosit-o este u8g2. Aceasta faciliteaza accesul la functiile LCD-ului, se poate afisa text usor, forme etc. | + | Am utilizat biblioteca u8g2, care a simplificat semnificativ accesul la funcțiile LCD-ului, permițând afișarea facilă de text și forme grafice. |
**Elemente originale** | **Elemente originale** | ||
- | Elementul de unicitate este schimbarea vitezei, nemaintalnind aceasta functionalitate cand am jucat diverite versiuni ale jocului "Snake". | + | Un aspect distinctiv al proiectului nostru este funcționalitatea de schimbare a vitezei. Aceasta nu am întâlnit-o în alte versiuni ale jocului 'Snake' pe care le-am experimentat. |
**Concepte aplicate** | **Concepte aplicate** | ||
- | Ca si concepte dobandite de la laborator am folosit intreruperi, generate de butoane cand sunt apasate. Rutinele de intreruperi schimba directia in care se misca sarpele. Am folosit si SPI, pentru comunicarea dintre placa si LCD, si de asemenea ADC, pentru citirea inputul-ui analog al potentiometrului. | + | Dintre conceptele asimilate la laborator, am aplicat întreruperile, generate de apăsarea butoanelor pentru a modifica direcția șarpelui. De asemenea, am utilizat SPI pentru comunicarea dintre placă și LCD, iar ADC-ul pentru a citi valorile analogice de la potențiometru. |
**Sumarul scheletului de cod** | **Sumarul scheletului de cod** | ||
- | In functia de loop, capul sarpelui isi actualizeaza coordonatele in functie de directia curenta. | + | În funcția principală a programului (loop), capul șarpelui își actualizează coordonatele conform direcției sale curente. Imediat după această actualizare a poziției, sistemul efectuează o serie de verificări cruciale. În primul rând, dacă șarpele atinge un punct de scor, lungimea sa este extinsă și scorul din joc este incrementat. Apoi, se verifică dacă player-ul s-a lovit de o parte a șarpelui sau de un perete, caz în care jocul se termină (GAME OVER); scorul este resetat, iar dacă acesta este mai mare decât recordul curent al sesiunii, recordul este actualizat. La finalul buclei, un delay controlat de 100ms + map(valoare_ADC) asigură că viteza șarpelui rămâne reglabilă. |
- | Dupa actualizarea pozitiei se verifica urmatoarele: | + | |
- | -Player-ul a atins un punct de scor -> Lungimea sarpelui va creste, iar scorul jocului va fi incrementat | + | |
- | -Player-ul s-a lovit de o parte a sarpelui sau perete -> GAME OVER, se reseteaza scor-ul (in cazul in care scorul este mai mare decat recordul curent, acesta va fi actualizat) | + | |
- | + | ||
- | De asemenea la finalul loop-ului exista un delay de 100ms + map(valoare_ADC) pentru ca viteza sarpelui sa poata fi reglabila. | + | |
**Un scurt demo al jocului** | **Un scurt demo al jocului** | ||
- | <HTML><iframe width="560" height="315" src="//www.youtube.com/embed/aUAevvRBBr4" frameborder="0" allowfullscreen></iframe></HTML> | + | <HTML><iframe width="560" height="315" src="//www.youtube.com/embed/eJmF104xEIE" frameborder="0" allowfullscreen></iframe></HTML> |
+ | |||
+ | |||
+ | |||
+ | **Calibrarea elementelor de senzoristica** | ||
+ | |||
+ | Am utilizat funcția map() pentru a converti valorile citite de la ADC într-un interval de milisecunde ajustabil. | ||
+ | |||
+ | |||
+ | #include <U8g2lib.h> | ||
+ | #include <avr/io.h> | ||
+ | #include <avr/interrupt.h> | ||
+ | #include <Arduino.h> | ||
+ | | ||
+ | const int buttonRight = 2; | ||
+ | const int buttonDown = 3; | ||
+ | const int buttonUp = 4; | ||
+ | const int buttonLeft = 9; | ||
+ | const int analogPin = A0; | ||
+ | |||
+ | #define LEFT 1 | ||
+ | #define DOWN 2 | ||
+ | #define UP 3 | ||
+ | #define RIGHT 4 | ||
+ | |||
+ | #define CS_PIN 10 | ||
+ | #define RST_PIN 8 | ||
+ | |||
+ | U8G2_ST7920_128X64_F_HW_SPI u8g2(U8G2_R0, CS_PIN, RST_PIN); | ||
+ | |||
+ | volatile int direction = 0; | ||
+ | int analogValue = 0; | ||
+ | int delay_ms = 0; | ||
+ | |||
+ | int dotX; | ||
+ | int dotY; | ||
+ | int newHeadX; | ||
+ | int newHeadY; | ||
+ | const int dotRadius = 4; | ||
+ | const int moveStep = 4; | ||
+ | const int gameWidth = 96; | ||
+ | | ||
+ | #define MAX_SNAKE_LENGTH 50 | ||
+ | int snakeX[MAX_SNAKE_LENGTH]; | ||
+ | int snakeY[MAX_SNAKE_LENGTH]; | ||
+ | int snakeLength = 1; | ||
+ | |||
+ | int scoreX; | ||
+ | int scoreY; | ||
+ | int score = 0; | ||
+ | int record = 0; | ||
+ | |||
+ | int manhattanDistance(int x1, int y1, int x2, int y2) { | ||
+ | return abs(x1 - x2) + abs(y1 - y2); | ||
+ | } | ||
+ | |||
+ | int isCloseToSnake(int x, int y) { | ||
+ | for (int i = 0; i < snakeLength; i++) { | ||
+ | if (manhattanDistance(x, y, snakeX[i], snakeY[i]) < 4) { | ||
+ | return 1; | ||
+ | } | ||
+ | } | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | void generateScorePoint() { | ||
+ | int newX, newY; | ||
+ | do { | ||
+ | newX = random(dotRadius, gameWidth - dotRadius); | ||
+ | newY = random(dotRadius, u8g2.getHeight() - dotRadius); | ||
+ | newX -= newX % moveStep; | ||
+ | newY -= newY % moveStep; | ||
+ | } while (isCloseToSnake(newX, newY)); | ||
+ | |||
+ | scoreX = newX; | ||
+ | scoreY = newY; | ||
+ | } | ||
+ | |||
+ | void resetGame() { | ||
+ | dotX = gameWidth / 2 - (gameWidth / 2) % moveStep; | ||
+ | dotY = u8g2.getHeight() / 2 - (u8g2.getHeight() / 2) % moveStep; | ||
+ | direction = 0; | ||
+ | score = 0; | ||
+ | snakeLength = 1; | ||
+ | generateScorePoint(); | ||
+ | } | ||
+ | |||
+ | void showGameOver() { | ||
+ | u8g2.clearBuffer(); | ||
+ | u8g2.setFont(u8g2_font_ncenB14_tr); | ||
+ | const char* gameOverText = "GAME OVER"; | ||
+ | int textWidth = u8g2.getStrWidth(gameOverText); | ||
+ | int textX = (u8g2.getWidth() - textWidth) / 2; | ||
+ | int textY = u8g2.getHeight() / 2; | ||
+ | u8g2.drawStr(textX, textY, gameOverText); | ||
+ | u8g2.sendBuffer(); | ||
+ | delay(3000); | ||
+ | | ||
+ | if (score > record) { | ||
+ | record = score; | ||
+ | u8g2.clearBuffer(); | ||
+ | u8g2.setFont(u8g2_font_ncenB14_tr); | ||
+ | const char* highScoreText = "RECORD"; | ||
+ | textWidth = u8g2.getStrWidth(highScoreText); | ||
+ | textX = (u8g2.getWidth() - textWidth) / 2; | ||
+ | int textY = u8g2.getHeight() / 2; | ||
+ | u8g2.drawStr(textX, textY, highScoreText); | ||
+ | u8g2.sendBuffer(); | ||
+ | delay(3000); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | int readADC(uint8_t channel) { | ||
+ | ADMUX = (0 << REFS1) | (1 << REFS0) | (channel & 0x07); | ||
+ | ADCSRA |= (1 << ADSC); | ||
+ | while (ADCSRA & (1 << ADSC)); | ||
+ | return ADCW; | ||
+ | } | ||
+ | |||
+ | void SPI_MasterInit(void) { | ||
+ | DDRB |= (1 << DDB3) | (1 << DDB5) | (1 << DDB2); | ||
+ | SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0); | ||
+ | } | ||
+ | |||
+ | void setup() { | ||
+ | DDRD &= ~((1 << PIND2) | (1 << PIND3) | (1 << PIND4)); | ||
+ | PORTD |= (1 << PIND2) | (1 << PIND3) | (1 << PIND4); | ||
+ | | ||
+ | DDRB &= ~(1 << PINB1); | ||
+ | PORTB |= (1 << PINB1); | ||
+ | | ||
+ | UBRR0H = (uint8_t)(51 & 0xF00); | ||
+ | UBRR0L = (uint8_t)(51 & 0x0FF); | ||
+ | UCSR0B = (1 << RXEN0) | (1 << TXEN0); | ||
+ | UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); | ||
+ | |||
+ | ADMUX = (0 << REFS1) | (1 << REFS0) | (0 << ADLAR); | ||
+ | ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); | ||
+ | |||
+ | randomSeed(readADC(1)); | ||
+ | |||
+ | EICRA |= (1 << ISC11) | (1 << ISC01); | ||
+ | EIMSK |= (1 << INT1) | (1 << INT0); | ||
+ | | ||
+ | PCICR |= (1 << PCIE2) | (1 << PCIE0); | ||
+ | PCMSK0 |= (1 << PCINT1); | ||
+ | PCMSK2 |= (1 << PCINT20); | ||
+ | | ||
+ | sei(); | ||
+ | Serial.begin(9600); | ||
+ | |||
+ | SPI_MasterInit(); | ||
+ | |||
+ | u8g2.begin(); | ||
+ | |||
+ | resetGame(); | ||
+ | } | ||
+ | |||
+ | ISR(INT0_vect) { | ||
+ | if (direction != LEFT) | ||
+ | direction = RIGHT; | ||
+ | delayMicroseconds(20000); | ||
+ | } | ||
+ | |||
+ | ISR(INT1_vect) { | ||
+ | if (direction != UP) | ||
+ | direction = DOWN; | ||
+ | delayMicroseconds(20000); | ||
+ | } | ||
+ | |||
+ | ISR(PCINT2_vect) { | ||
+ | if (!((PIND >> PIND4) & 1)) { | ||
+ | if (direction != DOWN) | ||
+ | direction = UP; | ||
+ | } | ||
+ | delayMicroseconds(20000); | ||
+ | } | ||
+ | |||
+ | ISR(PCINT0_vect) { | ||
+ | if (!((PINB >> PINB1) & 1)) { | ||
+ | if (direction != RIGHT) | ||
+ | direction = LEFT; | ||
+ | } | ||
+ | delayMicroseconds(20000); | ||
+ | } | ||
+ | |||
+ | void loop() { | ||
+ | u8g2.clearBuffer(); | ||
+ | | ||
+ | analogValue = readADC(0); | ||
+ | delay_ms = map(analogValue, 0, 1023, 0, 200); | ||
+ | | ||
+ | switch (direction) { | ||
+ | case LEFT: | ||
+ | dotX -= moveStep; | ||
+ | break; | ||
+ | case RIGHT: | ||
+ | dotX += moveStep; | ||
+ | break; | ||
+ | case UP: | ||
+ | dotY -= moveStep; | ||
+ | break; | ||
+ | case DOWN: | ||
+ | dotY += moveStep; | ||
+ | break; | ||
+ | } | ||
+ | |||
+ | if (dotX < 0 || dotX >= gameWidth || dotY < 0 || dotY >= u8g2.getHeight()) { | ||
+ | showGameOver(); | ||
+ | resetGame(); | ||
+ | } | ||
+ | |||
+ | newHeadX = dotX; | ||
+ | newHeadY = dotY; | ||
+ | | ||
+ | if (dotX == scoreX && dotY == scoreY) { | ||
+ | score++; | ||
+ | snakeLength++; | ||
+ | if (snakeLength > MAX_SNAKE_LENGTH) { | ||
+ | snakeLength = MAX_SNAKE_LENGTH; | ||
+ | } | ||
+ | generateScorePoint(); | ||
+ | } | ||
+ | |||
+ | for (int i = 1; i < snakeLength; i++) { | ||
+ | if (snakeX[i] == dotX && snakeY[i] == dotY) { | ||
+ | showGameOver(); | ||
+ | resetGame(); | ||
+ | } | ||
+ | } | ||
+ | | ||
+ | for (int i = snakeLength - 1; i > 0; i--) { | ||
+ | snakeX[i] = snakeX[i - 1]; | ||
+ | snakeY[i] = snakeY[i - 1]; | ||
+ | } | ||
+ | snakeX[0] = newHeadX; | ||
+ | snakeY[0] = newHeadY; | ||
+ | |||
+ | u8g2.setDrawColor(1); | ||
+ | for (int i = 0; i < snakeLength; i++) { | ||
+ | u8g2.drawBox(snakeX[i], snakeY[i], dotRadius, dotRadius); | ||
+ | } | ||
+ | u8g2.drawBox(scoreX, scoreY, dotRadius, dotRadius); | ||
+ | u8g2.drawVLine(gameWidth, 0, u8g2.getHeight()); | ||
+ | u8g2.setFont(u8g2_font_6x10_tf); | ||
+ | char scoreTextDisplay[10]; | ||
+ | sprintf(scoreTextDisplay, "Scor"); | ||
+ | u8g2.drawStr(gameWidth + (u8g2.getWidth() - gameWidth - u8g2.getStrWidth(scoreTextDisplay)) / 2, 10, scoreTextDisplay); | ||
+ | u8g2.setFont(u8g2_font_7x13_tf); | ||
+ | char scoreString[5]; | ||
+ | sprintf(scoreString, "%d", score); | ||
+ | u8g2.drawStr(gameWidth + (u8g2.getWidth() - gameWidth - u8g2.getStrWidth(scoreString)) / 2, 25, scoreString); | ||
+ | u8g2.drawHLine(gameWidth, u8g2.getHeight() / 2, u8g2.getWidth() - gameWidth); | ||
+ | u8g2.setFont(u8g2_font_6x10_tf); | ||
+ | char recordText[10]; | ||
+ | sprintf(recordText, "Best"); | ||
+ | u8g2.drawStr(gameWidth + (u8g2.getWidth() - gameWidth - u8g2.getStrWidth(recordText)) / 2, u8g2.getHeight() / 2 + 11, recordText); | ||
+ | u8g2.setFont(u8g2_font_7x13_tf); | ||
+ | char recordString[5]; | ||
+ | sprintf(recordString, "%d", record); | ||
+ | u8g2.drawStr(gameWidth + (u8g2.getWidth() - gameWidth - u8g2.getStrWidth(recordString)) / 2, u8g2.getHeight() / 2 + 26, recordString); | ||
+ | | ||
+ | u8g2.sendBuffer(); | ||
+ | Serial.println(delay_ms); | ||
+ | | ||
+ | delay(100 + delay_ms); | ||
+ | } | ||