#define SW_VERS "v2.0"
// TARGET
// Arduino UNO R3
// DEBUG Const
#define DEBUG true //set to true for debug output, false for no debug output
#define DEBUG_SERIAL \
if (DEBUG) Serial
// Add HW target (eventually boards installed)
// Add External Library
// Display MAX72xx
#include <stdio.h>
#include <MD_Parola.h> // include MajicDesigns Parola library
#include <MD_MAX72xx.h> // include MajicDesigns MAX72xx LED matrix library
#include <SPI.h> // include Arduino SPI library
// Add Internal Library
#include "BTN.h"
// Pin assignment
#define PIN_BUTTON {2, 3, 4, 5, 6}
#define PIN_LED {7, 8, 9, 14, 15}
#define PIN_CS 10
#define PIN_DIN 11
#define PIN_CLK 13
// HW Constants
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW //MD_MAX72XX::FC16_HW // this line defines our dot matrix hardware type (FC-16)
#define MAX_DEVICES 4 // define number of total cascaded modules
#define INTENSITY 3 // 0-15 values
#define NUM_POINTS 5
// SW Constants
const uint8_t logo[] = {8, 0, 28, 106, 137, 129, 98, 28, 0};
#define MAX_SEQ 50
// HW Global variables
MyBtn btn[NUM_POINTS];
int pinBtn[NUM_POINTS] = PIN_BUTTON;
int pinLed[NUM_POINTS] = PIN_LED;
MD_Parola display = MD_Parola(HARDWARE_TYPE, PIN_CS, MAX_DEVICES);
// SW Global variables
enum gameStateEnum {VERSION, INIT_GUACK, START_GUACK, GAME_GUACK, WINNER_GUACK, FINISH_GUACK, INIT_SIMON, START_SIMON, GAME_SIMON, GAME_SIMON_SEQ, GAME_SIMON_CHECK, WINNER_SIMON, FINISH_SIMON};
enum gameStateEnum gameState = VERSION;
enum gameStateEnum gameStateOld = START_GUACK;
bool first = false;
bool ledOn[NUM_POINTS];
uint16_t scorePlay = 0;
char sScorePlay[6];
int livesPlay = 10;
uint8_t ledSeq[MAX_SEQ];
uint8_t indexSeq = 0;
void setup() {
Serial.begin(9600);
DEBUG_SERIAL.println("SETUP");
for (int i=0; i<NUM_POINTS; i++) {
pinMode(pinLed[i], OUTPUT);
pinMode(pinBtn[i], INPUT_PULLUP);
btn[i].configPin(pinBtn[i]);
}
// initialize the dot matrix display
display.begin();
// set the intensity (brightness) of the display (choose a number between 0 and 15)
display.setIntensity(INTENSITY);
display.setZone(0,0,3);
display.setZone(1,0,1);
display.setZone(2,2,3);
display.addChar('$', logo);
// display version
display.displayReset();
display.displayClear();
display.setTextBuffer(SW_VERS);
display.setTextAlignment(PA_CENTER);
delay(1000);
randomSeed(analogRead(3));
}
void loop() {
// First evaluation for first time in new state
if (gameStateOld != gameState) {
first = true;
gameStateOld = gameState;
}
// TODO: a cosa serve?
if (display.displayAnimate()) { // animate the display
display.displayReset(); // reset the current animation
}
switch (gameState) {
case VERSION:
if (first) {
first = false;
DEBUG_SERIAL.println("VERSION");
}
if (animVers()) {
// TODO Start with INIT_GUACK
gameState = INIT_SIMON;
}
break;
case INIT_GUACK:
if (first) {
first = false;
clearBtnsAllEvents();
clearLeds();
// Splash Text
display.displayClear();
display.displayReset();
display.setTextBuffer("Guack-a-mole $");
display.setTextAlignment(PA_LEFT);
display.setSpeed(50);
display.setTextEffect(PA_SCROLL_LEFT, PA_SCROLL_LEFT);
DEBUG_SERIAL.println("INIT_GUACK");
}
animInitGuack();
for (int i=0; i<NUM_POINTS; i++) {
if (btn[i].wasReleased()) {
btn[i].clearReleased();
gameState = START_GUACK;
}
}
if (btn[0].wasLongPressed() && btn[NUM_POINTS-1].wasLongPressed()) {
btn[0].clearLongPressed();
btn[NUM_POINTS-1].clearLongPressed();
gameState = INIT_SIMON;
}
break;
case START_GUACK:
if (first) {
first = false;
clearBtnsAllEvents();
clearLeds();
display.displayClear();
DEBUG_SERIAL.println("START_GUACK");
}
if (animStartGuack()) {
gameState = GAME_GUACK;
}
break;
case GAME_GUACK:
if (first) {
first = false;
clearBtnsAllEvents();
clearLeds();
display.displayClear();
display.displayReset();
display.setTextBuffer("GO!");
display.setTextAlignment(PA_CENTER);
display.setTextEffect(PA_NO_EFFECT, PA_NO_EFFECT);
memset(ledOn, 0, NUM_POINTS * sizeof(bool));
scorePlay = 0;
livesPlay = 10;
DEBUG_SERIAL.println("GAME_GUACK");
}
for (int i=0; i<NUM_POINTS; i++) {
if (btn[i].wasPressed()) {
btn[i].clearPressed();
if (ledOn[i]) {
scorePlay = scorePlay + 10;
ledOn[i] = false;
turnOffLed(i);
}
else {
if (scorePlay>=5) scorePlay = scorePlay - 5;
livesPlay--;
}
itoa(scorePlay, sScorePlay, 10);
display.displayClear();
display.displayReset();
display.setTextBuffer(sScorePlay);
display.setTextAlignment(PA_CENTER);
display.setTextEffect(PA_NO_EFFECT, PA_NO_EFFECT);
}
}
animGameGuack();
if (livesPlay <= 0) {
gameState = WINNER_GUACK;
}
break;
case WINNER_GUACK:
if (first) {
first = false;
clearBtnsAllEvents();
clearLeds();
// Game over text
display.displayClear();
display.displayReset();
display.setTextBuffer("GAME OVER!");
display.setTextAlignment(PA_LEFT);
display.setSpeed(50);
display.setTextEffect(PA_SCROLL_LEFT, PA_SCROLL_LEFT);
DEBUG_SERIAL.println("WINNER_GUACK");
}
if (animWinnerGuack()) {
gameState = FINISH_GUACK;
}
break;
case FINISH_GUACK:
if (first) {
first = false;
clearBtnsAllEvents();
clearLeds();
itoa(scorePlay, sScorePlay, 10);
display.displayClear();
display.displayReset();
display.setTextBuffer(sScorePlay);
display.setTextAlignment(PA_CENTER);
display.setTextEffect(PA_NO_EFFECT, PA_NO_EFFECT);
DEBUG_SERIAL.println("FINISH_GUACK");
}
for (int i=0; i<NUM_POINTS; i++) {
if (btn[i].wasReleased()) {
btn[i].clearReleased();
gameState = INIT_GUACK;
}
}
break;
case INIT_SIMON:
if (first) {
first = false;
clearBtnsAllEvents();
clearLeds();
// Splash Text
display.displayClear();
display.displayReset();
display.setTextBuffer("Simon $");
display.setTextAlignment(PA_LEFT);
display.setSpeed(50);
display.setTextEffect(PA_SCROLL_LEFT, PA_SCROLL_LEFT);
DEBUG_SERIAL.println("INIT_SIMON");
}
animInitSimon();
for (int i=0; i<NUM_POINTS; i++) {
if (btn[i].wasReleased()) {
btn[i].clearReleased();
gameState = START_SIMON;
}
}
if (btn[0].wasLongPressed() && btn[NUM_POINTS-1].wasLongPressed()) {
btn[0].clearLongPressed();
btn[NUM_POINTS-1].clearLongPressed();
gameState = INIT_GUACK;
}
break;
case START_SIMON:
if (first) {
first = false;
clearBtnsAllEvents();
clearLeds();
display.displayClear();
DEBUG_SERIAL.println("START_SIMON");
}
if (animStartSimon()) {
gameState = GAME_SIMON;
}
break;
case GAME_SIMON:
if (first) {
first = false;
clearBtnsAllEvents();
clearLeds();
display.displayClear();
display.displayReset();
display.setTextBuffer("GO!");
display.setTextAlignment(PA_CENTER);
display.setTextEffect(PA_NO_EFFECT, PA_NO_EFFECT);
for (int i=0; i<MAX_SEQ; i++) {
ledSeq[i] = random(0, NUM_POINTS);
}
scorePlay = 0;
DEBUG_SERIAL.println("GAME_SIMON");
}
gameState = GAME_SIMON_SEQ;
break;
case GAME_SIMON_SEQ:
if (first) {
first = false;
itoa(scorePlay+1, sScorePlay, 10);
display.displayClear();
display.displayReset();
display.setTextBuffer(sScorePlay);
display.setTextAlignment(PA_CENTER);
display.setTextEffect(PA_NO_EFFECT, PA_NO_EFFECT);
DEBUG_SERIAL.println("GAME_SIMON_SEQ");
}
// Attendo fine dimostrazione sequenza
if (animSeqSimon(scorePlay+1)) {
gameState = GAME_SIMON_CHECK;
}
break;
case GAME_SIMON_CHECK:
if (first) {
first = false;
indexSeq = 0;
clearBtnsAllEvents();
DEBUG_SERIAL.println("GAME_SIMON_CHECK");
}
// Leggo risposta giocatore
for (int i=0; i<NUM_POINTS; i++) {
if (btn[i].wasPressed()) {
btn[i].clearPressed();
if (ledSeq[indexSeq] == i) {
indexSeq++;
if ((indexSeq-1) == scorePlay) {
scorePlay++;
gameState = GAME_SIMON_SEQ;
}
}
else {
gameState = WINNER_SIMON;
}
}
}
break;
case WINNER_SIMON:
if (first) {
first = false;
clearBtnsAllEvents();
clearLeds();
// Game over text
display.displayClear();
display.displayReset();
display.setTextBuffer("GAME OVER!");
display.setTextAlignment(PA_LEFT);
display.setSpeed(50);
display.setTextEffect(PA_SCROLL_LEFT, PA_SCROLL_LEFT);
DEBUG_SERIAL.println("WINNER_GUACK");
}
if (animWinnerSimon()) {
gameState = FINISH_SIMON;
}
break;
case FINISH_SIMON:
if (first) {
first = false;
clearBtnsAllEvents();
clearLeds();
itoa(scorePlay, sScorePlay, 10);
display.displayClear();
display.displayReset();
display.setTextBuffer(sScorePlay);
display.setTextAlignment(PA_CENTER);
display.setTextEffect(PA_NO_EFFECT, PA_NO_EFFECT);
DEBUG_SERIAL.println("FINISH_GUACK");
}
for (int i=0; i<NUM_POINTS; i++) {
if (btn[i].wasReleased()) {
btn[i].clearReleased();
gameState = INIT_SIMON;
}
}
break;
default:
gameState = INIT_GUACK;
break;
}
readBtns();
}
bool animVers() {
static long timerVers = 0;
const uint16_t TIME_VERS = 1000;
if (timerVers == 0) timerVers = millis();
if ((millis() - timerVers) > TIME_VERS) {
timerVers = 0;
return true;
}
return false;
}
bool animInitGuack() {
static long timerInitGuack = 0;
const uint16_t TIME_INIT_ANIM_GUACK = 1000;
static uint8_t numLedOn = 0;
static bool upAnim = true;
if ((millis() - timerInitGuack) > TIME_INIT_ANIM_GUACK) {
if (upAnim) {
//spengo led precedente
if (numLedOn == 0) {
turnOffLed(numLedOn+1);
}
else {
turnOffLed(numLedOn-1);
}
//accendo led corrente
turnOnLed(numLedOn);
if (numLedOn == (NUM_POINTS-1)) {
upAnim = !upAnim;
numLedOn = NUM_POINTS-1-1;
}
else {
numLedOn++;
}
}
else {
//spengo led precedente
if (numLedOn == (NUM_POINTS-1)) {
turnOffLed(numLedOn-1);
}
else {
turnOffLed(numLedOn+1);
}
//accendo led corrente
turnOnLed(numLedOn);
if (numLedOn == 0) {
upAnim = !upAnim;
numLedOn = 1;
}
else {
numLedOn--;
}
}
timerInitGuack = millis();
}
}
bool animStartGuack() {
static long timerStartGuack = 0;
const uint16_t TIME_START_ANIM_GUACK = 1000;
static int step = 6;
if ((millis() - timerStartGuack) > TIME_START_ANIM_GUACK) {
step--;
sprintf(sScorePlay, "%d", step);
//itoa(step, sCountDown, 10);
display.displayClear();
display.displayReset();
display.setTextBuffer(sScorePlay);
display.setTextAlignment(PA_CENTER);
display.setTextEffect(PA_NO_EFFECT, PA_NO_EFFECT);
timerStartGuack = millis();
if (step == 0) {
step = 6;
return true;
}
}
return false;
}
bool animGameGuack() {
const uint16_t TIME_NEW_LED_START_ANIM = 2000;
const uint16_t TIME_NEW_LED_END_ANIM = 100;
static long timerNewLed = 0;
static uint16_t timeNewLedAnim = TIME_NEW_LED_START_ANIM;
uint8_t ledLost = 0;
uint8_t maxLedOn = 0;
uint8_t numLedOn = 0;
uint8_t index = 0;
for (int i = 0; i < NUM_POINTS; i++) {
if (ledOn[i]) {
ledLost++;
break;
}
}
if (((millis() - timerNewLed) > timeNewLedAnim) || (ledLost==0)) {
// Valuto quanti led sono rimasti accesi e li spengo
if (ledLost != 0) {
ledLost = 0;
for (int i = 0; i < NUM_POINTS; i++) {
if (ledOn[i]) {
ledLost++;
ledOn[i] = false;
turnOffLed(i);
}
}
livesPlay = livesPlay - ledLost;
}
else {
if (livesPlay<10) livesPlay++;
}
// Accendo nuovi led
maxLedOn = (scorePlay/100)+2;
if (maxLedOn > 6) maxLedOn = 6;
numLedOn = floor(random(1, maxLedOn));
for (int i=0; i<numLedOn; i++) {
index = random(0, NUM_POINTS);
ledOn[index] = true;
turnOnLed(index);
}
// Valuto tempo per nuovi led
if ((TIME_NEW_LED_START_ANIM - scorePlay) > TIME_NEW_LED_END_ANIM) {
timeNewLedAnim = TIME_NEW_LED_START_ANIM - scorePlay;
}
else {
timeNewLedAnim = TIME_NEW_LED_END_ANIM;
}
timerNewLed = millis();
}
}
bool animWinnerGuack() {
static long timerWinnerGuack = 0;
const uint16_t TIME_WINNER_GUACK = 3000;
if (timerWinnerGuack == 0) timerWinnerGuack = millis();
if ((millis() - timerWinnerGuack) > TIME_WINNER_GUACK) {
timerWinnerGuack = 0;
return true;
}
return false;
}
bool animInitSimon() {
return animInitGuack();
}
bool animStartSimon() {
return animStartGuack();
}
bool animSeqSimon(uint8_t endIndex) {
static long timerSeqSimon = 0;
const uint16_t TIME_ON_OFF = 500;
static uint8_t seqStep = 1;
if ((millis() - timerSeqSimon) > TIME_ON_OFF) {
if (seqStep%2) {
turnOnLed(ledSeq[(seqStep-1)/2]);
}
else {
turnOffLed(ledSeq[(seqStep-2)/2]);
}
seqStep++;
if (seqStep > (2*endIndex)) {
seqStep = 1;
return true;
}
timerSeqSimon = millis();
}
return false;
}
bool animWinnerSimon() {
return animWinnerGuack();
}
void turnOnLed(int i) {
digitalWrite(pinLed[i], HIGH);
}
void turnOffLed(int i) {
digitalWrite(pinLed[i], LOW);
}
void clearLeds() {
for (int i = 0; i <NUM_POINTS; i++) {
digitalWrite(pinLed[i], LOW); //Spegni tutti i led
}
}
void clearBtnsAllEvents() {
for (int i=0; i<NUM_POINTS; i++) {
btn[i].clearAllEvents();
}
}
void readBtns() {
for (int i=0; i<NUM_POINTS; i++) {
btn[i].readBtn();
}
}
/*
#define TIMER_LIVELLO1 2500 //tempo di gioco all'inizio
#define TIMER_DELTA 10 //tempo riduzione per ogni livello
#define TIMER_MIN 120 // tempo per ogni Led
#define TIMER_ANIMAZIONE 150
#define TIMER_OVER 250
#define TIMER_COUNT 6000
#define END_TIME 5000
#define PUNTI_LED 10 // punti per led spento
#define PUNTI_ERRORE 5 // punti per pulsante premuto sbagliato
long startTime, timerLivello;
int numInit;
int maxLed;
int numLed, numLedOn;
int punti;
int secondiRimanenti;
int8_t vite;
char s_punti[6];
char s_over[15];
void loop() {
case initSLOT:
if (first) {
// first = false;
Serial.println("stato INIT SLOT");
for (int i = 0; i < NUMBUTTON; i++) {
digitalWrite(pinLED[i], LOW); //Spegni il led
}
numInit++; //numero di volte che siamo passati in initSLOT
}
numLedOn = 0;
//elezione led da accendere
//max variabile da 2->NUMBUTTON+1
maxLed = (numInit/10)+2;
Serial.print("Max led ACCESI: ");
Serial.println(maxLed);
if (maxLed > 6){
maxLed = 6;
}
numLed = floor(random(1,maxLed));
//"spengo" i led
for (int i=0; i<NUMBUTTON; i++){
ledOn[i]= LOW;
}
//individuo quali led accendere
for (int i=0; i<numLed; i++){
ledOn[random(0,NUMBUTTON)]= HIGH;
}
Serial.print("led ACCESI: ");
for(int i = 0; i < NUMBUTTON; i++)
{
Serial.print(ledOn[i]);
}
for (int i=0; i<NUMBUTTON; i++){
if (ledOn[i]==HIGH) {
numLedOn++;
}
}
// Serial.print("Numero led ACCESI: ");
// Serial.println(numLedOn);
//tempo di accensione led
timerLivello = max(TIMER_MIN*numLed, TIMER_LIVELLO1-numInit*TIMER_DELTA);
Serial.print("Timer livello: ");
Serial.println(timerLivello);
//accendo i Led
for (int i=0; i<NUMBUTTON; i++){
digitalWrite(pinLED[i], ledOn[i]);
}
stato = loopSLOT;
break;
case loopSLOT:
if (first) {
first = false;
Serial.println("stato LOOP SLOT");
startTime = millis();
}
//controllo quale pulsante è stato premuto, se corrisponde ad un led acceso, lo spengo e decremento il num di led accesi
for (int i = 0; i < NUMBUTTON; i++) {
if (pressed[i]==true) {
if (ledOn[i]== HIGH){
ledOn[i] = LOW;
numLedOn--;
Serial.print("Numero led ACCESI: ");
Serial.println(numLedOn);
punti = punti + PUNTI_LED;
}
else
{
punti = punti - PUNTI_ERRORE;
//Serial.println("ERRORE");
vite--;
}
pressed[i]=false;
Serial.print("PUNTI: ");
Serial.println(punti);
// Convert 123 to string [buf]
itoa(punti, s_punti, 10);
Serial.println(s_punti);
display.displayClear();
display.displayZoneText(0, s_punti, PA_LEFT, 50, 0, PA_NO_EFFECT, PA_NO_EFFECT);
}
}
for (int i=0; i<NUMBUTTON; i++){
digitalWrite(pinLED[i], ledOn[i]);
}
//MODIFICA TEMPORANEA//
// Diego: impostato -10 e non a 0 i punti per perdere per evitare che appena inizi il gioco si perda subito
// da re-impostare a 0 quando si implementa l'anti-rimbalzo dei pulsanti
if (((millis()>=(startTime+timerLivello)))||(numLedOn<=0)) {
stato = endSLOT;
}
break;
case endSLOT:
if (first) {
first = false;
Serial.println("stato END SLOT");
}
if (numLedOn<=0){
stato = initSLOT;
vite<10 ? vite++ : vite=10;
}
else {
vite = vite - numLedOn;
Serial.print("VITE: ");
Serial.println(vite);
if (vite < 0) {
stato = END;
}
else {
stato = initSLOT;
}
}
break;
case END:
if (first) {
first = false;
Serial.println("stato END");
startTime = millis();
st = 0;
//MODIFICA TEMPORANEA//
// Diego: aggiunto il punteggio finale prima di GAME OVER, da discutere come migliorarlo
strncpy(s_over, "", sizeof(s_over));
strcat(s_over,s_punti);
strcat(s_over,": GAME OVER!!!");
//FINE MODIFICA TEMPORANEA//
display.displayClear();
display.displayZoneText(0,s_over, PA_LEFT, 50, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
// print text on the display
}
if (millis()>=startTime+TIMER_OVER) {
st++;
startTime = millis();
}
if ((st%2) == 0) {
for (int i=0; i<NUMBUTTON; i++){
ledOn[i]= HIGH;
}
}
else {
for (int i=0; i<NUMBUTTON; i++){
ledOn[i]= LOW;
}
}
for (int i=0; i<NUMBUTTON; i++){
digitalWrite(pinLED[i], ledOn[i]);
}
for (int i = 0; i < NUMBUTTON; i++) {
if (true==pressed[i]) {
pressed[i]=false;
stato = countDOWN;
}
}
Serial.println(st);
if (st>=100) {
stato = statoSTART;
}
break;
default:
stato = statoSTART;
break;
}
}
void leggiTasti(){
static int pressedOLD[NUMBUTTON];
static long timeLastPress[NUMBUTTON];
int val;
//Serial.println("leggo tasti");
for (int i = 0; i < NUMBUTTON; i++) {
val = digitalRead(pinBUTTON[i]);
if (val == LOW && pressedOLD[i]==HIGH){
if (millis() > timeLastPress[i]+10) {
pressed[i]=true;
timeLastPress[i]=millis();
}
}
pressedOLD[i]=val;
}
}
/*
// TODO: per eventualmente filtrare le chiamate digitalwrite solo ai cambi di stato del led
void aggiornaLed(){
static int ledOLD[NUMBUTTON];
//Serial.println("aggiorno led");
for (int i = 0; i < NUMBUTTON; i++) {
if (val == HIGH && pressedOLD[i]==LOW){
val = digitalRead(pinBUTTON[i]);
}
pressedOLD[i]=val;
}
}
*/