Close

Code

A project log for Simon Says: Solder!

A minimalist version of Simon Says, designed as a tool for teaching folks of all ages about electronics.

alex-rykerAlex Ryker 09/08/2024 at 16:540 Comments

For easy copy-paste, the code for the Simon Says game is below. It is also attached as a file to this project.

/**
 * Simon Says
 * 
 * This program represents a simple game of Simon Says with two LEDs and two push buttons.
 * It is designed to showcase a minimalistic circuit that still does something interesting.
 * 
 * When the circuit is powered on, both LEDs will blink three times. Then, LEDs will light up
 * one at a time in a random pattern that the user must duplicate using the push buttons.
 * Once the sequence has completed, each button controls an LED. The pattern in which the
 * LEDs blink is randomly generated, and has a length measured in blinks. The patterns begin
 * with one blink, then increase by one blink each time the player correctly mimics the sequence.
 * 
 * If the user gets the sequence wrong, both LEDs will blink three times, then start a new
 * pattern at length 1. The game will continue until the pattern is 50 blinks long. If the
 * user correctly mimics all 50 patterns, the game is over and the LEDs will blink in a
 * celebratory pattern until the circuit is power cycled.
 * 
 * This code can be run on an Arduino, but is designed for the ATtiny85 microcontroller.
 * 
 * Author: acr
 */

 // pin configuration
 const int greenLED = 4;
 const int redLED = 1;
 const int greenButton = 3;
 const int redButton = 2;

 // debounce
 unsigned long lastGreenDebounceTime = 0;
 unsigned long lastRedDebounceTime = 0;
 unsigned long debounceDelay = 50;

 // game data
 char pattern[50] = {0};
 int curPatternLength = 1;
 int greenButtonState = 0;
 int redButtonState = 0;
 int lastGreenButtonState = 0;
 int lastRedButtonState = 0;

void setup() {
  // initialize LED pins as digital outputs
  pinMode(greenLED, OUTPUT);
  pinMode(redLED, OUTPUT);

  // initialize button pins as digital inputs
  pinMode(greenButton, INPUT);
  pinMode(redButton, INPUT);

  // seed random number generator
  randomSeed(analogRead(0));
}

/**
 * Blink both LEDs in the pattern that occurs when
 * the player has lost the game.
 */
void blinkLose() {
  for(int i = 0; i < 3; i++) {
    digitalWrite(greenLED, HIGH);
    digitalWrite(redLED, HIGH);
    delay(500);
    digitalWrite(greenLED, LOW);
    digitalWrite(redLED, LOW);
    delay(500);  
  }  
}

/**
 * Blink both LEDs in the pattern that occurs when
 * the player has won the game.
 */
void blinkWin() {
  for(int i = 0; i < 2; i++) {
    digitalWrite(greenLED, HIGH);
    digitalWrite(redLED, LOW);
    delay(500);
    digitalWrite(greenLED, LOW);
    digitalWrite(redLED, HIGH);
    delay(500);
    digitalWrite(greenLED, LOW);
    digitalWrite(redLED, LOW);
  }

  for(int i = 0; i < 2; i++) {
    digitalWrite(greenLED, HIGH);
    digitalWrite(redLED, HIGH);
    delay(500);
    digitalWrite(greenLED, LOW);
    digitalWrite(redLED, LOW);  
    delay(500);
  }
}

/**
 * Generate a blink pattern that is length blinks long,
 * and store that pattern in the pattern array.
 */
void generatePattern(int length) {
  for(int i = 0; i < length; i++) {
    int rand = random(10);
    if(rand < 5) {
      pattern[i] = 'r';
    }else {
      pattern[i] = 'g';
    }
  }
}

/**
 * Turns off all LEDs.
 */
void turnLightsOff() {
  digitalWrite(greenLED, LOW);
  digitalWrite(redLED, LOW);
}

/**
 * Turns on all LEDs.
 */
void turnLightsOn() {
  digitalWrite(greenLED, HIGH);
  digitalWrite(redLED, HIGH);
}

