Introduction: Arduino Cyclone Reaction Time Game

In this instructable, we walk through how I made a "Cyclone" - type arcade game using an Arduino. This game also includes a reaction timer mode. Let's get started!

A list of things you will need:

  1. Arduino Uno
  2. LCD Screen
  3. MCP23017 serial port expander
  4. 2 pushbuttons
  5. LEDs of differing colors (I used 3mm and 5mm LEDs in red, green, and yellow)
  6. Resistors
    1. 100 and 150 ohm for LEDs
    2. 10k ohm for port expander and buttons
  7. Lots of jumper wires
  8. A willing contestant

Step 1: Wiring Up Your Breadboard and Accessories

Wire up your breadboard as shown, connecting the Arduino with all the LEDs through the port expander (NOTE: in the first photo, the chip labelled "Microcontroller" represents the MCP23017 port expander). Additionally, connect the Arduino to the LCD screen. Ensure the contrast of the screen is properly adjusted by using the potentiometer on the back of the unit.

Step 2: Step 2: Coding!

Here is the code I wrote for this project. Note: you must download the appropriate libraries for the MCP23017 port expander (can be found on Adafruit) and your specific LCD screen.

I have included comments that explain most parts of the code. Feel free to comment if you have questions, and I will do my best to answer them.

#include <adafruit_mcp23017.h>                //Including MCP23017 port expander and LCD libraries

#include <liquidcrystal_i2c.h>                //can be downloaded from Adafruit

LiquidCrystal_I2C lcd(0x3f, 16, 2);           //These two lines create lcd and mcp objects
Adafruit_MCP23017 mcp;const int led1 = 15;                          //Initializing pin numbers as integers
const int led2 = 14;
const int led3 = 13;
const int led4 = 12;
const int led5 = 11;
const int led6 = 10;
const int led7 = 9;
const int led8 = 8;
const int led9 = 7;
const int led10 = 6;
const int led11 = 5;
const int buttonPin = 3;
const int modePin = 2;
boolean mode = true;                          //These two booleans are used to change the mode from Cyclone to Reaction,
boolean pass = true;                          //and to check for early button presses in Reaction mode

int chaseDelay = 100;                         //This section initializes a bunch of variables used later in the code
int level = 0;
int hiScore = 0;
long reactionTime = 0;
long reactionStart = 0;
long hiReact = 999;
long adjust = 0;
int i = 0;

void setup() {                                //This block runs only once at the start of the program
  Serial.begin(9600);                         //Begin serial monitor communication
  lcd.begin();                                //Turn on the LCD screen, and its backlight
  lcd.backlight();
  pinMode(buttonPin, INPUT);                  //Setting the button pins as inputs
  pinMode(modePin, INPUT);
  mcp.begin();                                //Begins communication through the MCP23017 port expander
  mcp.pinMode(led1, OUTPUT);                  //Setting the pins associated with the ledX variables to outputs
  mcp.pinMode(led2, OUTPUT);
  mcp.pinMode(led3, OUTPUT);
  mcp.pinMode(led4, OUTPUT);
  mcp.pinMode(led5, OUTPUT);
  mcp.pinMode(led6, OUTPUT);
  mcp.pinMode(led7, OUTPUT);
  mcp.pinMode(led8, OUTPUT);
  mcp.pinMode(led9, OUTPUT);
  mcp.pinMode(led10, OUTPUT);
  mcp.pinMode(led11, OUTPUT);
}

void loop() {
  attachInterrupt(digitalPinToInterrupt(modePin), modeToggle, RISING);          //This line assigns the modePin as an interrupt, running the modeToggle function whenever the mode button is pressed
  if (mode) {                                                                   //If the mode is set to true, Cyclone begins
    lcd.setCursor(0, 0);                                                        //This block prints text on the LCD screen
    lcd.print(" CYCLONE!  HS:" + String(hiScore) + "   ");
    lcd.setCursor(0, 1);
    lcd.print("Current Level:" + String(level));
    cyclone();
  } else {                                                                      //If the mode is set to false, Reaction begins
    lcd.setCursor(0, 0);
    lcd.print("REACTION! HS:" + String(hiReact) + "     ");
    lcd.setCursor(0, 1);
    lcd.print("Last Time:" + String(reactionTime) + "          ");
    if (digitalRead(modePin) == 1) {
      mode = true;
    }
    reaction();
  }
}

void modeToggle() {                                       //This is the modeToggle function which switches the mode
  mode = !mode;
}

void cyclone() {                                          //Cyclone function, which cycles through all the LEDs in order,
  for (i = 5; i <= 15; i++) {                             //and waits for a button press
    mcp.digitalWrite(i, 1);
    if (i == 10 && digitalRead(buttonPin) == 1) {         //If the button is pressed at the same time as the middle green LED,
      goodBlink();                                        //the goodBlink function is triggered and the game resets
      i = 5;
      break;
    } else if (digitalRead(buttonPin) == 1) {             //If the button is pressed any other time,
      badBlink();                                         //the badBlink function is triggered and the game resets
      i = 5;
      break;
    }
    delay(chaseDelay);
    mcp.digitalWrite(i, 0);
  }
}

