Build a memory game with a joystick

Watch the lights, then repeat the pattern with the joystick. The pattern grows by one step each round, and the UNO remembers your high score.
  • Level:Intermediate
  • Duration: 1 h 20 min
  • Ages 10+
  • Platform: Arduino

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Build a memory game with a joystick – EEMENTOR STEM™ lesson

In this capstone you build a complete game: the classic memory game where the machine shows a pattern of lights and sounds, and you have to repeat it. Each round adds one more step and the game gets faster. Four LEDs stand for up, right, down and left, and you answer by pushing the joystick in the same direction. Your best score is stored in the UNO's permanent memory, so it's still there after you unplug it.

What you'll learn

  • How to store a growing sequence in an array
  • How to turn two analog joystick readings into four directions
  • How to play notes with a passive buzzer and tone()
  • How to keep a value in EEPROM when the power is off

What you need

  • UNO R3 board and USB cable (UNO R3 Starter Kit)
  • Joystick module (UNO R3 Starter Kit)
  • Red, yellow, green and white LEDs and 4 resistors, 220 Ω (UNO R3 Starter Kit)
  • Passive buzzer module (45-in-1 Sensor Kit)
  • Breadboard and jumper wires (UNO R3 Starter Kit)

Wiring

Place the four LEDs in a row. Later you can bend their legs or use longer wires to arrange them as a cross: up, right, down, left.

Breadboard wiring diagram: Build a memory game with a joystick
Breadboard view. Check each connection against the wiring table below.
From To
Joystick GND GND
Joystick +5V 5V
Joystick VRx UNO pin A0
Joystick VRy UNO pin A1
Joystick SW UNO pin 7
UNO pin 2 220 Ω resistor, then the red LED's long leg (up)
UNO pin 3 220 Ω resistor, then the yellow LED's long leg (right)
UNO pin 4 220 Ω resistor, then the green LED's long leg (down)
UNO pin 5 220 Ω resistor, then the white LED's long leg (left)
All LED short legs GND
Passive buzzer module S UNO pin 8
Passive buzzer module − GND

Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.

How to play

  1. After power-up the four LEDs chase each other. Press the joystick down (it clicks) to start.
  2. Watch and listen: the game shows the pattern.
  3. Repeat the pattern by pushing the joystick in each direction and letting it spring back.
  4. A correct answer adds one more step. A wrong direction, or waiting longer than five seconds, ends the game.
  5. The LEDs blink once for each point you scored. A new record plays a short tune.

The sketch

#include <EEPROM.h>

const int LED_PINS[4] = {2, 3, 4, 5};          // up, right, down, left
const int NOTES[4] = {262, 330, 392, 523};     // one note per direction (Hz)
const int BUZZER_PIN = 8;
const int X_PIN = A0;
const int Y_PIN = A1;
const int SW_PIN = 7;
const int MAX_STEPS = 50;

byte pattern[MAX_STEPS];
byte highScore = 0;

// Returns 0 = up, 1 = right, 2 = down, 3 = left, -1 = joystick in the middle
int readDirection() {
  int x = analogRead(X_PIN);
  int y = analogRead(Y_PIN);
  if (y < 250) return 0;
  if (x > 770) return 1;
  if (y > 770) return 2;
  if (x < 250) return 3;
  return -1;
}

void show(int direction, int ms) {
  digitalWrite(LED_PINS[direction], HIGH);
  tone(BUZZER_PIN, NOTES[direction]);
  delay(ms);
  digitalWrite(LED_PINS[direction], LOW);
  noTone(BUZZER_PIN);
}

// Waits up to 5 seconds for a push. Returns the direction, or -1 if time runs out.
int waitForMove() {
  unsigned long start = millis();
  while (millis() - start < 5000) {
    int direction = readDirection();
    if (direction >= 0) {
      digitalWrite(LED_PINS[direction], HIGH);
      tone(BUZZER_PIN, NOTES[direction]);
      while (readDirection() != -1) delay(10);   // wait until the stick is released
      delay(80);
      digitalWrite(LED_PINS[direction], LOW);
      noTone(BUZZER_PIN);
      return direction;
    }
  }
  return -1;
}

