This shows you the differences between two versions of the page.
|
pm:prj2025:fstancu:sebastian.badea0506 [2025/05/20 20:22] florin.stancu |
pm:prj2025:fstancu:sebastian.badea0506 [2025/05/25 23:26] (current) sebastian.badea0506 |
||
|---|---|---|---|
| Line 14: | Line 14: | ||
| ===== Hardware Design ===== | ===== Hardware Design ===== | ||
| Lista de componente: | Lista de componente: | ||
| - | * LCD Nokia 5110 | + | * OLED SSD1306 |
| - | * 4 x push button | + | * 5 x push button |
| * Arduino UNO | * Arduino UNO | ||
| * 3 x LED | * 3 x LED | ||
| * rezistente | * rezistente | ||
| * fire | * fire | ||
| + | * buzzer | ||
| Cum am legat pinii: | Cum am legat pinii: | ||
| - | LCD Nokia 5110: | + | OLED SSD1306: |
| - | * RST la pinul digital 8 | ||
| - | * CE (CS) la pinul digital 7 | ||
| - | * DC la pinul digital 6 | ||
| - | * DIN la pinul digital 5 | ||
| - | * CLK la pinul digital 4 | ||
| * VCC la 3.3V | * VCC la 3.3V | ||
| - | * Backlight (BL) la GND | ||
| * GND la GND | * GND la GND | ||
| + | * SDA la pinul digital A4 | ||
| + | * SCL la pinul digital A5 | ||
| LED-urile: | LED-urile: | ||
| - | * LED1 anod la digital 2, catod la GND | + | * LED1 anod la digital 3, catod la GND |
| - | * LED2 anod la digital 3, catod la GND | + | * LED2 anod la digital 5, catod la GND |
| - | * LED3 anod la digital 12, catod la GND | + | * LED3 anod la digital 6, catod la GND |
| Butoanele: | Butoanele: | ||
| - | * Buton1 un pin la analog A0, celalalt pin la GND | + | * Buton1 un pin la digital 9, celalalt pin la GND |
| - | * Buton2 un pin la analog A1, celalalt pin la GND | + | * Buton2 un pin la digital 10, celalalt pin la GND |
| - | * Buton3 un pin la analog A2, celalalt pin la GND | + | * Buton3 un pin la digital 11, celalalt pin la GND |
| - | * Buton4 un pin la analog A3, celalalt pin la GND | + | * Buton4 un pin la digital 12, celalalt pin la GND |
| + | * Buton5 un pin la digital 8, celalalt pin la GND | ||
| + | |||
| + | Buzzer: | ||
| + | |||
| + | * Buzzer plus la digital 7, minus la GND | ||
| {{:pm:prj2025:fstancu:pm_arduino_pacman.png?300|}} | {{:pm:prj2025:fstancu:pm_arduino_pacman.png?300|}} | ||
| ===== Software Design ===== | ===== Software Design ===== | ||
| - | Descrierea codului aplicaţiei (firmware): | + | <code> |
| - | * mediu de dezvoltare (if any) (e.g. AVR Studio, CodeVisionAVR) | + | #include <Wire.h> |
| - | * librării şi surse 3rd-party (e.g. Procyon AVRlib) | + | #include <Adafruit_GFX.h> |
| - | * algoritmi şi structuri pe care plănuiţi să le implementaţi | + | #include <Adafruit_SSD1306.h> |
| - | * (etapa 3) surse şi funcţii implementate | + | |
| + | #define SCREEN_WIDTH 128 | ||
| + | #define SCREEN_HEIGHT 64 | ||
| + | |||
| + | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); | ||
| + | |||
| + | #define UP_BUTTON 9 | ||
| + | #define DOWN_BUTTON 10 | ||
| + | #define LEFT_BUTTON 11 | ||
| + | #define RIGHT_BUTTON 12 | ||
| + | #define PAUSE_BUTTON 8 | ||
| + | |||
| + | #define BUZZER_PIN 7 | ||
| + | |||
| + | #define LIFE_LED_1 3 | ||
| + | #define LIFE_LED_2 5 | ||
| + | #define LIFE_LED_3 6 | ||
| + | |||
| + | int pacmanX = 10; | ||
| + | int pacmanY = 10; | ||
| + | const int pacmanRadius = 3; | ||
| + | |||
| + | int lives = 3; | ||
| + | int score = 0; | ||
| + | |||
| + | struct Wall { | ||
| + | int x, y, w, h; | ||
| + | }; | ||
| + | |||
| + | #define NUM_WALLS 12 | ||
| + | Wall walls[NUM_WALLS] = { | ||
| + | {0, 0, 128, 5}, | ||
| + | {0, 59, 128, 5}, | ||
| + | {0, 0, 5, 64}, | ||
| + | {123, 0, 5, 64}, | ||
| + | {20, 15, 5, 35}, | ||
| + | {40, 5, 5, 15}, | ||
| + | {40, 45, 5, 20}, | ||
| + | {60, 15, 5, 35}, | ||
| + | {80, 5, 5, 15}, | ||
| + | {80, 45, 5, 20}, | ||
| + | {100, 15, 5, 35}, | ||
| + | {20, 30, 85, 5} | ||
| + | }; | ||
| + | |||
| + | #define DOT_SPACING 10 | ||
| + | bool dots[SCREEN_WIDTH / DOT_SPACING][SCREEN_HEIGHT / DOT_SPACING]; | ||
| + | |||
| + | struct Ghost { | ||
| + | int x, y; | ||
| + | int dx, dy; | ||
| + | int stepCount; | ||
| + | int radius; | ||
| + | }; | ||
| + | |||
| + | #define NUM_GHOSTS 3 | ||
| + | Ghost ghosts[NUM_GHOSTS]; | ||
| + | |||
| + | bool isValidPosition(int x, int y, int radius) { | ||
| + | for (int i = 0; i < NUM_WALLS; i++) { | ||
| + | if (x + radius > walls[i].x && x - radius < walls[i].x + walls[i].w && | ||
| + | y + radius > walls[i].y && y - radius < walls[i].y + walls[i].h) { | ||
| + | return false; | ||
| + | } | ||
| + | } | ||
| + | return true; | ||
| + | } | ||
| + | |||
| + | bool checkDotCollision(int x, int y) { | ||
| + | int cx = x / DOT_SPACING; | ||
| + | int cy = y / DOT_SPACING; | ||
| + | if (cx >= 0 && cx < SCREEN_WIDTH / DOT_SPACING && cy >= 0 && cy < SCREEN_HEIGHT / DOT_SPACING) { | ||
| + | if (dots[cx][cy]) { | ||
| + | dots[cx][cy] = false; | ||
| + | score += 10; | ||
| + | tone(BUZZER_PIN, 1000, 100); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void updateLEDs() { | ||
| + | if (lives >= 1) analogWrite(LIFE_LED_1, 255); else analogWrite(LIFE_LED_1, 0); | ||
| + | if (lives >= 2) analogWrite(LIFE_LED_2, 180); else analogWrite(LIFE_LED_2, 0); | ||
| + | if (lives >= 3) analogWrite(LIFE_LED_3, 100); else analogWrite(LIFE_LED_3, 0); | ||
| + | } | ||
| + | |||
| + | void fadeOutLed(int pin) { | ||
| + | for (int brightness = 255; brightness >= 0; brightness -= 1) { | ||
| + | analogWrite(pin, brightness); | ||
| + | delay(2); | ||
| + | } | ||
| + | analogWrite(pin, 0); | ||
| + | } | ||
| + | |||
| + | void loseLife() { | ||
| + | if (lives > 0) { | ||
| + | if (lives == 3) fadeOutLed(LIFE_LED_3); | ||
| + | else if (lives == 2) fadeOutLed(LIFE_LED_2); | ||
| + | else if (lives == 1) fadeOutLed(LIFE_LED_1); | ||
| + | |||
| + | lives--; | ||
| + | updateLEDs(); | ||
| + | tone(BUZZER_PIN, 500, 300); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void respawnGhost(Ghost &g) { | ||
| + | int x, y; | ||
| + | do { | ||
| + | x = random(10, SCREEN_WIDTH - 10); | ||
| + | y = random(10, SCREEN_HEIGHT - 10); | ||
| + | } while (!isValidPosition(x, y, g.radius)); | ||
| + | g.x = x; | ||
| + | g.y = y; | ||
| + | g.stepCount = 0; | ||
| + | g.dx = 0; | ||
| + | g.dy = 0; | ||
| + | } | ||
| + | |||
| + | void moveGhost(Ghost &g) { | ||
| + | if (g.stepCount <= 0) { | ||
| + | int possibleDirs[3] = {-1, 0, 1}; | ||
| + | g.dx = possibleDirs[random(3)]; | ||
| + | g.dy = possibleDirs[random(3)]; | ||
| + | g.stepCount = random(10, 30); | ||
| + | } | ||
| + | |||
| + | if (random(100) < 20) { | ||
| + | if (pacmanX > g.x) g.dx = 1; | ||
| + | else if (pacmanX < g.x) g.dx = -1; | ||
| + | else g.dx = 0; | ||
| + | |||
| + | if (pacmanY > g.y) g.dy = 1; | ||
| + | else if (pacmanY < g.y) g.dy = -1; | ||
| + | else g.dy = 0; | ||
| + | } | ||
| + | |||
| + | int newX = g.x + g.dx; | ||
| + | int newY = g.y + g.dy; | ||
| + | |||
| + | if (isValidPosition(newX, newY, g.radius) && | ||
| + | newX - g.radius >= 0 && newX + g.radius <= SCREEN_WIDTH && | ||
| + | newY - g.radius >= 0 && newY + g.radius <= SCREEN_HEIGHT) { | ||
| + | g.x = newX; | ||
| + | g.y = newY; | ||
| + | } else { | ||
| + | g.stepCount = 0; | ||
| + | } | ||
| + | |||
| + | g.stepCount--; | ||
| + | } | ||
| + | |||
| + | bool checkGhostCollision(Ghost &g) { | ||
| + | int dx = pacmanX - g.x; | ||
| + | int dy = pacmanY - g.y; | ||
| + | int distSq = dx * dx + dy * dy; | ||
| + | int radiiSum = pacmanRadius + g.radius; | ||
| + | return distSq <= radiiSum * radiiSum; | ||
| + | } | ||
| + | |||
| + | void drawGhost(int x, int y, int radius) { | ||
| + | display.fillCircle(x, y - radius / 2, radius / 1.5, SSD1306_WHITE); | ||
| + | display.fillRect(x - radius, y - radius / 2, radius * 2, radius, SSD1306_WHITE); | ||
| + | for (int i = -radius; i < radius; i += radius / 3) { | ||
| + | display.fillTriangle(x + i, y + radius, x + i + radius / 3 / 2, y + radius - 3, x + i + radius / 3, y + radius, SSD1306_WHITE); | ||
| + | } | ||
| + | display.fillRect(x - radius / 2, y - radius, radius / 3, radius / 3, SSD1306_BLACK); | ||
| + | display.fillRect(x + radius / 6, y - radius, radius / 3, radius / 3, SSD1306_BLACK); | ||
| + | } | ||
| + | |||
| + | void setup() { | ||
| + | Serial.begin(9600); | ||
| + | randomSeed(analogRead(A0)); | ||
| + | |||
| + | if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { | ||
| + | Serial.println(F("SSD1306 allocation failed")); | ||
| + | for (;;); | ||
| + | } | ||
| + | display.clearDisplay(); | ||
| + | display.display(); | ||
| + | |||
| + | pinMode(UP_BUTTON, INPUT_PULLUP); | ||
| + | pinMode(DOWN_BUTTON, INPUT_PULLUP); | ||
| + | pinMode(LEFT_BUTTON, INPUT_PULLUP); | ||
| + | pinMode(RIGHT_BUTTON, INPUT_PULLUP); | ||
| + | pinMode(PAUSE_BUTTON, INPUT_PULLUP); | ||
| + | pinMode(BUZZER_PIN, OUTPUT); | ||
| + | |||
| + | pinMode(LIFE_LED_1, OUTPUT); | ||
| + | pinMode(LIFE_LED_2, OUTPUT); | ||
| + | pinMode(LIFE_LED_3, OUTPUT); | ||
| + | |||
| + | updateLEDs(); | ||
| + | |||
| + | for (int i = 0; i < NUM_GHOSTS; i++) { | ||
| + | ghosts[i].radius = 3; | ||
| + | respawnGhost(ghosts[i]); | ||
| + | } | ||
| + | |||
| + | for (int x = 0; x < SCREEN_WIDTH / DOT_SPACING; x++) { | ||
| + | for (int y = 0; y < SCREEN_HEIGHT / DOT_SPACING; y++) { | ||
| + | if (isValidPosition(x * DOT_SPACING, y * DOT_SPACING, pacmanRadius)) { | ||
| + | dots[x][y] = true; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void loop() { | ||
| + | if (digitalRead(PAUSE_BUTTON) == LOW) { | ||
| + | display.clearDisplay(); | ||
| + | display.setTextSize(1); | ||
| + | display.setTextColor(SSD1306_WHITE); | ||
| + | display.setCursor(20, 30); | ||
| + | display.print(F("SCOR: ")); | ||
| + | display.print(score); | ||
| + | display.display(); | ||
| + | delay(500); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | if (lives <= 0) { | ||
| + | display.clearDisplay(); | ||
| + | display.setTextSize(2); | ||
| + | display.setTextColor(SSD1306_WHITE); | ||
| + | display.setCursor(20, 25); | ||
| + | display.println(F("GAME OVER")); | ||
| + | display.display(); | ||
| + | delay(2000); | ||
| + | |||
| + | lives = 3; | ||
| + | pacmanX = 10; | ||
| + | pacmanY = 10; | ||
| + | updateLEDs(); | ||
| + | score = 0; | ||
| + | |||
| + | for (int i = 0; i < NUM_GHOSTS; i++) { | ||
| + | respawnGhost(ghosts[i]); | ||
| + | } | ||
| + | |||
| + | for (int x = 0; x < SCREEN_WIDTH / DOT_SPACING; x++) { | ||
| + | for (int y = 0; y < SCREEN_HEIGHT / DOT_SPACING; y++) { | ||
| + | if (isValidPosition(x * DOT_SPACING, y * DOT_SPACING, pacmanRadius)) { | ||
| + | dots[x][y] = true; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | int newX = pacmanX; | ||
| + | int newY = pacmanY; | ||
| + | |||
| + | if (digitalRead(UP_BUTTON) == LOW) newY -= 2; | ||
| + | if (digitalRead(DOWN_BUTTON) == LOW) newY += 2; | ||
| + | if (digitalRead(LEFT_BUTTON) == LOW) newX -= 2; | ||
| + | if (digitalRead(RIGHT_BUTTON) == LOW) newX += 2; | ||
| + | |||
| + | if (newX - pacmanRadius < 0) newX = pacmanRadius; | ||
| + | if (newX + pacmanRadius > SCREEN_WIDTH) newX = SCREEN_WIDTH - pacmanRadius; | ||
| + | if (newY - pacmanRadius < 0) newY = pacmanRadius; | ||
| + | if (newY + pacmanRadius > SCREEN_HEIGHT) newY = SCREEN_HEIGHT - pacmanRadius; | ||
| + | |||
| + | if (isValidPosition(newX, newY, pacmanRadius)) { | ||
| + | pacmanX = newX; | ||
| + | pacmanY = newY; | ||
| + | checkDotCollision(pacmanX, pacmanY); | ||
| + | } | ||
| + | |||
| + | for (int i = 0; i < NUM_GHOSTS; i++) { | ||
| + | moveGhost(ghosts[i]); | ||
| + | if (checkGhostCollision(ghosts[i])) { | ||
| + | loseLife(); | ||
| + | respawnGhost(ghosts[i]); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | display.clearDisplay(); | ||
| + | |||
| + | for (int i = 0; i < NUM_WALLS; i++) { | ||
| + | display.fillRect(walls[i].x, walls[i].y, walls[i].w, walls[i].h, SSD1306_WHITE); | ||
| + | } | ||
| + | |||
| + | for (int x = 0; x < SCREEN_WIDTH / DOT_SPACING; x++) { | ||
| + | for (int y = 0; y < SCREEN_HEIGHT / DOT_SPACING; y++) { | ||
| + | if (dots[x][y]) { | ||
| + | display.drawPixel(x * DOT_SPACING, y * DOT_SPACING, SSD1306_WHITE); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | display.fillCircle(pacmanX, pacmanY, pacmanRadius, SSD1306_WHITE); | ||
| + | |||
| + | for (int i = 0; i < NUM_GHOSTS; i++) { | ||
| + | drawGhost(ghosts[i].x, ghosts[i].y, ghosts[i].radius); | ||
| + | } | ||
| + | |||
| + | display.setTextSize(1); | ||
| + | display.setCursor(100, 0); | ||
| + | display.print(score); | ||
| + | |||
| + | display.display(); | ||
| + | delay(50); | ||
| + | } | ||
| + | |||
| + | </code> | ||
| + | |||
| + | ==== Librarii si surse 3rd-party utilizate ==== | ||
| + | * **Adafruit_GFX** - pentru desenarea formelor grafice. | ||
| + | * **Adafruit_SSD1306** - pentru controlul afisajului OLED SSD1306. | ||
| + | |||
| + | ==== Concepte din laboratoare utilizate in proiect ==== | ||
| + | * **I2C** - utilizat pentru comunicarea cu display-ul OLED SSD1306 | ||
| + | * **PWM** - folosit pentru controlul intensitatii LED-urilor care indica vietile jucatorului | ||
| + | * **GPIO** - utilizat pentru gestionarea butoanelor de control (sus, jos, stanga, dreapta, pauza) | ||
| ===== Rezultate Obţinute ===== | ===== Rezultate Obţinute ===== | ||