#include <LedControl.h>
#include <avr/pgmspace.h>
// ---------------- COSTANTI ----------------
const int SCREEN_WIDTH = 16;
const int SCREEN_HEIGHT = 8;
const int PADDLE_SIZE = 3;
const int MAX_SCORE = 5;
const int PLAYER_X = 0;
const int AI_X = 15;
// Pins: DIN, CLK, CS, Numero di dispositivi
LedControl lc = LedControl(12, 11, 10, 2);
// ---------------- STATO DI GIOCO ----------------
int ballX = 7, ballY = 3;
int dirX = 1, dirY = 1;
int playerPaddleY = 3;
int aiPaddleY = 3;
int aiTargetY = 3; // Aggiunto per prevenire le vibrazioni
int playerScore = 0;
int aiScore = 0;
// Tempistica
unsigned long lastBallMove = 0;
unsigned long lastPaddleMove = 0;
unsigned long lastFrame = 0;
const int ORIGINAL_BALL_INTERVAL = 180;
int ballInterval = ORIGINAL_BALL_INTERVAL;
int paddleInterval = 60;
int aiDifficulty = 2; // 1: Easy, 2: Medium, 3: Hard
bool serving = true;
// ---------------- GRAFICA (PROGMEM(modificatore di variabile)) ----------------
// Salvataggio del titolo nella memoria Flash per risparmiare RAM
const byte title_bmp[8][16] PROGMEM = {
{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0},
{1,1,1,0, 0,1,1,0, 1,0,0,1, 0,1,1,1},
{1,0,1,0, 1,0,0,1, 1,1,0,1, 1,0,0,0},
{1,1,1,0, 1,0,0,1, 1,1,1,1, 1,0,0,0},
{1,0,0,0, 1,0,0,1, 1,0,1,1, 1,0,1,1},
{1,0,0,0, 1,0,0,1, 1,0,0,1, 1,0,0,1},
{1,0,0,0, 0,1,1,0, 1,0,0,1, 0,1,1,1},
{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0}
};
// ---------------- SETUP ----------------
void setup() {
for (int i = 0; i < 2; i++) {
lc.shutdown(i, false);
lc.setIntensity(i, 8); // Luminosità sicura
lc.clearDisplay(i);
}
randomSeed(analogRead(A5));
showTitle();
resetGame();
}
// ---------------- MAIN LOOP ----------------
void loop() {
unsigned long now = millis();
readPlayer();
// logica di movimento della sfera
if (!serving && (now - lastBallMove > (unsigned long)ballInterval)) {
moveBall();
checkCollisions();
lastBallMove = now;
}
// logica di movimento dell'IA
if (now - lastPaddleMove > (unsigned long)paddleInterval) {
updateAITarget(); // Decidi dove andare
moveAI(); // Muoviti davvero
lastPaddleMove = now;
}
// Rendering (circa 30 FPS)
if (now - lastFrame > 33) {
drawGame();
lastFrame = now;
}
}
// ---------------- FUNZIONI LOGICHE ----------------
void readPlayer() {
// Mappatura dell'input analogico ai limiti della pagaia
int val = analogRead(A0);
playerPaddleY = map(val, 0, 1023, 0, SCREEN_HEIGHT - PADDLE_SIZE);
}
void updateAITarget() {
// Ricalcola il bersaglio solo se la palla si sta muovendo verso l'IA
if (dirX > 0) {
int errorRange = 0;
if (aiDifficulty == 1) errorRange = 3;
else if (aiDifficulty == 2) errorRange = 1;
// Aggiornare il target solo occasionalmente per evitare "vibrazioni".
if (random(0, 10) > 7) {
aiTargetY = ballY + random(-errorRange, errorRange + 1);
}
}
}
void moveAI() {
int aiSpeed = (aiDifficulty == 3) ? 2 : 1;
int center = aiPaddleY + (PADDLE_SIZE / 2);
if (center < aiTargetY) aiPaddleY += aiSpeed;
else if (center > aiTargetY) aiPaddleY -= aiSpeed;
aiPaddleY = constrain(aiPaddleY, 0, SCREEN_HEIGHT - PADDLE_SIZE);
}
void moveBall() {
ballX += dirX;
ballY += dirY;
}
void checkCollisions() {
// Rimbalzare dall'alto/dal basso
if (ballY <= 0 || ballY >= SCREEN_HEIGHT - 1) {
dirY = -dirY;
}
// Collisione tra racchetta e giocatore (verificare la distanza di sicurezza)
if (ballX <= PLAYER_X + 1 && dirX < 0) {
if (ballY >= playerPaddleY && ballY < playerPaddleY + PADDLE_SIZE) {
dirX = 1;
speedUp();
}
}
// collisione della pagaia AI
if (ballX >= AI_X - 1 && dirX > 0) {
if (ballY >= aiPaddleY && ballY < aiPaddleY + PADDLE_SIZE) {
dirX = -1;
speedUp();
}
}
// Punteggio
if (ballX < 0) {
aiScore++;
handlePointScored();
} else if (ballX >= SCREEN_WIDTH) {
playerScore++;
handlePointScored();
}
}
void speedUp() {
ballInterval = max(50, ballInterval - 8);
}
void handlePointScored() {
showScore();
if (playerScore >= MAX_SCORE || aiScore >= MAX_SCORE) {
gameOver();
} else {
startServe();
}
}
void resetGame() {
playerScore = 0;
aiScore = 0;
startServe();
}
void startServe() {
serving = true;
ballInterval = ORIGINAL_BALL_INTERVAL;
ballX = 7;
ballY = random(2, 6);
dirX = random(0, 2) ? 1 : -1;
dirY = random(0, 2) ? 1 : -1;
countdown();
serving = false;
}
// ---------------- FUNZIONI DI VISUALIZZAZIONE ----------------
void setPixel(int x, int y, bool state) {
if (x < 0 || x >= 16 || y < 0 || y >= 8) return;
// X logico 0-15 mappato su due dispositivi 8x8
// Inverti l'asse X se l'orientamento dell'hardware lo richiede.
int virtualX = 15 - x;
int device = virtualX / 8;
int col = virtualX % 8;
lc.setLed(device, y, col, state);
}
void drawGame() {
lc.clearDisplay(0);
lc.clearDisplay(1);
// Paletta del giocatore
for (int i = 0; i < PADDLE_SIZE; i++) setPixel(PLAYER_X, playerPaddleY + i, true);
// Paletta AI
for (int i = 0; i < PADDLE_SIZE; i++) setPixel(AI_X, aiPaddleY + i, true);
// Palla
if (!serving) setPixel(ballX, ballY, true);
}
void showTitle() {
lc.clearDisplay(0);
lc.clearDisplay(1);
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 16; x++) {
// Reading from PROGMEM
bool pixel = pgm_read_byte(&(title_bmp[y][x]));
setPixel(x, y, pixel);
}
}
delay(2000);
}
void showScore() {
lc.clearDisplay(0);
lc.clearDisplay(1);
// Semplice visualizzazione a barre dei punteggi
for (int i = 0; i < playerScore; i++) lc.setLed(0, 0, i, true);
for (int i = 0; i < aiScore; i++) lc.setLed(1, 0, i, true);
delay(1500);
}
void countdown() {
for (int i = 0; i < 3; i++) {
setPixel(7, 3, true); setPixel(8, 3, true);
delay(200);
lc.clearDisplay(0); lc.clearDisplay(1);
delay(200);
}
}
void gameOver() {
// Mostra il lato del vincitore
int device = (playerScore >= MAX_SCORE) ? 0 : 1;
for (int i = 0; i < 4; i++) {
for (int r = 0; r < 8; r++) lc.setRow(device, r, B11111111);
delay(300);
lc.clearDisplay(device);
delay(300);
}
resetGame();
}