void allLeds(bool on) {
  for (int i = 0; i < 4; i++) digitalWrite(LED_PINS[i], on);
}

// Plays one game and returns the score
int playGame() {
  int steps = 0;
  while (steps < MAX_STEPS) {
    pattern[steps] = random(4);                  // add one new step
    steps++;

    int speed = max(150, 500 - steps * 25);      // each round is a little faster
    for (int i = 0; i < steps; i++) {
      show(pattern[i], speed);
      delay(speed / 3);
    }

    for (int i = 0; i < steps; i++) {
      if (waitForMove() != pattern[i]) return steps - 1;   // wrong or too slow
    }
    delay(700);
  }
  return MAX_STEPS;
}

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < 4; i++) pinMode(LED_PINS[i], OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(SW_PIN, INPUT_PULLUP);
  randomSeed(analogRead(A5));                    // a different pattern every time

  highScore = EEPROM.read(0);
  if (highScore > MAX_STEPS) highScore = 0;      // a new chip contains 255
  Serial.print(F("High score: "));
  Serial.println(highScore);
}

void loop() {
  // Attract mode: chase the LEDs until the joystick is pressed
  int i = 0;
  while (digitalRead(SW_PIN) == HIGH) {
    digitalWrite(LED_PINS[i], HIGH);
    delay(120);
    digitalWrite(LED_PINS[i], LOW);
    i = (i + 1) % 4;
  }
  delay(800);

  int score = playGame();
  Serial.print(F("Score: "));
  Serial.println(score);

  tone(BUZZER_PIN, 131, 600);                    // low "game over" note
  delay(900);
  for (int n = 0; n < score; n++) {              // one blink per point
    allLeds(true);
    delay(180);
    allLeds(false);
    delay(180);
  }

  if (score > highScore) {
    highScore = score;
    EEPROM.write(0, highScore);                  // survives without power
    Serial.println(F("New high score!"));
    for (int n = 0; n < 3; n++) {
      for (int d = 0; d < 4; d++) show(d, 90);
    }
  }
  delay(1000);
}

How it works

The pattern lives in the array pattern. Each round, random(4) picks a number from 0 to 3 and stores it in the next free place. The game then replays the whole array from the start and checks your answers against it one by one. The old steps never change, which is why the pattern grows instead of being new each time.

The joystick is two potentiometers at right angles. In the middle, both read about 512. The sketch treats a reading below 250 or above 770 as a clear push and ignores everything in between, so small movements don't count. waitForMove() also waits for the stick to return to the middle before it accepts the next answer; otherwise one long push would count several times.

Computers can't produce truly random numbers by themselves. randomSeed(analogRead(A5)) reads the electrical noise on an unconnected pin and uses it as the starting point, so the pattern is different every time you play.

EEPROM is a small memory inside the UNO that keeps its contents without power. EEPROM.write(0, highScore) stores one byte at address 0. Each EEPROM cell can be rewritten about 100,000 times, so the sketch writes only when there's a new record.

Try this next

  • Make the joystick button during a game repeat the pattern once, as a "hint" that costs a point.
  • Hold the joystick button while plugging in to reset the high score to zero.
  • Add a two-player mode: players take turns, and each one adds a step of their own.
  • Show the score on the 4-digit display from the stopwatch project.

Troubleshooting

  • The wrong LED lights when you push the joystick: your joystick is turned differently. Either turn the module, or reorder the four pin numbers in LED_PINS until the directions match.
  • Up and down are swapped: swap y < 250 and y > 770 in readDirection(). Do the same with x for left and right.
  • The game starts by itself: check the SW wire on pin 7.
  • No sound: make sure you're using the passive buzzer module. An active buzzer only makes one fixed tone.
  • The pattern is the same after every restart: something is connected to A5. Leave that pin empty.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

UNO R3 Starter Kit | Arduino IDE Compatible | Free Tutorials $56.00

Shop the kit