/**
 * Turns both lights on and blocks all other activity.
 * For use in case of an error state to indicate that
 * the user should check the logs and power cycle.
 */
void error() {
  while(1) {
    turnLightsOn();
  }
}

/**
 * Display the current pattern stored in the pattern array
 * using the red and green LEDs.
 */
void displayPattern() {
  for(int i = 0; i < curPatternLength; i++) {
    turnLightsOff();
    delay(500);    
    if(pattern[i] == 'g') {
      digitalWrite(greenLED, HIGH);
      delay(500);
    }else if(pattern[i] == 'r') {
      digitalWrite(redLED, HIGH);
      delay(500);
    }else {
      error();
    }
  }
  turnLightsOff();
}

/**
 * Check whether the color at position pos in the pattern
 * matches the given color. Returns 1 if true, 0 if false.
 */
int checkPatternColor(char color, int pos) {
  if(pattern[pos] == color) {
    return 1;
  }  
  return 0;
}

/**
 * Returns 1 if both buttons are unpressed, 0 otherwise.
 */
int buttonsUnpressed(int redState, int greenState) {
  if(redState == LOW && greenState == LOW) {
    return 1;
  }
  return 0;
}

/**
 * Reads the pattern as input by the player. Returns 1 if
 * the player entered the pattern correctly, 0 otherwise.
 */
int readPlayerPattern() {
  int cntr = 0; // counter for current position in the pattern
  int correct = 1; // whether the pattern is correct
  char playerColor = 0; // store the current color selection by the player
  
  while(cntr < curPatternLength) {
    int greenReading = digitalRead(greenButton);
    int redReading = digitalRead(redButton);
    int buttonsChanged = 0;

    if(greenReading != lastGreenButtonState) {
      lastGreenDebounceTime = millis();
    }

    if(redReading != lastRedButtonState) {
      lastRedDebounceTime = millis();
    }

    if((millis() - lastGreenDebounceTime) > debounceDelay) {
      if(greenReading != greenButtonState) {
        greenButtonState = greenReading;
        buttonsChanged = 1;
      }
    }

    if((millis() - lastRedDebounceTime) > debounceDelay) {
      if(redReading != redButtonState) {
        redButtonState = redReading;
        buttonsChanged = 1;
      }
    }    
    
    if(buttonsChanged == 1) { // monitor button state to debounce
      if(greenButtonState == HIGH && redButtonState == HIGH) {
        // fail - the two lights will never be on at the same time
        correct = 0;
        break;
      }else if(greenButtonState == HIGH && redButtonState == LOW) {
        // green button pressed
        playerColor = 'g';
      }else if(greenButtonState == LOW && redButtonState == HIGH) {
        // red button pressed
        playerColor = 'r';
      }else {
        playerColor = 0;
      }

      /* Note: Nothing is done if both buttons are low so that the player
       * has time to input the pattern at their own pace. */

      if(playerColor != 0) {
       // check if the choice was correct
       correct = checkPatternColor(playerColor, cntr);
       if(correct == 1) {
         // if so, increment the counter and continue
         cntr++;
         playerColor = 0;
       }else {
         // if not, break out of the loop
         break;
       }
      }      
    }
    
    lastGreenButtonState = greenReading;
    lastRedButtonState = redReading;
    buttonsChanged = 0;
  }
  return correct;
}

/**
 * The main loop of the program, runs repeatedly while the
 * power is on.
 */
void loop() {    
  // Serial.println("Starting program");
  blinkLose(); // blink 3 times on power up

  // start the game
  while(curPatternLength <= 50) { // game is 50 levels long
    generatePattern(curPatternLength);
    displayPattern();
    int playerResult = readPlayerPattern();
    if(playerResult == 1) {
      // player got the latest pattern correct, continue to the next one
      curPatternLength++;
    }else {
      // player failed the latest pattern, notify player of loss and restart pattern counter
      blinkLose();
      curPatternLength = 1;
    }

    delay(2000); // break in between patterns so user can keep up
  }
  
  // at this point, the user must have won all levels, so display win pattern
  blinkWin();
}

Discussions