Arduino Tutorial 20 - Reflex Tester

In this project, we're gonna make a fun reaction speed game with Arduino! You'll score points by pressing a button as fast as you can when an LED lights up. The game lasts just one minute, so get ready to put your reflexes to the test! Let's roll up our sleeves and get started!

 

COMPONENT LIST

 

HARDWARE

For this project, we'll be reusing the LED lights and button modules from previous endeavors. One key difference, though, is that instead of using an external pull-down resistor on the button like in our 'Traffic Signal Light' project, we'll leverage the internal pull-up resistors found on the Arduino chip. Be sure to check out the hardware review section for details. Now, let's connect everything correctly according to the diagram below. 

fig 1 Reflex Tester Circuit

 

CODE

Sample code:

CODE
// Project - Reflex Tester
// Define pin numbers for LEDs and buttons  
const int LEDS[] = {3, 5, 7};  
const int BUTTONS[] = {2, 4, 6};  
  
int score = 0; // Player's score  
bool gameStarted = false; // Flag indicating if the game has started  
unsigned long gameStartTime; 
// Timestamp to record when the game starts  
const unsigned long gameDuration = 60000; 
// Duration of the game in milliseconds (1 minute)  
  
void setup() {  
  Serial.begin(9600); // Initialize serial communication  
  // Set pin modes for LEDs and buttons  
  for (int i = 0; i < 3; i++) {  
    pinMode(LEDS[i], OUTPUT);  
    pinMode(BUTTONS[i], INPUT_PULLUP);  
  }  
}  
  
void loop() {  
  /* Check if all three buttons are pressed simultaneously to start the game  */
  if (!gameStarted && digitalRead(BUTTONS[0]) == LOW && digitalRead(BUTTONS[1]) == LOW && digitalRead(BUTTONS[2]) == LOW) {  
    Serial.println("Game Start");  
    gameStarted = true;  
    score = 0; // Reset score  
    gameStartTime = millis(); // Record the start time of the game  
    delay(1000); // Wait for a second before starting the game  
  }  
  
  // If the game has started, check if the game has timed out  
  if (gameStarted && millis() - gameStartTime < gameDuration) {  
    int led = random(0, 3); // Randomly select an LED  
    digitalWrite(LEDS[led], HIGH); // Turn on the selected LED  
long startTime = millis(); 
// Record the time when the LED is turned on  
bool buttonPressed = false; 
// Set button pressed status to false  
      
    // Give the player some time to press the button  
    while (millis() - startTime < 1000) {  
      if (digitalRead(BUTTONS[led]) == LOW) { 
// Check if the corresponding button is pressed  
        buttonPressed = true;  
        break; // Break the loop if the button is pressed  
      }  
    }  
  
    digitalWrite(LEDS[led], LOW); // Turn off the LED  
  
    if (buttonPressed) {  
      score++; // Increment score if the button is pressed  
      Serial.println("Score +1");  
    }  
delay(200); 
// Wait for a short period before continuing to the next round  
  } else if (gameStarted) {  
    /* If the game duration exceeds one minute or the player fails to press the button within time, the game ends  */
    gameStarted = false;  
    Serial.print("Game Over! Your Score: ");  
    Serial.println(score); // Output the score through serial  
delay(3000); 
// Wait for 3 seconds before allowing the game to be restarted  
  }  
}

After uploading the code, open the Serial Monitor and press all three buttons simultaneously to activate the game. You'll see the message "Game Start" appear on the Serial Monitor. Keep your eyes peeled for any LED to light up. When you spot one, you've got just one second to press the corresponding button to extinguish it and earn a point. Once the game clock strikes one minute, the Serial Monitor will display "Game Over" along with your final score.

 

 

CODE REVIEW


The variable declaration section will not be explained in detail here, as it primarily serves to define the pin arrays for the LEDs and buttons, along with variables used to track the score, game state, and game time.

 

const int LEDS[] = {3, 5, 7};  
const int BUTTONS[] = {2, 4, 6};  
 
int score = 0;  
bool gameStarted = false;  
unsigned long gameStartTime;   
const unsigned long gameDuration = 60000;

 

Among these variables, gameDuration is a non-negative long integer constant that can store a large non-negative number and cannot be changed after it is defined. In this project, it is used to define the duration of the game, which is set to 60000 milliseconds, equivalent to 1 minute.

 

In the setup() function, we initialize serial communication and use a for loop to set the pin modes for the LEDs and buttons.

 

In the loop() function, the flow is as follows:

 

