/*
One-Bit Reflex — Catch the Moment
Arduino Nano / Wokwi Reaction Game
------------------------------------------------------------
PURPOSE
------------------------------------------------------------
This is an upgraded version of the "Catch the Clock" game.
Original idea:
555 clock + 74LS00 NAND latch = one-bit memory game
Software version:
Arduino/Nano clock + logic + one stored bit = reaction game
The machine waits, gives a GO signal, checks the PLAY button,
remembers success, and reports the reaction time.
------------------------------------------------------------
WIRING
------------------------------------------------------------
D8 -> RED LED anode through 220/330 ohm resistor
RED LED cathode -> GND
D9 -> GREEN LED anode through 220/330 ohm resistor
GREEN LED cathode -> GND
D10 -> buzzer/speaker +
buzzer/speaker - -> GND
D2 -> PLAY button -> GND
D3 -> RESET/START button -> GND
Buttons use INPUT_PULLUP:
not pressed = HIGH
pressed = LOW
------------------------------------------------------------
GAME RULES
------------------------------------------------------------
1. Press RESET/START to arm a new round.
2. Wait. Do not press PLAY early.
3. When the RED LED turns ON, press PLAY as fast as you can.
4. If PLAY is pressed after RED turns ON:
GREEN LED latches ON.
Serial monitor prints YOU WIN and reaction time.
5. If PLAY is pressed before RED turns ON:
FALSE START.
6. Press RESET/START to play again.
------------------------------------------------------------
CONCEPTUAL MEANING
------------------------------------------------------------
RED LED = GO signal / clock event
PLAY = human input
logic = decides whether PLAY counts
GREEN LED = latched memory bit Q
RESET = clears memory and starts a new round
Q = 0 means no successful reaction stored.
Q = 1 means success has been latched.
*/
const int TARGET_LED = 8; // Red GO/target LED
const int SUCCESS_LED = 9; // Green latched-success LED
const int BUZZER_PIN = 10; // Buzzer/speaker
const int PLAY_BTN = 2; // PLAY button to GND
const int RESET_BTN = 3; // RESET/START button to GND
// Random wait before GO signal.
// Adjust these to make the game easier or harder.
const unsigned long MIN_WAIT_MS = 1500;
const unsigned long MAX_WAIT_MS = 5000;
// Serial status update while waiting.
const unsigned long STATUS_PERIOD_MS = 1000;
// Game states.
enum GameState {
IDLE, // Waiting for RESET/START
ARMED, // Round started, waiting before GO
GO_SIGNAL, // Red LED is ON; PLAY can win
WIN, // Green LED latched ON
FALSE_START // PLAY was pressed too early
};
GameState gameState = IDLE;
// One-bit memory: this is the software equivalent of latch Q.
bool latchQ = false;
// Previous button states for press detection.
bool lastPlayPressed = false;
bool lastResetPressed = false;
// Timing variables.
unsigned long armTime = 0;
unsigned long goTime = 0;
unsigned long waitDelay = 0;
unsigned long lastStatus = 0;
// ------------------------------------------------------------
// Button helpers
// ------------------------------------------------------------
bool playPressed() {
// INPUT_PULLUP: pressed = LOW
return digitalRead(PLAY_BTN) == LOW;
}
bool resetPressed() {
// INPUT_PULLUP: pressed = LOW
return digitalRead(RESET_BTN) == LOW;
}
// ------------------------------------------------------------
// Sound helpers
// ------------------------------------------------------------
void soundStart() {
tone(BUZZER_PIN, 523, 80); // C5
delay(100);
tone(BUZZER_PIN, 659, 80); // E5
delay(100);
noTone(BUZZER_PIN);
}
void soundGo() {
tone(BUZZER_PIN, 880, 120); // A5
delay(140);
noTone(BUZZER_PIN);
}
void soundWin() {
tone(BUZZER_PIN, 784, 120); // G5
delay(140);
tone(BUZZER_PIN, 988, 120); // B5
delay(140);
tone(BUZZER_PIN, 1175, 220); // D6
delay(240);
noTone(BUZZER_PIN);
}
void soundFalseStart() {
tone(BUZZER_PIN, 220, 250); // A3 low warning
delay(280);
noTone(BUZZER_PIN);
}
// ------------------------------------------------------------
// Serial helpers
// ------------------------------------------------------------
void printLine() {
Serial.println(F("--------------------------------------------------"));
}
void printState(const char *label) {
Serial.print(label);
Serial.print(F(" | state="));
switch (gameState) {
case IDLE: Serial.print(F("IDLE")); break;
case ARMED: Serial.print(F("ARMED")); break;
case GO_SIGNAL: Serial.print(F("GO")); break;
case WIN: Serial.print(F("WIN")); break;
case FALSE_START: Serial.print(F("FALSE_START")); break;
}
Serial.print(F(" | Q="));
Serial.print(latchQ ? 1 : 0);
Serial.print(F(" | PLAY="));
Serial.print(playPressed() ? 1 : 0);
Serial.print(F(" | RESET="));
Serial.print(resetPressed() ? 1 : 0);
Serial.print(F(" | RED="));
Serial.print(digitalRead(TARGET_LED) ? 1 : 0);
Serial.print(F(" | GREEN="));
Serial.println(digitalRead(SUCCESS_LED) ? 1 : 0);
}
void printInstructions() {
Serial.println();
printLine();
Serial.println(F("ONE-BIT REFLEX — CATCH THE MOMENT"));
printLine();
Serial.println(F("Rule:"));
Serial.println(F(" Press RESET/START to arm the game."));
Serial.println(F(" Wait for the RED LED to turn ON."));
Serial.println(F(" Press PLAY as fast as possible after RED turns ON."));
Serial.println();
Serial.println(F("Meaning:"));
Serial.println(F(" RED = GO signal"));
Serial.println(F(" GREEN = latched success bit Q"));
Serial.println(F(" Q=0 = no success stored"));
Serial.println(F(" Q=1 = successful reaction captured"));
Serial.println();
Serial.println(F("Buttons use INPUT_PULLUP:"));
Serial.println(F(" not pressed = HIGH"));
Serial.println(F(" pressed = LOW"));
printLine();
Serial.println();
}
// ------------------------------------------------------------
// Game control
// ------------------------------------------------------------
void clearOutputs() {
digitalWrite(TARGET_LED, LOW);
digitalWrite(SUCCESS_LED, LOW);
noTone(BUZZER_PIN);
}
void startRound() {
latchQ = false;
clearOutputs();
waitDelay = random(MIN_WAIT_MS, MAX_WAIT_MS + 1);
armTime = millis();
lastStatus = millis();
gameState = ARMED;
printLine();
Serial.println(F("NEW ROUND ARMED"));
Serial.print(F("Wait delay selected: "));
Serial.print(waitDelay);
Serial.println(F(" ms"));
Serial.println(F("Do not press PLAY until RED turns ON."));
printState("ARMED");
printLine();
soundStart();
}
void triggerGoSignal() {
gameState = GO_SIGNAL;
goTime = millis();
digitalWrite(TARGET_LED, HIGH);
Serial.println();
Serial.println(F("**************** GO! ****************"));
Serial.println(F("RED is ON. Press PLAY now!"));
printState("GO_SIGNAL");
Serial.println(F("*************************************"));
Serial.println();
soundGo();
}
void winRound() {
unsigned long reactionTime = millis() - goTime;
latchQ = true;
gameState = WIN;
digitalWrite(SUCCESS_LED, HIGH);
Serial.println();
Serial.println(F("*************************************"));
Serial.println(F("* YOU WIN! *"));
Serial.println(F("* Reaction captured into Q = 1 *"));
Serial.println(F("*************************************"));
Serial.print(F("Reaction time: "));
Serial.print(reactionTime);
Serial.println(F(" ms"));
printState("WIN");
Serial.println(F("Press RESET/START for another round."));
Serial.println();
soundWin();
}
void falseStart() {
latchQ = false;
gameState = FALSE_START;
digitalWrite(TARGET_LED, LOW);
digitalWrite(SUCCESS_LED, LOW);
Serial.println();
Serial.println(F("XXXXXXXXXXXX FALSE START XXXXXXXXXXXX"));
Serial.println(F("PLAY was pressed before RED turned ON."));
Serial.println(F("Q remains 0. Press RESET/START to try again."));
printState("FALSE_START");
Serial.println(F("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"));
Serial.println();
soundFalseStart();
}
// ------------------------------------------------------------
// Arduino setup
// ------------------------------------------------------------
void setup() {
pinMode(TARGET_LED, OUTPUT);
pinMode(SUCCESS_LED, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(PLAY_BTN, INPUT_PULLUP);
pinMode(RESET_BTN, INPUT_PULLUP);
Serial.begin(115200);
delay(300);
// Seed pseudo-random wait from an unconnected analog input.
randomSeed(analogRead(A0));
clearOutputs();
gameState = IDLE;
latchQ = false;
printInstructions();
Serial.println(F("System ready."));
Serial.println(F("Press RESET/START to begin."));
printState("START");
lastPlayPressed = playPressed();
lastResetPressed = resetPressed();
}
// ------------------------------------------------------------
// Arduino main loop
// ------------------------------------------------------------
void loop() {
unsigned long now = millis();
bool playNow = playPressed();
bool resetNow = resetPressed();
bool playEdge = playNow && !lastPlayPressed;
bool resetEdge = resetNow && !lastResetPressed;
// RESET/START always starts a fresh round.
if (resetEdge) {
startRound();
}
// State machine.
switch (gameState) {
case IDLE:
// Waiting for RESET/START.
if (playEdge) {
Serial.println();
Serial.println(F("PLAY ignored. Press RESET/START to arm the game first."));
printState("IDLE_PLAY_IGNORED");
}
break;
case ARMED:
// During ARMED, pressing PLAY is too early.
if (playEdge) {
falseStart();
break;
}
// Periodic waiting message.
if (now - lastStatus >= STATUS_PERIOD_MS) {
lastStatus = now;
Serial.println(F("Waiting..."));
printState("ARMED_WAIT");
}
// Time to turn on RED.
if (now - armTime >= waitDelay) {
triggerGoSignal();
}
break;
case GO_SIGNAL:
// PLAY after GO is a valid reaction.
if (playEdge) {
winRound();
}
break;
case WIN:
// Q remains latched until RESET/START.
if (playEdge) {
Serial.println(F("Already won. Q is latched at 1. Press RESET/START for a new round."));
printState("WIN_PLAY_IGNORED");
}
break;
case FALSE_START:
// Wait for RESET/START.
if (playEdge) {
Serial.println(F("False start already recorded. Press RESET/START to try again."));
printState("FALSE_START_PLAY_IGNORED");
}
break;
}
lastPlayPressed = playNow;
lastResetPressed = resetNow;
delay(10);
}