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.

MILESTONE 2 - HARDWARE

Componente: LCD 12864 Albastru ST7920, Modul potentiometru rotativ RV09, 4 x Buton Mini 6x6x5 4 pini.

Am finalizat conexiunile componentelor, configurarea pinilor butoanelor, citirea valorilor potențiometrului și funcționarea display-ului LCD. Partea hardware este completă, urmând să mă concentrez pe implementarea software-ului.

Butoane:

Cele 4 butoane sunt conectate la pinii 2, 3, 4 și 9. Am ales acești pini deoarece:

  1. Butonul conectat la pinul 2 va genera o întrerupere externă INT0.
  2. Butonul conectat la pinul 3 va genera o întrerupere externă INT1.
  3. Butonul conectat la pinul 4 va genera o întrerupere la schimbarea stării pinului (PCINT2)
  4. Butonul conectat la pinul 9 va genera o întrerupere la schimbarea stării pinului (PCINT0)

În acest mod, avem rutine separate (sau mecanisme de identificare) pentru fiecare buton, și nu trebuie să verificăm ce buton a fost apăsat în mod constant în bucla principală a programului. Un pin al fiecărui buton este conectat la ground, iar celălalt este conectat la pinul de intrare respectiv al plăcii. De asemenea, butoanele folosesc rezistențele de pull-up interne ale Arduino.

Potentiometru:

Potențiometrul este conectat astfel: GND la masă, Vcc la 5V, iar ieșirea (OUT) la pinul analogic A0 al Arduino. Valoarea citită de pe potențiometru va fi utilizată pentru a controla viteza șarpelui

LCD-ul:

Pinii LCD-ului ST7920 128×64 sunt conectati de placa Arduino in felul urmator:

  • 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)

LCD-ul ST7920 comunică cu placa de dezvoltare Arduino prin protocolul SPI.

Bill of Materials:

Diagrama Bloc a proiectului:

Circuit:

Schema electrică:

Poză:

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

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);
  }
pm/prj2025/fstancu/david_ionut.luca.txt · Last modified: 2025/05/28 09:53 by david_ionut.luca
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0