void goodBlink() {                              //When this function is triggered, the user has successfully hit the green LED
  chaseDelay -= 10;                             //The delay between LED flashes is decreased by 10ms, making the game harder
  level ++;                                     //The shown "level" increases by 1
  if (level > hiScore) {                        //If the level is above the hi score, the hi score goes up
    hiScore = level;
  }
  for (int j = 1; j <= 5; j++) {                //This for loop blinks all the LEDs 5 times
    mcp.digitalWrite(5, 1);
    mcp.digitalWrite(6, 1);
    mcp.digitalWrite(7, 1);
    mcp.digitalWrite(8, 1);
    mcp.digitalWrite(9, 1);
    mcp.digitalWrite(10, 1);
    mcp.digitalWrite(11, 1);
    mcp.digitalWrite(12, 1);
    mcp.digitalWrite(13, 1);
    mcp.digitalWrite(14, 1);
    mcp.digitalWrite(15, 1);
    delay(250);
    mcp.digitalWrite(5, 0);
    mcp.digitalWrite(6, 0);
    mcp.digitalWrite(7, 0);
    mcp.digitalWrite(8, 0);
    mcp.digitalWrite(9, 0);
    mcp.digitalWrite(10, 0);
    mcp.digitalWrite(11, 0);
    mcp.digitalWrite(12, 0);
    mcp.digitalWrite(13, 0);
    mcp.digitalWrite(14, 0);
    mcp.digitalWrite(15, 0);
    delay(250);
  }
}

void badBlink() {                               //When this function is triggered, the user missed the green LED
  chaseDelay = 100;                             //The delay and level are reset to their default values
  level = 0;
  if (level > hiScore) {
    hiScore = level;
  }
  for (int k = 1; k <= 5; k++) {                //This for loop blinks just the red LEDs 5 times
    mcp.digitalWrite(5, 1);
    mcp.digitalWrite(6, 1);
    mcp.digitalWrite(14, 1);
    mcp.digitalWrite(15, 1);
    delay(150);
    mcp.digitalWrite(5, 0);
    mcp.digitalWrite(6, 0);
    mcp.digitalWrite(14, 0);
    mcp.digitalWrite(15, 0);
    delay(150);
  }
  mcp.digitalWrite(i, 0);
}

void reaction() {                               //This function runs the reaction game
  mcp.digitalWrite(5, 1);                       //The game begins with all the red LEDs on
  mcp.digitalWrite(6, 1);
  mcp.digitalWrite(14, 1);
  mcp.digitalWrite(15, 1);
  reactionStart = millis();                     //The time in milliseconds is stored as reactionStart
  while (millis() - reactionStart < 3000) {     //and the game waits for 3 seconds, while also
    if (digitalRead(buttonPin) == 1) {          //constantly checking for an early button press
      pass = false;                             //If you press the button early, the "pass" boolean is set to false
    }                                           //and the game skips to earlyBlink
  }
  if (pass) {                                   //The red LEDs turn off, and yellow LEDs are turned on
    mcp.digitalWrite(5, 0);
    mcp.digitalWrite(6, 0);
    mcp.digitalWrite(14, 0);
    mcp.digitalWrite(15, 0);
    mcp.digitalWrite(7, 1);
    mcp.digitalWrite(8, 1);
    mcp.digitalWrite(12, 1);
    mcp.digitalWrite(13, 1);
  } else {
    earlyBlink();
  }
  reactionStart = millis();
  long rando = random(500, 5000);               //The game then waits for a random delay between .5 and 5 seconds
  Serial.println(rando);                        //Printing out the delay on the serial monitor, for testing purposes
  while ((millis() - reactionStart) < rando) {  //Again waiting for an early button press
    if (digitalRead(buttonPin) == 1) {
      pass = false;
    }
  }
  if (pass) {                                   //If you dont press early, the yellow LEDs turn off and the green turn on
    mcp.digitalWrite(7, 0);
    mcp.digitalWrite(8, 0);
    mcp.digitalWrite(12, 0);
    mcp.digitalWrite(13, 0);
    mcp.digitalWrite(9, 1);
    mcp.digitalWrite(10, 1);
    mcp.digitalWrite(11, 1);
    reactionTime = 0;                           //The clock begins ticking right when the green LEDs turn on
    reactionStart = millis();
    while (digitalRead(buttonPin) == 0) {       //The game hangs while it waits for you to react and press the button
    }
    reactionTime = millis() - reactionStart;
    lcd.setCursor(0, 1);
    lcd.print("Last Time:" + String(reactionTime) + "   ");     //After you press the button, the game stores and prints out your reaction time, in milliseconds
  } else {                                                      //This else statement runs if you pressed a button early at any point in the game
    earlyBlink();
  }
  mcp.digitalWrite(9, 0);
  mcp.digitalWrite(10, 0);
  mcp.digitalWrite(11, 0);
  delay(1000);
  if (reactionTime < hiReact && reactionTime != 0) {
    hiReact = reactionTime;
  }
  pass = true;
}

void earlyBlink() {                             //This function blinks the red LEDs 3 times if you press the button early, then restarts the game
  for (int k = 1; k <= 3; k++) {
    mcp.digitalWrite(5, 1);
    mcp.digitalWrite(6, 1);
    mcp.digitalWrite(14, 1);
    mcp.digitalWrite(15, 1);
    delay(150);
    mcp.digitalWrite(5, 0);
    mcp.digitalWrite(6, 0);
    mcp.digitalWrite(14, 0);
    mcp.digitalWrite(15, 0);
    delay(150);
    mcp.digitalWrite(5, 0);
    mcp.digitalWrite(6, 0);
    mcp.digitalWrite(7, 0);
    mcp.digitalWrite(8, 0);
    mcp.digitalWrite(9, 0);
    mcp.digitalWrite(10, 0);
    mcp.digitalWrite(11, 0);
    mcp.digitalWrite(12, 0);
    mcp.digitalWrite(13, 0);
    mcp.digitalWrite(14, 0);
    mcp.digitalWrite(15, 0);
  }
}

Step 3: Step 3: Play!

Once you compile the code and upload it to your Arduino, the game should run! (Assuming everything is wired correctly) Attached is a video showing gameplay of the project. I hope you enjoyed, and good luck!