This shows you the differences between two versions of the page.
pm:prj2025:atoader:catalin.giuglan [2025/05/28 03:21] catalin.giuglan [Software Design] |
pm:prj2025:atoader:catalin.giuglan [2025/05/28 16:00] (current) catalin.giuglan [Implementarea Hardware:] |
||
---|---|---|---|
Line 52: | Line 52: | ||
</note> | </note> | ||
- | ====== Implementarea Hardware: ====== | + | {{:pm:prj2025:atoader:catalin_pong.jpg?650|}}====== Implementarea Hardware: ====== |
- | {{:pm:prj2025:atoader:catalin_pong.jpg?650|}} | ||
===== Software Design ===== | ===== Software Design ===== | ||
Line 71: | Line 71: | ||
**Funcții:** | **Funcții:** | ||
- | + | *void setup() - Inițializează componentele hardware, verifică fișierul muzical de pe SD și calibrează joystick-urile. | |
- | <note> | + | *void loop() - Gestionează logica jocului, redă muzica și actualizează ecranul și pozițiile elementelor în mod continuu. |
- | + | *void drawFrame() - Desenează întreaga scenă de joc: terenul, scorul, mingea și playerii. | |
- | void setup() { | + | *void resetGame() - Plasează mingea într-o poziție aleatorie și îi setează o direcție nouă de mișcare. |
- | Serial.begin(9600); | + | *void drawCourt() - Desenează cadrul terenului de joc pe afișaj (un dreptunghi de 128x64 pixeli). |
- | pinMode(BEEPER, OUTPUT); | + | *void drawScore() - Afișează scorurile celor doi jucători pe ecran. |
- | tone(BEEPER, 440, 200); delay(250); | + | *void gameOver() - Afișează ecranul de final și anunță jucătorul câștigător, apoi resetează scorurile și mingea. |
- | + | *void updateBallPosition() - Calculează noua poziție a mingii și gestionează coliziunile cu pereții, playerii și dacă s-a marcat sau nu un punct. | |
- | if (!sd.begin(chipSelect, SD_SCK_MHZ(10))) { | + | *void updatePaddlePositions() - Citește pozițiile joystick-urilor și actualizează paletele în funcție de mișcare. |
- | Serial.println(F("SD init failed!")); | + | *void constrainPaddlePosition(uint8_t &paddle_y) - Asigură că playerul nu iese în afara ecranului. |
- | } else { | + | *void soundBounce() - Redă un sunet scurt când mingea lovește un player sau marginea orizontală. |
- | Serial.println(F("SD init OK.")); | + | *void soundPoint() - Redă un sunet diferit când se marchează un punct. |
- | } | + | *void initMusic() - Deschide fișierul mario.txt de pe cardul SD dacă nu a fost deja deschis. |
- | + | *void playMarioTheme() - Redă notele muzicale din fișierul mario.txt, una câte una. | |
- | wholenote = (60000L * 4) / marioTempo; | + | |
- | + | ||
- | display.begin(); | + | |
- | display.setFont(u8g2_font_6x10_tf); | + | |
- | display.firstPage(); | + | |
- | do { | + | |
- | display.drawStr(0, 10, "Initializare..."); | + | |
- | display.drawStr(0, 20, sd.begin(chipSelect, SD_SCK_MHZ(10)) ? "SD OK" : "Eroare SD"); | + | |
- | + | ||
- | char buf[12]; | + | |
- | strcpy_P(buf, FILENAME); | + | |
- | if (musicFile.open(buf, O_RDONLY)) { | + | |
- | display.drawStr(0, 30, "mario.txt OK"); | + | |
- | musicFile.close(); | + | |
- | } else { | + | |
- | display.drawStr(0, 30, "Fisier lipsa"); | + | |
- | } | + | |
- | } while (display.nextPage()); | + | |
- | + | ||
- | delay(3000); | + | |
- | + | ||
- | pinMode(SW_pin, INPUT); | + | |
- | pinMode(RESET_BUTTON, INPUT_PULLUP); | + | |
- | digitalWrite(SW_pin, HIGH); | + | |
- | + | ||
- | // Calibrare joystick-uri | + | |
- | player1_center = analogRead(player1); | + | |
- | player2_center = analogRead(player2); | + | |
- | + | ||
- | ball_update = millis(); | + | |
- | paddle_update = ball_update; | + | |
- | } | + | |
- | </note> | + | |
- | + | ||
- | <note> | + | |
- | + | ||
- | void loop() { | + | |
- | unsigned long now = millis(); | + | |
- | bool update = false; | + | |
- | + | ||
- | if (resetBall) { | + | |
- | (player1Score == maxScore || player2Score == maxScore) ? gameOver() : resetGame(); | + | |
- | } | + | |
- | + | ||
- | if (now > ball_update) { | + | |
- | updateBallPosition(); | + | |
- | update = true; | + | |
- | } | + | |
- | + | ||
- | if (now > paddle_update) { | + | |
- | updatePaddlePositions(); | + | |
- | update = true; | + | |
- | } | + | |
- | + | ||
- | if (update) { | + | |
- | drawFrame(); | + | |
- | } | + | |
- | + | ||
- | playMarioTheme(); | + | |
- | } | + | |
- | </note> | + | |
- | <note> | + | |
- | void drawFrame() { | + | |
- | display.firstPage(); | + | |
- | do { | + | |
- | drawCourt(); | + | |
- | drawScore(); | + | |
- | display.drawPixel(ball_x, ball_y); | + | |
- | display.drawVLine(PLAYER2_X, player2_y, PADDLE_HEIGHT); | + | |
- | display.drawVLine(PLAYER_X, player1_y, PADDLE_HEIGHT); | + | |
- | } while (display.nextPage()); | + | |
- | } | + | |
- | </note> | + | |
- | <note> | + | |
- | void resetGame() { | + | |
- | ball_x = random(45, 50); | + | |
- | ball_y = random(23, 33); | + | |
- | do { ball_dir_x = random(-1, 2); } while (ball_dir_x == 0); | + | |
- | do { ball_dir_y = random(-1, 2); } while (ball_dir_y == 0); | + | |
- | resetBall = false; | + | |
- | } | + | |
- | </note> | + | |
- | <note> | + | |
- | void drawCourt() { | + | |
- | display.drawFrame(0, 0, 128, 64); | + | |
- | } | + | |
- | + | ||
- | void drawScore() { | + | |
- | char buf[4]; | + | |
- | sprintf(buf, "%d", player2Score); | + | |
- | display.drawStr(45, 10, buf); | + | |
- | sprintf(buf, "%d", player1Score); | + | |
- | display.drawStr(75, 10, buf); | + | |
- | } | + | |
- | </note> | + | |
- | <note> | + | |
- | void gameOver() { | + | |
- | display.firstPage(); | + | |
- | do { | + | |
- | display.drawStr(20, 25, (player1Score > player2Score) ? "Player 1" : "Player 2"); | + | |
- | display.drawStr(40, 40, "won"); | + | |
- | } while (display.nextPage()); | + | |
- | delay(2000); | + | |
- | player1Score = player2Score = 0; | + | |
- | resetBall = true; | + | |
- | ball_update = paddle_update = millis(); | + | |
- | } | + | |
- | </note> | + | |
- | <note> | + | |
- | void updateBallPosition() { | + | |
- | uint8_t new_x = ball_x + ball_dir_x; | + | |
- | uint8_t new_y = ball_y + ball_dir_y; | + | |
- | + | ||
- | if (new_x == 0 || new_x == 127) handleVerticalWallCollision(new_x); | + | |
- | if (new_y == 0 || new_y == 63) { | + | |
- | soundBounce(); ball_dir_y = -ball_dir_y; | + | |
- | new_y += ball_dir_y * 2; | + | |
- | } | + | |
- | + | ||
- | if ((new_x == PLAYER2_X && new_y >= player2_y && new_y <= player2_y + PADDLE_HEIGHT) || | + | |
- | (new_x == PLAYER_X && new_y >= player1_y && new_y <= player1_y + PADDLE_HEIGHT)) { | + | |
- | soundBounce(); ball_dir_x = -ball_dir_x; | + | |
- | new_x += ball_dir_x * 2; | + | |
- | } | + | |
- | + | ||
- | ball_x = new_x; | + | |
- | ball_y = new_y; | + | |
- | ball_update += BALL_RATE; | + | |
- | } | + | |
- | </note> | + | |
- | <note> | + | |
- | void updatePaddlePositions() { | + | |
- | paddle_update += PADDLE_RATE; | + | |
- | + | ||
- | int val2 = analogRead(player2); | + | |
- | if (val2 < player2_center - DEADZONE) player2_y--; | + | |
- | if (val2 > player2_center + DEADZONE) player2_y++; | + | |
- | constrainPaddlePosition(player2_y); | + | |
- | + | ||
- | int val1 = analogRead(player1); | + | |
- | if (val1 < player1_center - DEADZONE) player1_y--; | + | |
- | if (val1 > player1_center + DEADZONE) player1_y++; | + | |
- | constrainPaddlePosition(player1_y); | + | |
- | } | + | |
- | </note> | + | |
- | <note> | + | |
- | void constrainPaddlePosition(uint8_t &paddle_y) { | + | |
- | if (paddle_y < 1) paddle_y = 1; | + | |
- | if (paddle_y + PADDLE_HEIGHT > 63) paddle_y = 63 - PADDLE_HEIGHT; | + | |
- | } | + | |
- | + | ||
- | void handleVerticalWallCollision(uint8_t new_x) { | + | |
- | if (new_x == 0) { | + | |
- | player1Score++; soundPoint(); resetBall = true; | + | |
- | } else { | + | |
- | player2Score++; soundPoint(); resetBall = true; | + | |
- | } | + | |
- | ball_dir_x = -ball_dir_x; | + | |
- | } | + | |
- | </note> | + | |
- | <note> | + | |
- | void soundBounce() { tone(BEEPER, 500, 50); } | + | |
- | void soundPoint() { tone(BEEPER, 100, 50); } | + | |
- | + | ||
- | void initMusic() { | + | |
- | if (musicInitialized) return; | + | |
- | char buf[12]; | + | |
- | strcpy_P(buf, FILENAME); | + | |
- | if (musicFile.open(buf, O_RDONLY)) { | + | |
- | Serial.println(F("Fisier mario.txt deschis.")); | + | |
- | } else { | + | |
- | Serial.println(F("Eroare deschidere mario.txt")); | + | |
- | } | + | |
- | musicInitialized = true; | + | |
- | } | + | |
- | </note> | + | |
- | <note> | + | |
- | void playMarioTheme() { | + | |
- | if (!musicInitialized) { | + | |
- | initMusic(); | + | |
- | return; | + | |
- | } | + | |
- | if (!musicFile.isOpen()) return; | + | |
- | + | ||
- | unsigned long now = millis(); | + | |
- | + | ||
- | if (!isNotePlaying && now - noteStartTime >= pauseDuration) { | + | |
- | if (!musicFile.available()) musicFile.rewind(); | + | |
- | + | ||
- | char line[20]; | + | |
- | uint8_t idx = 0; | + | |
- | char ch; | + | |
- | while (musicFile.available() && idx < sizeof(line) - 1) { | + | |
- | ch = musicFile.read(); | + | |
- | if (ch == '\n') break; | + | |
- | line[idx++] = ch; | + | |
- | } | + | |
- | line[idx] = '\0'; | + | |
- | + | ||
- | char* commaPtr = strchr(line, ','); | + | |
- | if (commaPtr) { | + | |
- | *commaPtr = '\0'; | + | |
- | int freq = atoi(line); | + | |
- | int dur = atoi(commaPtr + 1); | + | |
- | noteDuration = (dur > 0) ? (wholenote / dur) : ((wholenote / abs(dur)) * 1.5); | + | |
- | if (freq != REST) tone(BEEPER, freq); | + | |
- | isNotePlaying = true; | + | |
- | noteStartTime = now; | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | if (isNotePlaying && now - noteStartTime >= noteDuration * 0.9) { | + | |
- | noTone(BEEPER); | + | |
- | isNotePlaying = false; | + | |
- | pauseDuration = noteDuration * 0.1; | + | |
- | noteStartTime = now; | + | |
- | } | + | |
- | } | + | |
- | </note> | + | |
</note> | </note> | ||
Line 328: | Line 109: | ||
<note warning> | <note warning> | ||
- | O arhivă (sau mai multe dacă este cazul) cu fişierele obţinute în urma realizării proiectului: surse, scheme, etc. Un fişier README, un ChangeLog, un script de compilare şi copiere automată pe uC crează întotdeauna o impresie bună ;-). | + | Arhiva conține codul Arduino și videoclipul demo de prezentare al proiectului: {{:pm:prj2025:atoader:pong.zip|}} |
- | + | ||
- | Fişierele se încarcă pe wiki folosind facilitatea **Add Images or other files**. Namespace-ul în care se încarcă fişierele este de tipul **:pm:prj20??:c?** sau **:pm:prj20??:c?:nume_student** (dacă este cazul). **Exemplu:** Dumitru Alin, 331CC -> **:pm:prj2009:cc:dumitru_alin**. | + | |
</note> | </note> | ||
- | |||
===== Jurnal ===== | ===== Jurnal ===== | ||