This is the ino file.
// Arduino Nano pin definitions
#define RED_LED 5 // Pin number for the red LED, Assuming succeeding pins R, B, Y, G
#define button_pin A1 // Input pullup pin for reading all 6 buttons
byte button_state = 6; // See read_buttons()
// Awesome calibration table: (Old prototype ver)
/* // Button Analog Value [LSB]
#define red_val 42
#define yellow_val 245
#define green_val 378
#define blue_val 465
String button2str[] = {"Red", "Yellow", "Green", "Blue"}; */
// Awesome calibration table: (Final)
// Button Analog Value [LSB]
#define white_val 589
#define red_val 535
#define blue_val 466
#define yellow_val 376
#define black_val 246
#define green_val 41 //235
String button2str[] = {"Red", "Blue", "Yellow", "Green", "White", "Black"};
#define val_tol 8 // +- How many LSB the measurement can be far from the calibrated value
#define N_avg 15 // Number of analog reads to average for a button value - minimize jitter
// External libraries
#include <Tone.h> // https://github.com/bhagman/Tone/ (V1.0.0)
#include <LiquidCrystal_I2C.h> // https://github.com/fmalpartida/New-LiquidCrystal (V1.5.0)
#include <EEPROM.h> // Arduino built-in
// Speaker stuff
#define PIN_PLAYER_1 10
#define PIN_PLAYER_2 11
int tronca = 15; // Increasing this value separates notes from each other. [OPTIONAL]
bool isMute = false; // Global sound switch, stored @ EEPROM, address "isMute_addr".
Tone player_1; // Speaker 1 object
Tone player_2; // Speaker 2 object
// Internal headers
#include "lcd_functions.h"
#include "tone_functions.h"
#include "global_functions.h"
#include "memory_game.h"
#include "reaction_game.h"
void setup(){
pinMode(button_pin,INPUT_PULLUP);
// pinMode(LED_BUILTIN, OUTPUT);
for(int i=0; i<4; i++){
pinMode(RED_LED+i,OUTPUT);
// digitalWrite(LED_BUILTIN, LOW);
}
lcd.begin(SCREEN_WIDTH,SCREEN_HEIGHT);
// Switch on the backlight
lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
lcd.setBacklight(HIGH);
setup_progressbar();
#if defined(debug_flag)
lcd.home(); lcd.print(F("Waiting for")); lcd.setCursor(0,1); lcd.print(F("serial..."));
Serial.begin(9600);
delay(100);
while(!Serial);
Serial.println("Welcome to Lou's Arcade!\nPlayer 1 Ready...");
#endif
lcd.home();
lcd.print(F(" LOU'S ARCADE! ")); lcd.setCursor(0,1);
lcd.print(F(" READY? ;) "));
// Load settings
EEPROM.get(isMute_addr, isMute); // Get Sound settings from the last time
EEPROM.get(N_times_played_addr, N_times_played);
if (N_times_played == 65536) {N_times_played = 0;} // init value in EEPROM
randomSeed(N_times_played);
EEPROM.get(reaction_initial_timeout_addr, reaction_initial_timeout);
if (reaction_initial_timeout == 65536) {reaction_initial_timeout = 3000;} // init value
EEPROM.get(memory_letters_delay_ms_addr, memory_letters_delay_ms);
if (memory_letters_delay_ms == 65536) {memory_letters_delay_ms = 250;} // init value
// Init Speakers
player_1.begin(PIN_PLAYER_1);
player_2.begin(PIN_PLAYER_2);
introduzione();
delay(200);
memory_game();
}
void listen_serial(){
if (Serial.available() > 0) {
receivedChar = Serial.read();
if (receivedChar == 'a'){ // Add a sequence letter
add_letter();
}
if (receivedChar == 'p'){ // Play sequence!
play_sequence();
}
if (receivedChar == 'i'){ // Reset EEPROM
EEPROM.put(memory_highscore_addr, 0);
EEPROM.put(reaction_highscore_addr, 0);
lcd.clear(); lcd.print(F(" Highscore ")); lcd.setCursor(0,1); lcd.print(" Cleared. ");
delay(1000);
menu();
}
if (receivedChar == 'm'){ // Play all music
playAllMusic();
}
if (receivedChar == 's'){ // Start memory_game play!
free_play = false;
memory_game();
}
if (receivedChar == 'r'){ // Start reaction_game play!
free_play = false;
reaction_game();
}
if (receivedChar == 'f'){ // Free button pressing
debugln("Welcome to Free Play! Press buttons and have fun!");
free_play = true;
}
if (receivedChar == 'v'){ // Volume toggle
toggle_volume();
}
if (receivedChar == 'c'){ // Button calibration
free_play = false;
while (!Serial.available()){
debugln(analogRead(button_pin));
delay(refresh_interval);
}
}
}
}
void menu(){
//sensorValue = analogRead(sensorPin);
const String menu_choices[] PROGMEM = {"Memory Game", "Reaction Game", "Free Play", "Music"};
button_state = 6;
int current_choice = 0;
lcd.clear(); lcd.print(menu_choices[current_choice] + "?");
while (true){
button_state = read_buttons(); //analogRead
if (button_state == 4){ // Change selection
current_choice++;
if (current_choice == mylength(menu_choices)){
current_choice = 0;
}
lcd.clear(); lcd.print(menu_choices[current_choice] + "?");
}
if (button_state == 5){ // Execute selection
break;
}
if (button_state == 0){ // Double click for settings (child proof)
if (is_double_click(button_state)){
settings();
lcd.clear(); lcd.print(menu_choices[current_choice] + "?");
}
}
if (Serial.available() > 0) {return;}
}
// Execute!
lcd.clear(); lcd.print("Execute!");
switch (current_choice){
case 0:
free_play = false;
memory_game();
break;
case 1:
free_play = false;
reaction_game();
break;
case 2:
free_play = true;
lcd.clear(); lcd.print(menu_choices[current_choice] + "!");
break;
case 3:
playAllMusic();
break;
}
}
void display_parameter(byte current_setting){
//{"Memory speed", "Reaction speed", "Times played", "Memory score", "Reaction score"}
switch (current_setting){
case 0:
lcd_print(1, String(memory_letters_delay_ms));
break;
case 1:
lcd_print(1, String(reaction_initial_timeout));
break;
case 2:
lcd_print(1, String(N_times_played));
break;
case 3:
lcd_print(1, String(EEPROM.read(memory_highscore_addr)));
break;
case 4:
lcd_print(1, String(EEPROM.read(reaction_highscore_addr)));
break;
case 5:
lcd_print(1,"");
lcd.write(byte(6));
lcd.print((isMute) ? "x":"v");
break;
}
}
void change_up_setting(byte current_setting){
switch (current_setting){
case 0:
memory_letters_delay_ms = min(2500,round(((float)memory_letters_delay_ms) * 1.15));
break;
case 1:
reaction_initial_timeout = min(5000,round(((float)reaction_initial_timeout) * 1.15));
break;
case 5:
toggle_volume();
break;
case 6:
clear_EEPROM();
menu();
break;
}
display_parameter(current_setting);
}
void change_down_setting(byte current_setting){
switch (current_setting){
case 0:
memory_letters_delay_ms = max(100,round(((float)memory_letters_delay_ms) / 1.15));
break;
case 1:
reaction_initial_timeout = max(100,round(((float)reaction_initial_timeout) / 1.15));
break;
case 5:
toggle_volume();
break;
case 6:
clear_EEPROM();
menu();
break;
}
display_parameter(current_setting);
}
void settings(){
const String settings_choices[] PROGMEM = {"Memory [ms]", "Reaction [ms]", "Times played", "Memory score", "Reaction score", "Volume", "Clear EEPROM"};
byte current_setting = 0;
lcd_print(0, settings_choices[current_setting]);
display_parameter(current_setting);
while (true){
button_state = read_buttons();
if (button_state == 4){ // Change selection
current_setting++;
if (current_setting == mylength(settings_choices)){
current_setting = 0;
}lcd_print(0, settings_choices[current_setting]);
display_parameter(current_setting);
}
if (button_state == 5){ // Save and quit this sub-menu
EEPROM.put(memory_letters_delay_ms_addr,memory_letters_delay_ms);
EEPROM.put(reaction_initial_timeout_addr,reaction_initial_timeout);
return;
}
if (button_state == 2){ // Mute/ Unmute
toggle_volume();
}
if (button_state == 0){ // Change up
change_up_setting(current_setting);
}
if (button_state == 1){ // Change down
change_down_setting(current_setting);
}
if (Serial.available() > 0) {return;}
}
}
void loop(){
listen_serial();
if ( (button_state == 4) || read_buttons() == 4){
menu();
}
if (free_play){read_buttons();}
delay(refresh_interval);
}
This is global functions
// Timings
#define refresh_interval 1000/40 // [ms] Used for calibration and free play
#define highscore_disp_timeout 4000 // [ms] Time to display highscore before switching to the menu.
#define click_window_ms 1000 // [ms] Max delay between clicks in a double click
// Debug header definition
#ifdef debug_flag
#define debug(str) Serial.print (str);
#define debug_dec(num) Serial.print (num, DEC)
#define debugln_dec(num) Serial.println (num, DEC)
#define debug_bin(num) Serial.print (num,BIN)
#define debugln(str) Serial.println (str)
#else
#define debug(str)
#define debug_dec(num)
#define debug_bin(num)
#define debugln(str)
#endif
// Count an array's length
#define mylength(array) ((unsigned int) (sizeof (array) / sizeof (array [0])))
bool game_over; // This is the break condition of while loops within the various games. False returns to menu.
byte level = 0; // Level variable used for all games
uint16_t N_times_played;
char receivedChar; // Serial port incoming
bool free_play = false; // AKA toddler mode
// EEPROM Stuff // Length (bytes)
#define isMute_addr 0 // 1
#define memory_highscore_addr 1 // 1
#define reaction_highscore_addr 2 // 1
#define N_times_played_addr 3 // 2
#define reaction_initial_timeout_addr 5 // 2
#define memory_letters_delay_ms_addr 7 // 2
void turn_off_LEDs(){
for (int i=0; i<4; i++){digitalWrite(RED_LED+i,LOW);};
}
void turn_on_LEDs(){
for (int i=0; i<4; i++){digitalWrite(RED_LED+i,HIGH);};
}
void flash_LEDs(int n=3){
for(int i=1; i<=n; i++){
turn_on_LEDs();
lcd.setBacklight(LOW);
delay(150);
turn_off_LEDs();
lcd.setBacklight(HIGH);
delay(150);
}
}
byte which_button_pressed(int measured_val){
// Apply the calibration table
if ( measured_val > 1000 ){ return(6); } // Nothing pressed
if ( abs( measured_val - red_val ) < val_tol ){ return(0); }
if ( abs( measured_val - blue_val ) < val_tol ){ return(1); }
if ( abs( measured_val - yellow_val ) < val_tol ){ return(2); }
if ( abs( measured_val - green_val ) < val_tol ){ return(3); }
if ( abs( measured_val - white_val ) < val_tol ){ return(4); }
if ( abs( measured_val - black_val ) < val_tol ){ return(5); }
return(7); // Error measuring - ignore.
}
uint16_t read_analog(){
float measured_val = 0;
for (int i = 0; i < N_avg; i++){ // Try overcoming jitter
measured_val += float(analogRead(button_pin))/N_avg;
delay(1);
}
measured_val = round(measured_val);
#ifdef debug_flag
if (measured_val < 1000) {debugln_dec(measured_val);}
#endif
return( measured_val );
}
byte read_buttons(){
// Output:
// 7 Measurement error - ignore
// 6 : nothing's pressed
// 0-3 : R, B, Y, G Buttons
// 4 : White
// 5 : Black
byte intermediate_button_state = which_button_pressed(read_analog());
if (intermediate_button_state <=5) {// Debouncing function
button_state = intermediate_button_state;
if (which_button_pressed(read_analog()) == button_state){ // Validate its not jitter
if (intermediate_button_state <=3) {// Play the respective button's tone. Play an indefinite tone but stop it once button was released.
digitalWrite(RED_LED+button_state,HIGH);
PlayColor(button_state, 1000);
}
debugln(button2str[button_state] + "!");
}
else { // Jitter?
button_state = 6;
}
while (intermediate_button_state != 6 ){
intermediate_button_state = which_button_pressed(read_analog());
}
}
else { // Pressed nothing.
button_state = 6;
}
turn_off_LEDs();
player_1.stop();
player_2.stop();
return(button_state);
}
bool is_double_click(byte first_click){
long t_start = millis();
button_state = 6;
while ((button_state==6) && ((millis()-t_start)<click_window_ms)){
button_state = read_buttons();
}
return(button_state == first_click);
}
void end_game(){
button_state = 5; // Signal the main sketch to go to the menu
N_times_played++;
EEPROM.put(N_times_played_addr,N_times_played);
}
void new_high_score(int level){
const String str1 PROGMEM = "NEW HIGH SCORE: ";
lcd_print(0,str1);
lcd_print(1,String(level));
COIN(level);
flash_LEDs(3);
FLAGPOLEFANFARE();
long time = millis();
while ((read_buttons()==6) && ((millis()-time) < 2*highscore_disp_timeout)) { delay(refresh_interval); }
end_game();
}
void show_high_score(int level, byte highscore_addr){
const String str1 PROGMEM = "Game over ;)";
const String str2 PROGMEM = "Level ";
const String str3 PROGMEM = "High score:";
lcd_print(0,str1); lcd_print(1,str2 + String(level));
DEATH();
lcd_print(0,str3); lcd_print(1,String(EEPROM.read(highscore_addr)));
GAMEOVER();
long time = millis();
while ((read_buttons()==6) && ((millis()-time) < 1*highscore_disp_timeout)){delay(refresh_interval);}
end_game();
}
void toggle_volume(){
//lcd.scrollDisplayLeft();
lcd_print(1,""); lcd.write(byte(6)); // Speaker character
if (isMute){
isMute = false;
lcd.write("v");
}else
{
isMute = true;
lcd.write("x");
}
debugln("Mute = " + String(isMute));
EEPROM.put(isMute_addr, isMute);
}
void clear_EEPROM(){
const String str1 PROGMEM = "2X click black";
const String str2 PROGMEM = "clear EEPROM";
const String str3 PROGMEM = "Done!";
button_state = 6;
lcd_print(0,str1);
lcd_print(1,str2);
while (button_state == 6){
button_state = read_buttons();
delay(refresh_interval);
}
if (button_state == 5){ // First click on white
if (is_double_click(5)){
// Clear stuff
EEPROM.put(memory_highscore_addr, byte(0));
EEPROM.put(reaction_highscore_addr, byte(0));
N_times_played = uint16_t(0);
EEPROM.put(N_times_played_addr, N_times_played);
lcd.clear();
lcd_print(0,str3);
delay(750);
}
}
}
This is memory game
#define max_level 128 // Must be multiple of 4
uint16_t memory_letters_delay_ms = 300;
byte sequence[max_level/4];
const byte sequence_theme = 1; // 1: pseudo random
// In the future - can be 2: "Mary had a little lamb" :)
//const PROGMEM
byte current_letter_index = 0; // Stores the index of letter just pressed by the player
void play_sequence(){
byte word;
byte letter;
debug("Level " + String(level) + ". Playing sequence (start -> end):");
delay(memory_letters_delay_ms);
for (int i_letter=0; i_letter<level; i_letter++){
if (i_letter%4 == 0){
word = sequence[(int)(i_letter/4)];
debug(" ");
}
// Play a word, starting from 2 LSBs
letter = 0;
letter = bitRead(word,1)<<1 | bitRead(word,0)<<0;
digitalWrite(RED_LED+letter,HIGH);
// Play the respective button's tone
PlayColor(letter, memory_letters_delay_ms/inputTempo());
while(player_1.isPlaying()){delay(5);}
debug_dec(letter);
word = word >> 2;
digitalWrite(RED_LED+letter,LOW);
delay(memory_letters_delay_ms);
}
debugln("");
}
void add_letter(){
// Add 1 letter to the global var called "sequence"
byte letter;
switch (sequence_theme){
case 1: //1: pseudo random
letter = random(4); // 0, 1, 2, 3 : R, Y, G, B, Respectively
break;
case 2: //2: Mary had a little lamb - To be developed
//letter = random(4); // 0, 1, 2, 3 : R, Y, G, B, Respectively
break;
}
// Write the addition to the sequence
bitWrite(sequence[level/4], 2*(level % 4) + 0 , bitRead(letter,0));
bitWrite(sequence[level/4], 2*(level % 4) + 1 , bitRead(letter,1));
level++;
#if defined(debug_flag)
Serial.print("Level " + String(level) + ". Added letter: " + String(letter) + ". Sequence (end -> start): ");
for (int i=((level-1)/4); i>=0; i--){
for (int j=7; j>=0; j--){
Serial.print(bitRead(sequence[i],j));
}
Serial.print(" ");
}
Serial.println("");
#endif
}
bool is_correct_letter(byte button_state){
byte word = sequence[(int)(current_letter_index/4)];
byte expected_letter = bitRead(word,2*(current_letter_index%4) + 1)<<1 | bitRead(word,2*(current_letter_index%4) +0)<<0;
debug("Current word: ");
debug_bin(word);
debug(", Button expected: " + String(expected_letter) + ". Button pressed: " + String(button_state) + "\n");
return button_state == expected_letter;
}
void memory_game(){
debugln("Good luck, Player 1!");
game_over = false;
lcd.clear(); lcd.print(F(" Go ")); lcd.setCursor(0,1); lcd.print(" Memory Game!!! ");
ONEUP();
current_letter_index = 0;
level = 0;
add_letter();
play_sequence();
while (!game_over){
button_state = read_buttons();
if (button_state<=3) // If pressed on one of the color buttons
{
if (is_correct_letter(button_state)){
// Player got the right letter. Check if it was the end of the sequence
if (current_letter_index == level-1){
debugln("Correct! Level " + String(level) + " cleared!");
lcd.clear(); lcd.print(" Level " + String(level)); lcd.setCursor(0,1); lcd.print(F(" completed! "));
current_letter_index = 0;
POWERUP();
delay(memory_letters_delay_ms);
add_letter();
play_sequence();
}
else{ // Player pressed the correct button, in the middle of the sequence
current_letter_index++;
debugln("Go on!!!");
}
turn_off_LEDs();
}
else{ // Player got the wrong letter
if (level > EEPROM.read(memory_highscore_addr)) { // NEW HIGH SCORE!
EEPROM.put(memory_highscore_addr, level);
new_high_score(level);
}
else {
game_over = true;
debugln("Failed on letter " + String(current_letter_index) + ", level " + String(level) + "\n:()" );
show_high_score(level,memory_highscore_addr);
}
}
}
else if (button_state == 4){ // Go to the menu
game_over = true;
}
if (Serial.available() > 0) {return;}
}
}
in the calibrate mode the white button will scroll the lcd display through all of the options. When the power is applied the sketch go to "Go Memory Game! ! !" and stops pushing the white or black buttons does nothing. After a short time the speakers have a constant tone emitted from them. I uploaded the example code ananlogin serial out and the Nano replied back a value for each button pushed. Any thing else needed i will be happy to supply it. Thanks Duck 50