Community project

Arduino Reaction-Time Game

ESP32
Photo of Arduino Reaction-Time Game
Generated with AI

Tamzid Rahman

Last updated August 11, 2026

Build a reaction-time game that tests how quickly you can press a button after an LED lights up. The ESP32 microcontroller runs the game logic, measuring the time between the visual cue and your button press, then displaying your result with a blinking LED pattern.

This guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions. You'll install the LED with its current-limiting resistor, connect the momentary pushbutton, wire the USB-C power supply, and upload the firmware to your ESP32. Once assembled, the device is ready to play—press the button to start each round and see how fast your reflexes are.

Wiring diagram

Wiring diagram for Arduino Reaction-Time Game

Gather all the parts

QtyComponent
1

LED

Red, 220 Ω series resistor

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

Momentary pushbutton

Normally-open tactile switch

Normally-open tactile pushbutton for the reaction input, read with the Arduino internal pull-up resistor.

1

USB-C 5V Adapter

5 V USB-C wall adapter

USB-C wall adapter delivering regulated 5 V to the board's USB or VBUS rail. Default wired power source for desktop / stationary projects.

1

Resistor

220 Ω

Through-hole resistor (current-limiting in series with an LED)

Assemble it in 4 steps

1. Install the LED and its resistor

Place the red LED on the breadboard. Connect Metro GPIO 13 to one end of the separate 220 Ω resistor. Connect the resistor’s other end to the LED long leg (anode). Connect the LED short leg (cathode, usually beside the flat edge) to a Metro GND pin.

  • The Metro's 3.3 V GPIO output is suitable for a red LED with a 220 Ω resistor.
  • The board's built-in LED may also indicate the cue if it is tied to the same GPIO.
  • The 220 Ω resistor must be in series with the LED; never wire the LED directly from GPIO 13 to GND.

2. Wire the reaction button

Mount the normally-open tactile button across the breadboard center gap. Connect one side of the switch to Metro GPIO 2 and the opposite side to GND. The firmware enables the internal pull-up, so no external resistor is needed.

  • The input is normally HIGH and reads LOW only while the button is pressed.
  • On a four-leg tactile switch, use terminals from opposite internally connected pairs.
  • Do not connect the button to 5 V; this project expects the button to connect GPIO 2 to GND.

3. Connect the wall power supply

Use a certified regulated 5 V USB-C wall adapter and USB-C cable to plug the wall_adapter directly into the Metro ESP32-S3 USB-C port. The board regulates this input for its own electronics.

  • Choose a quality adapter rated for at least 0.5 A; the game normally draws about 100 mA.
  • Keep the USB-C cable connected while operating the game.
  • Use only a regulated 5 V USB-C power adapter. Do not connect an unregulated adapter, mains wiring, or a higher-voltage supply to the board USB-C port.

4. Final connection check

Before powering the board, verify: GPIO 13 reaches the LED only through the 220 Ω resistor; the LED cathode is at GND; GPIO 2 reaches one button side; and the opposite button side is at GND. Then apply wall power.

  • After using Schematik's Deploy button, view the Serial output at 115200 baud for round messages, false starts, and reaction times.
  • Release the button after the three LED result blinks so the next round can arm.
  • Do not power the Metro from two different sources at once unless they are designed to share USB power.

Review all connections

1. Connections between "led_resistor" and "ESP32"

Functionled_resistorESP32
digitalP1GPIO 13
digitalP2LED ANODEEXT

2. Connections between "red_led" and "ESP32"

Functionred_ledESP32
groundGNDGND

3. Connections between "reaction_button" and "ESP32"

Functionreaction_buttonESP32
digitalSW1GPIO 2
groundSW2GND

4. Connections between "wall_adapter" and "ESP32"

Functionwall_adapterESP32
power+5VAdafruit Metro ESP32-S3 USB-C power portEXT
groundGNDAdafruit Metro ESP32-S3 USB-C power returnEXT

Deploy the firmware

#include <Arduino.h>


// Hoisted type definitions
enum GameState {
  WAIT_FOR_RELEASE,
  WAITING_FOR_CUE,
  TIMING_REACTION,
  BLINKING_RESULT
};


// Forward declarations
bool buttonPressedEvent();
void startWaitingRound();
void startResultBlink();

constexpr uint8_t LED_PIN = 13;
constexpr uint8_t BUTTON_PIN = 2;
constexpr uint32_t DEBOUNCE_MS = 30;
constexpr uint32_t MIN_WAIT_MS = 2000;
constexpr uint32_t MAX_WAIT_MS = 5000;
constexpr uint32_t BLINK_INTERVAL_MS = 200;



GameState gameState = WAIT_FOR_RELEASE;
bool rawButtonState = HIGH;
bool stableButtonState = HIGH;
uint32_t lastRawChangeMs = 0;
uint32_t waitStartedMs = 0;
uint32_t randomDelayMs = 0;
uint32_t cueStartedMs = 0;
uint32_t blinkChangedMs = 0;
uint8_t blinkTransitions = 0;

bool buttonPressedEvent() {
  const bool reading = digitalRead(BUTTON_PIN);
  const uint32_t now = millis();

  if (reading != rawButtonState) {
    rawButtonState = reading;
    lastRawChangeMs = now;
  }

  if ((now - lastRawChangeMs) >= DEBOUNCE_MS && stableButtonState != rawButtonState) {
    stableButtonState = rawButtonState;
    return stableButtonState == LOW;
  }
  return false;
}

void startWaitingRound() {
  digitalWrite(LED_PIN, LOW);
  randomDelayMs = random(MIN_WAIT_MS, MAX_WAIT_MS + 1);
  waitStartedMs = millis();
  gameState = WAITING_FOR_CUE;
  Serial.println(F("Round started: wait for the LED."));
}

void startResultBlink() {
  digitalWrite(LED_PIN, LOW);
  blinkTransitions = 0;
  blinkChangedMs = millis();
  gameState = BLINKING_RESULT;
}

void setup() {
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  Serial.begin(115200);
  randomSeed(micros());
  Serial.println(F("Reaction-time game ready. Release the button to begin."));
}

void loop() {
  const uint32_t now = millis();
  const bool pressed = buttonPressedEvent();

  switch (gameState) {
    case WAIT_FOR_RELEASE:
      digitalWrite(LED_PIN, LOW);
      if (stableButtonState == HIGH) {
        startWaitingRound();
      }
      break;

    case WAITING_FOR_CUE:
      if (pressed) {
        Serial.println(F("False start! You pressed before the LED."));
        startResultBlink();
      } else if ((now - waitStartedMs) >= randomDelayMs) {
        digitalWrite(LED_PIN, HIGH);
        cueStartedMs = now;
        gameState = TIMING_REACTION;
        Serial.println(F("GO!"));
      }
      break;

    case TIMING_REACTION:
      if (pressed) {
        Serial.print(F("Reaction time: "));
        Serial.print(now - cueStartedMs);
        Serial.println(F(" ms"));
        startResultBlink();
      }
      break;

    case BLINKING_RESULT:
      if ((now - blinkChangedMs) >= BLINK_INTERVAL_MS) {
        blinkChangedMs = now;
        digitalWrite(LED_PIN, !digitalRead(LED_PIN));
        if (++blinkTransitions >= 6) {
          digitalWrite(LED_PIN, LOW);
          gameState = WAIT_FOR_RELEASE;
          Serial.println(F("Release the button for the next round."));
        }
      }
      break;
  }
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik