/*
Reaction Timer Game
*/
#include <LiquidCrystal_I2C.h>
const int NUM_IO = 4;
const int BUZZ_PIN = 4;
const int BTN_PINS[] = {12, 10, 8, 6};
const int LED_PINS[] = {11, 9, 7, 5};
// define pitches, 0 = rest
const int NOTE_C4 = 262;
const int NOTE_E4 = 330;
const int NOTE_G4 = 392;
const int NOTE_A4 = 440;
// array of 4-step sequences
const int SEQUENCE[][4] = {
NOTE_C4, NOTE_E4, NOTE_G4, NOTE_A4,
NOTE_C4, NOTE_E4, NOTE_G4, 0,
NOTE_C4, NOTE_E4, 0, 0,
NOTE_A4, NOTE_C4, 0, 0
};
const int TOTAL_STEPS = 4;
const char* RESULT_MSG[] = {
" Excellent!",
" Good ",
" Average ",
"S l o w ..."
};
const unsigned long RESULT_TIME = 5000;
LiquidCrystal_I2C lcd(0x27, 16, 2);
void clearAll() {
for (int i = 0; i < NUM_IO; i++) {
digitalWrite(LED_PINS[i], LOW);
}
lcd.clear();
}
void getReady() {
lcd.setCursor(6, 0);
lcd.print("Get");
lcd.setCursor(5, 1);
lcd.print("ready!");
delay(random(500, 3000));
lcd.clear();
lcd.setCursor(7, 1);
lcd.print("GO");
}
unsigned long getTime(int randomNum) {
digitalWrite(LED_PINS[randomNum], HIGH);
unsigned long startTime = millis();
// wait for the correct button to be pressed
while (digitalRead(BTN_PINS[randomNum])) {}
unsigned long endTime = millis();
unsigned long reactTime = endTime - startTime;
return reactTime;
}
void playResultTone(int segIdx) {
for (int currentStep = 0; currentStep < TOTAL_STEPS; currentStep++) {
// a delay time (100ms to 1000ms)
int stepDuration = 250;
// Play the tone for the current step
int currentNote = SEQUENCE[segIdx][currentStep];
if (currentNote > 0) {
tone(BUZZ_PIN, currentNote);
} else {
noTone(BUZZ_PIN); // Rest step
}
// Let the note play for 80% of the step duration
delay(stepDuration * 0.80);
// Turn off the sound briefly between steps to separate the notes distinctively
noTone(BUZZ_PIN);
delay(stepDuration * 0.20);
}
}
void showResults(unsigned long reactTime) {
char buffer[16];
int resultStrIdx = 0;
if (reactTime < 750) {
resultStrIdx = 0;
} else if (reactTime < 1000) {
resultStrIdx = 1;
} else if (reactTime < 1250) {
resultStrIdx = 2;
} else if (reactTime >= 1250) {
resultStrIdx = 3;
}
Serial.println(RESULT_MSG[resultStrIdx]);
lcd.clear();
lcd.setCursor(1, 0);
snprintf(buffer, 16, "Time: %4d ms", reactTime);
lcd.print(buffer);
lcd.setCursor(2, 1);
lcd.print(RESULT_MSG[resultStrIdx]);
playResultTone(resultStrIdx);
}
void setup() {
Serial.begin(9600);
lcd.init();
lcd.backlight();
for (int index = 0; index < NUM_IO; index++) {
pinMode(BTN_PINS[index], INPUT_PULLUP);
pinMode(LED_PINS[index], OUTPUT);
}
pinMode(BUZZ_PIN, OUTPUT);
randomSeed(analogRead(A0));
// initialize
lcd.setCursor(1, 0);
lcd.print("Reaction Timer");
lcd.setCursor(6, 1);
lcd.print("V1.0");
delay(3000);
lcd.clear();
}
void loop() {
// clear output
clearAll();
// get ramdom number
int randomNum = random(NUM_IO);
// wait random time
getReady();
// get reaction time
unsigned long result = getTime(randomNum);
// show reaction time
showResults(result);
delay(RESULT_TIME);
}