#include <Arduino.h>
#include <TM1637Display.h> // avishorp/TM1637 -> TM1637Display.h
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <Keypad.h>
// ====================== PINOUT ======================
#define TM1_CLK 6
#define TM1_DIO 7
#define TM2_CLK 8
#define TM2_DIO 9
// MAX7219 (8x32 = 4 moduli 8x8 in cascata)
#define MAX_DIN 11
#define MAX_CLK 10
#define MAX_CS 12
// Se il testo è rovesciato/specchiato prova ICSTATION_HW
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
// Tastiera 4x4: R1..R4, C1..C4
const byte ROWS = 4, COLS = 4;
byte rowPins[ROWS] = {13, 14, 15, 16}; // righe
byte colPins[COLS] = {17, 18, 21, 20}; // colonne (QUI 20 al posto di 22)
char keys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
// ====================== OGGETTI ======================
TM1637Display dispP1(TM1_CLK, TM1_DIO);
TM1637Display dispP2(TM2_CLK, TM2_DIO);
// Parola con SPI "software" sui pin definiti sopra
MD_Parola parola = MD_Parola(HARDWARE_TYPE, MAX_DIN, MAX_CLK, MAX_CS, MAX_DEVICES);
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// ====================== STATI & MODE ======================
enum GameMode : uint8_t { MODE_NONE=0, MODE_501, MODE_301, MODE_HISCORE };
enum Phase : uint8_t { PH_MENU=0, PH_ASK_PLAYERS, PH_RUNNING, PH_GAME_OVER };
Phase phase = PH_MENU;
GameMode gameMode = MODE_NONE;
int numPlayers = 0; // 1 o 2
bool player1Turn = true; // chi sta giocando adesso
bool startP1_501 = true; // alterna chi parte ad ogni partita 501
bool startP1_301 = true; // alterna chi parte ad ogni partita 301
bool startP1_HIS = true; // alterna chi parte ad ogni partita hiscore
// Punteggi/turni
int scoreP1 = 0, scoreP2 = 0; // x01 = residuo; HiScore = totale
int startScore = 0; // 501 o 301
int inputScore = 0;
// Hi-Score: 7 turni per giocatore
const int HISCORE_TURNS = 7;
int turnsP1 = 0, turnsP2 = 0; // turni già giocati da ciascun giocatore
// Undo (1 livello)
struct Move {
bool valid;
GameMode mode;
bool p1TurnBefore; // chi aveva il turno prima dell'applicazione
int prevScoreP1;
int prevScoreP2;
int prevTurnsP1;
int prevTurnsP2;
int applied; // punti applicati
};
Move lastMove = {false, MODE_NONE, true, 0, 0, 0, 0, 0};
// ====================== UTILITY: MAX7219 ======================
void showMessage(const String& msg, uint16_t speed = 40, uint8_t pause = 0) {
parola.displayClear();
parola.displayReset();
parola.displayText((char*)msg.c_str(), PA_CENTER, speed, pause, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
while (!parola.displayAnimate()) { /* anima finché finito */ }
}
void showStatic(const String& msg) {
parola.displayClear();
parola.displayReset();
parola.displayText((char*)msg.c_str(), PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
parola.displayAnimate(); // applica frame
}
void showTurnBanner() {
// Per HiScore mostriamo anche Tn/7
if (gameMode == MODE_HISCORE) {
int tNow = player1Turn ? (turnsP1+1) : (turnsP2+1);
tNow = constrain(tNow, 1, HISCORE_TURNS);
String who = player1Turn ? "P1 " : "P2 ";
String msg = who + "T" + String(tNow) + "/" + String(HISCORE_TURNS);
showStatic(msg);
} else {
showStatic(player1Turn ? "P1" : "P2");
}
}
// ====================== UTILITY: TM1637 ======================
void show4(TM1637Display &d, int v) {
// Visualizza valore intero, blank leading zeros
d.showNumberDecEx(constrain(v, 0, 9999), 0, true, 4, 0);
}
void update7seg() {
show4(dispP1, scoreP1);
if (numPlayers >= 2) show4(dispP2, scoreP2);
else show4(dispP2, 0);
}
// ====================== RESET & MENU ======================
void toMenu() {
phase = PH_MENU;
gameMode = MODE_NONE;
numPlayers = 0;
inputScore = 0;
turnsP1 = turnsP2 = 0;
scoreP1 = scoreP2 = 0;
lastMove.valid = false;
update7seg();
showMessage("SELEZIONA GIOCO A=501 B=301 C=HIS");
delay(200);
showStatic("A B C");
}
// ====================== AVVIO GIOCO ======================
void askPlayers() {
phase = PH_ASK_PLAYERS;
inputScore = 0;
showMessage("1 O 2 GIOC?");
showStatic("1 o 2");
}
void startGame(GameMode mode, bool p1Starts) {
gameMode = mode;
player1Turn = p1Starts;
inputScore = 0;
lastMove.valid = false;
if (mode == MODE_501 || mode == MODE_301) {
startScore = (mode == MODE_501) ? 501 : 301;
scoreP1 = startScore;
scoreP2 = startScore;
turnsP1 = turnsP2 = 0; // non usati nei x01 ma azzeriamo
update7seg();
showMessage((mode == MODE_501) ? "501" : "301");
} else { // Hi-Score
scoreP1 = scoreP2 = 0;
turnsP1 = turnsP2 = 0;
update7seg();
showMessage("HI SCORE 7 TURNI");
}
delay(200);
phase = PH_RUNNING;
showTurnBanner();
}
// ====================== APPLICA PUNTEGGIO ======================
void applyScore(int pts) {
if (pts <= 0 || pts > 180) { showMessage("ERR"); return; }
// Salva stato per UNDO
lastMove = {
true,
gameMode,
player1Turn,
scoreP1, scoreP2,
turnsP1, turnsP2,
pts
};
if (gameMode == MODE_501 || gameMode == MODE_301) {
int &s = player1Turn ? scoreP1 : scoreP2;
int newS = s - pts;
if (newS < 0) { // BUST: nessuna variazione, cambio turno (se 2 giocatori)
lastMove.valid = false; // non è un "punteggio inserito"
showMessage("BUST");
if (numPlayers >= 2) {
player1Turn = !player1Turn;
}
showTurnBanner();
return;
}
s = newS;
update7seg();
if (newS == 0) {
showMessage(player1Turn ? "P1 WIN" : "P2 WIN", 40, 0);
phase = PH_GAME_OVER;
delay(400);
showStatic("WIN");
return;
}
// Cambio turno solo se 2 giocatori
if (numPlayers >= 2) player1Turn = !player1Turn;
showStatic(String(pts));
delay(120);
showTurnBanner();
return;
}
// ====== Hi-Score ======
if (gameMode == MODE_HISCORE) {
if (player1Turn) {
if (turnsP1 >= HISCORE_TURNS) { showMessage("P1 FINITO"); return; }
scoreP1 += pts;
turnsP1++;
} else {
if (turnsP2 >= HISCORE_TURNS) { showMessage("P2 FINITO"); return; }
scoreP2 += pts;
turnsP2++;
}
update7seg();
showStatic(String(pts));
delay(120);
// Verifica fine gara
bool p1done = (turnsP1 >= HISCORE_TURNS);
bool p2done = (numPlayers == 1) ? true : (turnsP2 >= HISCORE_TURNS);
if (p1done && p2done) {
// Fine — determina vincitore o pareggio
if (scoreP1 > scoreP2) showMessage("WINNER P1");
else if (scoreP2 > scoreP1) showMessage("WINNER P2");
else showMessage("PAREGGIO");
phase = PH_GAME_OVER;
delay(400);
String finalMsg = "P1 " + String(scoreP1) + " P2 " + String(scoreP2);
showMessage(finalMsg);
delay(400);
showStatic("FINE");
return;
}
// Passa turno se 2 giocatori e l'altro non ha finito
if (numPlayers >= 2) {
// Se l'altro è già finito, resta allo stesso
bool otherDone = player1Turn ? (turnsP2 >= HISCORE_TURNS) : (turnsP1 >= HISCORE_TURNS);
if (!otherDone) player1Turn = !player1Turn;
}
showTurnBanner();
}
}
// ====================== UNDO ======================
void undoLast() {
if (!lastMove.valid) { showMessage("NO UNDO"); return; }
// Ripristina
gameMode = lastMove.mode; // (di fatto invariato)
scoreP1 = lastMove.prevScoreP1;
scoreP2 = lastMove.prevScoreP2;
turnsP1 = lastMove.prevTurnsP1;
turnsP2 = lastMove.prevTurnsP2;
player1Turn= lastMove.p1TurnBefore;
update7seg();
showMessage("UNDO " + String(lastMove.applied));
lastMove.valid = false;
delay(150);
showTurnBanner();
}
// ====================== SETUP / LOOP ======================
void setup() {
// TM1637Display
dispP1.setBrightness(5); // 0..7
dispP2.setBrightness(5);
// MAX7219 / Parola
parola.begin();
parola.setZone(0, 0, MAX_DEVICES - 1); // unica zona su 4 moduli
parola.setIntensity(8); // 0..15
parola.displayClear();
// Se la matrice appare capovolta/specchiata, puoi abilitare questi:
// parola.setZoneEffect(0, PA_FLIP_UD, true); // capovolto su↔giù
// parola.setZoneEffect(0, PA_FLIP_LR, true); // specchiato sx↔dx
Serial.begin(115200);
delay(100);
toMenu();
}
void loop() {
parola.displayAnimate();
char key = keypad.getKey();
if (!key) return;
// Input numerico: accumula (limite 180)
if (key >= '0' && key <= '9') {
int next = inputScore * 10 + (key - '0');
if (next <= 180) {
inputScore = next;
// Mostra input sul MAX (senza bloccare lo stato)
showStatic(String(inputScore));
} else {
showMessage("MAX 180");
}
return;
}
switch (key) {
// ========= SELEZIONE GIOCO (A/B/C) =========
case 'A': { // 501
if (phase == PH_MENU || phase == PH_GAME_OVER) {
bool thisStart = startP1_501; // usa chi deve partire ORA...
startP1_501 = !startP1_501; // ...e alterna per la prossima volta
gameMode = MODE_501;
askPlayers();
// memorizziamo chi partirà quando avvieremo il gioco (dopo 1/2)
player1Turn = thisStart;
showMessage(thisStart ? "501 PART P1" : "501 PART P2");
}
} break;
case 'B': { // 301
if (phase == PH_MENU || phase == PH_GAME_OVER) {
bool thisStart = startP1_301;
startP1_301 = !startP1_301;
gameMode = MODE_301;
askPlayers();
player1Turn = thisStart;
showMessage(thisStart ? "301 PART P1" : "301 PART P2");
}
} break;
case 'C': { // Hi-Score
if (phase == PH_MENU || phase == PH_GAME_OVER) {
bool thisStart = startP1_HIS;
startP1_HIS = !startP1_HIS;
gameMode = MODE_HISCORE;
askPlayers();
player1Turn = thisStart;
showMessage(thisStart ? "HIS PART P1" : "HIS PART P2");
}
} break;
// ========= RESET TOTALE =========
case 'D':
toMenu();
break;
// ========= CONFERMA PUNTEGGIO =========
case '#':
if (phase == PH_ASK_PLAYERS) {
// qui # NON fa nulla: serve '1' o '2'
showMessage("SCEGLI 1 O 2");
break;
}
if (phase == PH_RUNNING) {
if (inputScore > 0) {
applyScore(inputScore);
inputScore = 0;
} else {
showMessage("NO DATA");
}
}
break;
// ========= UNDO =========
case '*':
if (phase == PH_RUNNING || phase == PH_GAME_OVER) {
undoLast();
inputScore = 0;
}
break;
// ========= SCELTA NUMERO GIOCATORI (in PH_ASK_PLAYERS) =========
case '1':
case '2':
if (phase == PH_ASK_PLAYERS) {
numPlayers = (key == '1') ? 1 : 2;
// Avvia il gioco scelto: usa player1Turn già fissato al momento della scelta modalità
startGame(gameMode, player1Turn);
}
break;
default:
break;
}
}