arduino beginner 30 min

Arduino: Simon-says memory game

Build the Simon memory game with 4 buttons, 4 LEDs, and a buzzer. State machines, tone(), and the game loop that scales to any difficulty.

Code available for: Arduino CESP32 Arduino
Published Sep 22, 2026

The Simon game is four buttons, four LEDs, one buzzer, and a growing sequence the player must repeat. It is the project I hand people who have blinked an LED and want to build something that feels like a real product (e.g. it has input handling, game states, and a win/lose path), and it fits on one Uno.

This version uses tone() for musical notes, INPUT_PULLUP buttons, and a state machine clean enough to extend.

What you need

  • Arduino Uno
  • 4x LEDs (any four colors) + 4x 220 ohm resistors
  • 4x momentary pushbuttons (the tactile 6mm ones)
  • A piezo buzzer (passive, not the “just add 5V” active kind; passive is what tone() drives)
  • Breadboard and wires

Wiring

Wire key: D-pinGND
ComponentPin
LED 1-4D2, D3, D4, D5
Button 1-4D6, D7, D8, D9
Piezo +D11
Piezo -GND

LEDs through their 220-ohm resistors to GND. Buttons use the internal pull-ups (wired pin-to-GND, pressed = LOW).

The code

const int LEDS[4]  = {2, 3, 4, 5};
const int BUTTONS[4] = {6, 7, 8, 9};
const int PIEZO = 11;
const int NOTES[4] = {262, 330, 392, 523};   // C, E, G, C (the real Simon notes)

int sequence[100];
int seqLen = 0;
const int MAX_LEN = 100;

void setup() {
  for (int i = 0; i < 4; i++) {
    pinMode(LEDS[i], OUTPUT);
    pinMode(BUTTONS[i], INPUT_PULLUP);
  }
  pinMode(PIEZO, OUTPUT);
  randomSeed(analogRead(A0));   // floating pin: real randomness
  Serial.begin(115200);
}

void playRound() {
  sequence[seqLen++] = random(0, 4);
  for (int i = 0; i < seqLen; i++) {
    show(sequence[i], 400);
  }
}

void show(int idx) {
  digitalWrite(LEDS[idx], HIGH);
  tone(PIEZO, NOTES[idx], 350);
  delay(400);
  digitalWrite(LEDS[idx], LOW);
  delay(150);
}

bool getPlayerInput() {
  for (int i = 0; i < seqLen; i++) {
    int pressed = waitForButton();
    digitalWrite(LEDS[pressed], HIGH);
    tone(PIEZO, NOTES[pressed], 350);
    delay(250);
    digitalWrite(LEDS[pressed], LOW);
    if (pressed != sequence[i]) return false;   // wrong button
  }
  return true;
}

int waitForButton() {
  while (true) {
    for (int b = 0; b < 4; b++) {
      if (digitalRead(BUTTONS[b]) == LOW) {
        delay(30);                       // debounce settle
        while (digitalRead(BUTTONS[b]) == LOW) {}   // wait for release
        return b;
      }
    }
    delay(10);
  }
}

void winSound() {
  tone(PIEZO, 523, 150); delay(180);
  tone(PIEZO, 659, 150); delay(180);
  tone(PIEZO, 784, 250); delay(300);
}

void loseSound() {
  tone(PIEZO, 200, 300); delay(350);
  tone(PIEZO, 150, 500); delay(550);
}

void loop() {
  seqLen = 0;
  Serial.println("New game. Watch...");
  delay(1000);

  while (true) {
    playRound();        // show the sequence so far
    if (!getPlayerInput()) {
      loseSound();
      Serial.print("You reached round ");
      Serial.println(seqLen);
      break;
    }
    winSound();
    delay(800);
  }
}

Why this is a real project in disguise

Strip the game skin off and you have three transferable patterns:

  • Polling input with debounce (waitForButton): the press-and-wait- for-release pattern is what every real device button wants.
  • A bounded shared array (sequence[100]): the embedded version of “a list”, with a hard cap and no dynamic allocation.
  • Game states via loop structure (round loop inside game loop): the same shape as the traffic light’s state machine, just with a human on the other end.

The tone() trick deserves a note: one piezo, four notes, zero extra parts. The speaker is playing square waves at 262/330/392/523 Hz, which is why real Simon sounds exactly like this (the original game used the same four notes: C, E, G, high C).

The speed-up variant

Real Simon gets faster every round. Add it in one line:

int pause = max(400 - seqLen * 10, 120);   // floor of 120 ms
delay(pause);

By round 20 the sequence is a blur, which is the point.

What you learned

  • tone() drives a passive piezo with any frequency; four notes, four LEDs, one game.
  • INPUT_PULLUP + debounce + wait-for-release is the complete button.
  • The game loop inside a game loop is a state machine in practice.

When something breaks

  • Buzzer clicks but no notes: you bought an active buzzer (has a sticker saying “active” or a built-in oscillator). tone() still runs but you hear one fixed pitch. Passive piezo is the part to buy.
  • Random sequence identical every reset: randomSeed on a pinned or wired pin. A0 floating is the trick above; a truly fixed seed gives the same game every time.
  • Button registers twice: debounce. The 30 ms settle plus wait-for-release above covers most cheap tactile buttons.
  • ** LEDs dim or buttons ghost on a long breadboard**: shared thin power rails. Feed the LED rail from a second 5V point or a bigger breadboard’s power bus.

What to build next

  • The electronic dice tutorial reuses buttons + display for a different game.
  • The traffic light tutorial formalizes the state machine here.
  • Put the sequence in EEPROM (the EEPROM tutorial) and add a high-score that survives power.