This shows you the differences between two versions of the page.
pm:prj2025:fstancu:david_ionut.luca [2025/05/28 08:49] 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 29: | ||
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 (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) | * 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) | * 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) | |
- | * 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) | |
- | * PSB -> GND (Setează display-ul în modul de comunicare serial (SPI), fiind conectat la masă) | + | * 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) | |
- | * 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. | LCD-ul ST7920 comunică cu placa de dezvoltare Arduino prin protocolul SPI. | ||
Line 94: | Line 89: | ||
<HTML><iframe width="560" height="315" src="//www.youtube.com/embed/eJmF104xEIE" 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** | **Calibrarea elementelor de senzoristica** | ||
Line 99: | Line 97: | ||
Am utilizat funcția map() pentru a converti valorile citite de la ADC într-un interval de milisecunde ajustabil. | 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); | ||
+ | } | ||