#include <LiquidCrystal.h>
// Initialize the LCD library with the Arduino pins connected to RS, E, D4, D5, D6, D7 of the LCD
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
// Define pins for the LEDs
const int redLeds[2] = {6, 4}; // redLed1, redLed2
const int greenLed = 5;
// Define the pin for the push button
const int buttonPin = 3;
// Define the pin for the buzzer
const int buzzerPin = 2;
// Define note frequencies (in hz) for game actions
const int NOTE_START_GAME = 523; // C5
const int NOTE_TOO_EARLY = 349; // F4
const int NOTE_CORRECT = 659; // E5
const int NOTE_TOO_SLOW = 196; // G3
// Variables to track game state
bool greenLit = false; // Keeps track if the green LED is currently lit
// "greenStartTime" stores the exact moment (in ms) when the green LED lights up
// The function millis() returns how many milliseconds have passed since the program started
// By saving this value when the green LED turns on, we can later check the current time again when
// the user presses the button. Subtracting these two times gives the user's reaction time,
// measured in milliseconds, which is very precise for timing events
unsigned long greenStartTime = 0;
// Store the best (lowest) reaction time recorded so far, initialized with a very high value
unsigned long bestReactionTime = 9999;
// Count how many attempts the player has made
int attempts = 0;
void setup() {
// Initialize the LCD size (16x2)
lcd.begin(16, 2);
// Set LED pins as outputs
pinMode(greenLed, OUTPUT);
pinMode(redLeds[0], OUTPUT);
pinMode(redLeds[1], OUTPUT);
// Configure the button pin as input with an internal pull-up resistor enabled
// This means the pin normally reads HIGH (because of the internal resistor)
// When the button is pressed, it connects the pin directly to ground (LOW)
// Using INPUT_PULLUP means you don't need to add an external resistor to keep the pin stable
pinMode(buttonPin, INPUT_PULLUP);
// Initialize the random number generator with a seed (starting) value
// analogRead(0) reads the value from analog pin 0, which isn’t connected to anything
// That value changes a lot because of tiny electrical noise
// Starting with this value makes the random numbers less predictable each time the program runs
randomSeed(analogRead(0));
// Welcome message with LED animation
lcd.print("Helloooo!");
playTone(NOTE_START_GAME, 300);
// Blink all LEDs 3 times as a welcome animation
for (int i = 0; i < 3; i++) {
digitalWrite(redLeds[0], HIGH);
digitalWrite(redLeds[1], HIGH);
digitalWrite(greenLed, HIGH);
delay(200);
digitalWrite(redLeds[0], LOW);
digitalWrite(redLeds[1], LOW);
digitalWrite(greenLed, LOW);
delay(200);
}
lcd.setCursor(0, 1);
lcd.print("Wait for GREEN!");
delay(2500);
lcd.clear();
}
// Play a tone for a specified duration (in ms)
void playTone(int note, int duration) {
tone(buzzerPin, note, duration);
}
void loop() {
// Ensure all LEDs are off at the start of each round
lcd.clear();
digitalWrite(redLeds[0], LOW);
digitalWrite(redLeds[1], LOW);
digitalWrite(greenLed, LOW);
greenLit = false;
// Clear the LCD and inform the player to wait for a light
lcd.setCursor(0, 0);
lcd.print("Wait for light...");
// Wait for a random time between 1 and 2 seconds before lighting any LED
delay(random(1000, 2000));
// Generate a random number: 0, 1, or 2 to choose which LED to light up
// The sequence depends on the seed set earlier by randomSeed()
// Without calling randomSeed(), the random numbers would be the same every time
// you reset or power on the Arduino
// Using an unpredictable seed (like analog noise) helps create different random sequences
int choice = random(3);
// Loop until the green LED is selected
// This prevents the green LED from lighting on the first try
while (choice != 1) {
// If red LED 1 or red LED 2 is selected
int redIndex;
if (choice == 0) {
redIndex = 0;
}
else {
redIndex = 1;
}
digitalWrite(redLeds[redIndex], HIGH); // Turn on the selected red LED
// For 1 second, check if the user presses the button too early while red LED is on
unsigned long startTime = millis();
bool pressedEarly = false;
while (millis() - startTime < 1000) {
if (digitalRead(buttonPin) == LOW) {
playTone(NOTE_TOO_EARLY, 500);
// If button pressed too early, display a message and wait 2 seconds for the user to see it
lcd.clear();
lcd.print("Too early :(");
delay(2000);
pressedEarly = true;
break; // Exit this inner while loop to continue game
}
}
digitalWrite(redLeds[redIndex], LOW); // Turn off the red LED after 1 second
// Restart round if user pressed too early
if (pressedEarly) {
return;
}
// Pick a new random LED to light up again, looping until green LED is selected
choice = random(3);
}
// Once green LED is chosen, turn it on and record the time it lit up
digitalWrite(greenLed, HIGH);
greenStartTime = millis();
greenLit = true;
// Prompt the user to press the button only when the green LED is lit
lcd.clear();
lcd.print("Press ONLY green");
bool buttonPressed = false;
unsigned long waitStart = millis();
// Wait max 5 seconds for user to react
while (millis() - waitStart < 5000) {
if (digitalRead(buttonPin) == LOW) {
buttonPressed = true;
playTone(NOTE_CORRECT, 400);
break;
}
}
digitalWrite(greenLed, LOW); // Turn off green LED after response
lcd.clear();
if (!buttonPressed) {
// If the player failed to press the button within 5 seconds
lcd.print("Too Slow!");
playTone(NOTE_TOO_SLOW, 400);
delay(2000);
return; // Skip rest if no button was pressed
}
// Calculate the reaction time in milliseconds from green LED lighting to button press
unsigned long reactionTime = millis() - greenStartTime;
// Display the reaction time on the LCD for 2 seconds
lcd.print("Reaction:");
lcd.setCursor(0, 1);
lcd.print(reactionTime);
lcd.print(" ms");
delay(2000);
// Track best reaction time and update if current is better
if (reactionTime < bestReactionTime) {
bestReactionTime = reactionTime;
}
attempts++;
// Display best reaction time and attempts so far
lcd.clear();
lcd.print("Best: ");
lcd.print(bestReactionTime);
lcd.print(" ms");
lcd.setCursor(0, 1);
lcd.print("Tries: ");
lcd.print(attempts);
delay(3000);
}