Skip to main contentSkip to footer

Build the “Mind Clock” Challenge: A 2-Player Arduino Game

No Comments

If you are looking for a project that transitions you from simply blinking an LED to building a fully interactive device, you have found it. The Mind Clock Challenge is a 2-player game where participants compete to guess exactly when a specific amount of time has passed.

This project is fantastic for beginners and STEM teachers because it combines inputs (buttons), outputs (LEDs), and visual data (an OLED screen) using a programming concept called a “state machine.”

You can also watch the video below on how to build the project:

The Hardware Connections

Here is exactly how to wire the components. Notice that we do not use resistors for the buttons! We are using a clever software trick called INPUT_PULLUP to use the Arduino’s built-in resistors instead.

Component Leg/Pin Connects to Arduino
Start Button One leg Pin 3
Other leg GND
Player A Button One leg Pin 2
Other leg GND
Player B Button One leg Pin 4
Other leg GND
Player A LED Anode (+) Pin 5
Cathode (-) GND (via 220Ω resistor)
Start LED Anode (+) Pin 6
Cathode (-) GND (via 220Ω resistor)
Player B LED Anode (+) Pin 7
Cathode (-) GND (via 220Ω resistor)
OLED Screen VCC 5V
GND GND
SCL A5
SDA A4

How the OLED Screen Works

The 0.96″ OLED screen used in this project is incredibly powerful for its size. It uses a communication protocol called I2C (Inter-Integrated Circuit).

Instead of needing 8 or 10 wires to control the thousands of pixels on the screen, I2C only requires two data wires:

  1. SDA (Serial Data): This wire carries the actual data instructions (like “turn on this pixel”).

  2. SCL (Serial Clock): This wire keeps the Arduino and the screen synchronized so they understand each other perfectly.

We use the Adafruit_SSD1306 library to make controlling this screen as easy as printing text to a computer monitor.

The Code Explained

Here is the complete, heavily-commented code. There are a few core concepts at play here:

  • State Machine: The variable gameState tracks if we are waiting (0), playing (1), or looking at results (2). This keeps our code organized.

  • millis(): Instead of using delay() which pauses the entire Arduino, we use millis() (the Arduino’s internal stopwatch) to track time. This allows the Arduino to keep watching for button presses while the clock runs!

C++

// Include the libraries needed to communicate with the OLED screen
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Set the resolution of our screen
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// Initialize the OLED display using the I2C protocol
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Define exactly which pins our hardware is connected to
const int btnStart = 3;
const int btnPlayerA = 2;
const int btnPlayerB = 4;
const int ledA = 5;
const int ledStart = 6; 
const int ledB = 7;

// Variables to track time using the Arduino's internal clock
unsigned long startTime = 0;
unsigned long lastDisplayUpdate = 0; 
float timeA = 0.0;
float timeB = 0.0;

// Variables to remember if a player has already pressed their button
bool pressedA = false;
bool pressedB = false;

// Game states: 
// 0 = Waiting to start
// 1 = Game running
// 2 = Game over (showing results)
int gameState = 0; 

void setup() {
  // INPUT_PULLUP turns on the Arduino's internal resistors.
  // This means the pins will read HIGH normally, and LOW when the button is pressed.
  pinMode(btnStart, INPUT_PULLUP);
  pinMode(btnPlayerA, INPUT_PULLUP);
  pinMode(btnPlayerB, INPUT_PULLUP);
  
  // Set the LEDs so they can send power OUT
  pinMode(ledA, OUTPUT);
  pinMode(ledB, OUTPUT);
  pinMode(ledStart, OUTPUT); 

  // Boot up the OLED screen. 0x3C is the standard address for these screens.
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    for(;;); // If the screen isn't found, freeze the program here forever
  }
  
  showStartScreen();
}

void loop() {
  // ---------------------------------------------------------
  // STATE 0: Waiting for the referee to press START
  // ---------------------------------------------------------
  if (gameState == 0) {
    if (digitalRead(btnStart) == LOW) { // LOW means pressed!
      gameState = 1;         
      startTime = millis();  // Save the exact millisecond the game started
      pressedA = false;
      pressedB = false;
      
      // Turn off player LEDs and turn ON the Start indicator LED
      digitalWrite(ledA, LOW);
      digitalWrite(ledB, LOW);
      digitalWrite(ledStart, HIGH); 
      
      delay(300); // A small delay so a long button press doesn't trigger things twice
    }
  } 
  
  // ---------------------------------------------------------
  // STATE 1: Game is currently running!
  // ---------------------------------------------------------
  else if (gameState == 1) {
    
    // If Player A presses their button AND hasn't pressed it yet...
    if (!pressedA && digitalRead(btnPlayerA) == LOW) {
      // Calculate how many seconds have passed since startTime
      timeA = (millis() - startTime) / 1000.0; 
      pressedA = true;                         
      digitalWrite(ledA, HIGH); // Light up their LED to confirm!               
    }
    
    // If Player B presses their button AND hasn't pressed it yet...
    if (!pressedB && digitalRead(btnPlayerB) == LOW) {
      timeB = (millis() - startTime) / 1000.0; 
      pressedB = true;                         
      digitalWrite(ledB, HIGH);                
    }

    // UPDATE THE SCREEN LIVE
    // We only update the screen every 100 milliseconds. If we update it 
    // constantly, the Arduino might miss a button press!
    if (millis() - lastDisplayUpdate >= 100) {
      lastDisplayUpdate = millis();
      float runningTime = (millis() - startTime) / 1000.0;
      
      display.clearDisplay();
      display.setTextSize(2);
      display.setTextColor(WHITE);
      display.setCursor(30, 25);
      display.print(runningTime, 1); // The '1' tells it to show 1 decimal place
      display.println(" s");
      display.display();
    }

    // Check if BOTH players have locked in their guesses
    if (pressedA && pressedB) {
      gameState = 2; // Move to Game Over state
      digitalWrite(ledStart, LOW); // Turn off the Start LED
      showResults();
      delay(500); // Wait a half second before allowing a restart
    }
  } 
  
  // ---------------------------------------------------------
  // STATE 2: Game Over, showing results
  // ---------------------------------------------------------
  else if (gameState == 2) {
    // Wait for the start button to be pressed again to reset the game back to State 0
    if (digitalRead(btnStart) == LOW) {
      gameState = 0;
      showStartScreen();
      delay(300); // Debounce
    }
  }
}

// ---------------------------------------------------------
// HELPER FUNCTIONS (To keep the main loop clean)
// ---------------------------------------------------------

// Function to draw the start screen
void showStartScreen() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(5, 10);
  display.println("MIND CLOCK CHALLENGE");
  display.setCursor(0, 40);
  display.println("Ready for new mode!");
  display.setCursor(0, 50);
  display.println("Press START to begin");
  display.display();
}

// Function to draw the final results screen
void showResults() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(30, 5);
  display.println("RESULTS:");
  
  display.setTextSize(2); // Make the final numbers big and easy to read
  
  display.setCursor(0, 20);
  display.print("A: ");
  display.print(timeA);
  display.println("s");

  display.setCursor(0, 45);
  display.print("B: ");
  display.print(timeB);
  display.println("s");
  
  display.display();
}
Previous Post
Build an Easy Color-Coded DIY Study Timer with Arduino (No Soldering)
Next Post
Send Love Over Wi-Fi ❤️ Build an “I Miss You” Lamp With ESP32

Latest Updates