// Pre-game check
 if (!gameStarted && digitalRead(BUTTONS[0]) == LOW && digitalRead(BUTTONS[1]) == LOW && digitalRead(BUTTONS[2]) == LOW) {  
  // game initialization
gameStarted = true;
 }  
 
 // game is activated  
 if (gameStarted && millis() - gameStartTime < gameDuration) {  
     // Game on
   }   
   } else if (gameStarted) {  
   // Game over 
   gameStarted = false;  
 }  
// Go back to check if the game started

This flow can be represented as a flowchart as follows:


Pre-game check.

 

It is used to determine whether the game is activated. Activation refers to the transition from the game's inactive state (i.e., neither started nor in progress) to a state ready to start, while "start" may refer to the actual game loop that has been activated and initiated.

 

if (!gameStarted && digitalRead(BUTTONS[0]) == LOW && digitalRead(BUTTONS[1]) == LOW && digitalRead(BUTTONS[2]) == LOW)

 

Once the game is activated:

 

Serial.println("Game Start");  
gameStarted = true;  
score = 0; // Reset score  
gameStartTime = millis(); // Record the start time of the game  
delay(1000); // Wait for a second before starting the game

 

It will output "Game Start" through the serial port, set gameStarted to true to indicate that the game has started, reset the score to 0, record the game start time gameStartTime as the current milliseconds (using the millis() function to obtain), and then pause for 1 second to give the user a visual or psychological preparation time.

 

The second part is the logical structure after the game is activated.

 

if(gameStarted && millis() - gameStartTime < gameDuration) {  
   // Game on
 } else if (gameStarted) {  
   // Game over  
 }

 

While the game is ongoing and the game time has not exceeded the set duration (gameDuration), execute the logic for the game on. If the game has started but the game time has exceeded the set duration (gameDuration), execute the logic for the game over.

 

Game on:
First randomly select an LED by choosing a number between 0 and 2 inclusive using random(0, 3) (since array indices typically start from 0), and then light up that LED.

 

int led = random(0, 3); // Randomly select an LED  
digitalWrite(LEDS[led], HIGH); // Turn on the selected LED  
long startTime = millis(); 
// Initialize button state (not pressed).

 

At the same time, record the time (startTime) when the LED is lit, and set the buttonpressed state to false (initialization). After that, enter a while loop to wait for the player to press the corresponding button within 1 second.

 

while (millis() - startTime < 1000) {  
     if (digitalRead(BUTTONS[led]) == LOW) { 
// Check if the corresponding button is pressed  
       buttonPressed = true;  
       break; // Break the loop if the button is pressed  
     }  
   }

 

If the player presses the corresponding button within 1 second (by checking if BUTTONS[led] is pressed), mark buttonPressed as true and exit the loop. 
After the while loop ends, regardless of whether the button was pressed or not, the LED will be turned off.

 

if (buttonPressed) {  
     score++; // Increment score if the button is pressed  
     Serial.println("Score +1");  
   }  
   delay(200);

 

If buttonPressed is true, then increase the score and output a message indicating that the score has increased. Finally, use delay(200) to wait for 200 milliseconds before continuing to the next round of the game.

 

Game over:

 

gameStarted = false;  
Serial.print("Game Over! Your Score: ");  
Serial.println(score); // Output the score through serial  
delay(3000);

 

Else if the game has started but the game time has exceeded the set duration (gameDuration), then execute the logic for the end of the game. Set gameStarted to false to indicate that the game has ended, output a message through the serial port indicating that the game has ended along with the player's final score, and then pause for 3 seconds to give the user a buffer time before starting the game again.

 

HARDWARE REVIEW

Pull-up resistors
Often it is useful to steer an input pin to a known state if no input is present. This can be done by adding a pull-up resistor (to +5V), or a pull-down resistor (resistor to ground) on the input. A 10K resistor is a good value for a pull-up or pull-down resistor. The pushbutton used in this project, since there is no built-in pull-up resistor, need to set the pin in the code to INPUT or use an external resistor.

 

There are 20K pull-up resistors built into the ATmega chip that can be accessed from software. These built-in pull-up resistors are accessed by setting the pinMode() as INPUT_PULLUP. This effectively inverts the behavior of the INPUT mode, where HIGH means the sensor is off, and LOW means the sensor is on.

 

EXERCISE

Using the available hardware, let's attempt to create a simplified version of a memory card-flipping game using two LEDs (representing two cards) and a button. At the start of the game, the two LEDs will randomly light up (simulating the flipping of cards) and then turn off. The player needs to remember the lit state of these two LEDs and, through button inputs, "flip" the cards (actually relight the LEDs) in an attempt to match the previous lit state. Depending on the result, the serial output will either say "Congratulations, you win!" or "Sorry, you lost!". There are many more variations to this game waiting for you to discover.

icon reflex_tester_English.zip 1KB Download(0)
License
All Rights
Reserved
licensBg
0