This shows you the differences between two versions of the page.
pm:prj2025:fstancu:david_ionut.luca [2025/05/18 15:15] david_ionut.luca |
pm:prj2025:fstancu:david_ionut.luca [2025/05/28 09:53] (current) david_ionut.luca |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | **Snake Game | + | ====== Snake Game - Luca David-Ionut 334CD ====== |
- | 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 27: | 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) | ||
+ | * 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) | ||
+ | * 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ă) | ||
+ | * 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) | ||
+ | * BLK -> GND (Acesta este pinul negativ de alimentare pentru iluminarea de fundal a LCD-ului) | ||
- | * Vcc -> 5V (Pinul de alimentare al LCD-ului este conectat la 5V de pe Arduino.) | + | LCD-ul ST7920 comunică cu placa de dezvoltare Arduino prin protocolul SPI. |
- | + | ||
- | * 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.) | + | |
- | + | ||
- | * 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.) | + | |
- | + | ||
- | * 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.) | + | |
- | + | ||
- | * 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.) | + | |
Line 68: | Line 62: | ||
{{:pm:prj2025:fstancu:cablaraie2_luca_david.jpg?600|}} | {{:pm:prj2025:fstancu:cablaraie2_luca_david.jpg?600|}} | ||
+ | |||
+ | |||
+ | **__MILESTONE 3 - SOFTWARE__** | ||
+ | |||
+ | **Stadiul proiectului** | ||
+ | |||
+ | 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** | ||
+ | |||
+ | 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** | ||
+ | |||
+ | 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** | ||
+ | |||
+ | 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** | ||
+ | |||
+ | Î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ă. | ||
+ | |||
+ | **Un scurt demo al jocului** | ||
+ | |||
+ | <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); | ||
+ | } | ||
+ | |||