Simon Says Game

One of the more fun electronic games of childhood is Simon Says. The game has 4 buttons with different color lights beside each button.  The game will play a sequence and you have to repeat it. Every time you copy Simon correctly, the game makes the sequence you need to repeat one longer. Keep repeating the pattern until you get through all the levels (you win!) or miss a pattern (and you lose).

The retail Simon games have sounds in addition to lighted buttons.  So will we, with Circuit Playground! The sequences get longer by one for each round - can you remember them all?

// Adafruit Circuit Playground - Simon Game    Support Open Source, buy Adafruit
//   2016-08-07 Version 1  by Mike Barela for Adafruit Industries
// Uses the CircuitPlayground library to easily use the full functionality of the board
// Based on Simon Sings by @TheRealDod with permission http://goo.gl/ea4VDf

#include <Adafruit_CircuitPlayground.h>  // Library for Circuit Playground functions
#include "pitches.h"                     // File for musical tones

const int NLEDS = 4; 
const int LEDPINS[NLEDS] = {1,3,6,8};    // The NeoPixels used (counterclockwise from USB)
const int SWITCHPINS[NLEDS] = {2,0,6,9}; // Capacitive inputs 1-4 (match the NeoPixel positions)

const int FADESTEPS = 8;
const int FADEINDURATION = 200;
const int FADEOUTDURATION = 150;
const int SEQDELAY = 50;    // Millis between led flashes.
const int PAUSEB4SEQ = 500; // Millis before starting the sequence.
const int MINLEVEL =  2;   
const int MAXLEVEL = 16;
int gameLevel;
int simonSez[MAXLEVEL]; // sequence of 0..NLEDS-1

const int16_t CAP_SAMPLES   = 20;      // number of samples for each capacitive input pad
const int16_t CAP_THRESHOLD = 300;     // Threshold for a capacitive touch (higher = less sensitive)

// -- song-array note fields --
// Tone
const int NOTETONE = 0;
const int SILENCE = 0;
const int ENDOFSONG = -1;
// Duration
const int NOTEDURATION = 1;
const int SINGLEBEAT = 125; // Note duration (millis) is multiplied by this
const float PAUSEFACTOR=0.2; // relative length of silence after playing a note
// LED
const int NOTELED = 2;
const int ALLLEDS = -1;

const int NOTES[NLEDS] = {NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4}; // Notes for each LED/Switch

int CORRECTSONG[][3] = {    // song to play if you mimiced the sequence correctly
   {SILENCE,2,ALLLEDS}
  ,{NOTE_G4,1,ALLLEDS}
  ,{NOTE_G4,1,ALLLEDS}
  ,{NOTE_A4,2,ALLLEDS}
  ,{ENDOFSONG,ENDOFSONG,ENDOFSONG}
};

int WINSONG[][3] = {       // song to play if you win the entire game
   {SILENCE,2,ALLLEDS}
  ,{NOTE_E4,1,2}
  ,{NOTE_E4,1,2}
  ,{NOTE_E4,1,2}
  ,{NOTE_F4,1,3}
  ,{NOTE_E4,1,2}
  ,{NOTE_D4,3,1}
  ,{NOTE_G4,1,ALLLEDS}
  ,{NOTE_G4,1,ALLLEDS}
  ,{NOTE_G4,1,ALLLEDS}
  ,{NOTE_A4,2,ALLLEDS}
  ,{NOTE_G4,5,ALLLEDS}
  ,{ENDOFSONG,ENDOFSONG,ENDOFSONG}
};

int LOSESONG[][3] = {   // notes to play if you don't mimic correctly and lose the game
  {NOTE_B5,2,3},{NOTE_A5,2,2},{NOTE_GS5,2,1},{NOTE_G5,8,ALLLEDS},{ENDOFSONG,ENDOFSONG,ENDOFSONG}
};

//===========================================================================================

void setup() {
  CircuitPlayground.begin();           // initialize the Circuit Playground library
  CircuitPlayground.setBrightness(30); // we don't want the NeoPixels too bright
  Serial.begin(9600);                  // Serial monitor for debugging and info
  randomSeed(analogRead(4));           // random value based on sounds
  gameLevel=MINLEVEL;                  // start game at the minimum guess level
  playWinSequence();                   // Visual feedback after reset. 
}
 
void loop() {
  int done;
  initGameSequence(gameLevel);        // Set up moves for new game
  done = 0;
  while (!done) {                     // set up to loop while playing
    while( !CircuitPlayground.leftButton() && !CircuitPlayground.rightButton() ) ; // wait to start
    delay(PAUSEB4SEQ);
    playGameSequence(gameLevel);      // Play the sequence to user
    if (playerGuess(gameLevel)) {     // See if person repeated the same sequence
      playCorrectSequence();          //   You did it right, make it harder by 1 move
      done = 1;
      if(gameLevel < MAXLEVEL) {       //   Increasing level by 1
        gameLevel++;
      }
      else {                // You played all the levels
        playWinSequence();  // You won the entire game!
        while(1) ;          // Press Circuit Playground Reset to restart  
      }
    } 
    else {
      playLoseSequence();    // You didn't get it right, sorry
      gameLevel = MINLEVEL;  // Reset to the starting level
    }
  }
}

void initGameSequence(int gameLevel) {    // Set the values for the values to mimic
  // assertion: gameLevel<=MAXLEVEL
  if( gameLevel == MINLEVEL ) {           // Minimum level all random
    for( int i=0; i < gameLevel; i++ ) {
      simonSez[i] = random(NLEDS);        // Min - select all new random pattern
    }
  }
  else {
    simonSez[gameLevel-1] = random(NLEDS); // add one more random value for next level
  }                                        //  which is different than some variations of game
}

void playGameSequence(int gameLevel) {     // Play the sequence to mimic
  Serial.print("Try this: ");
  for (int i=0; i < gameLevel; i++) {
    playLed(simonSez[i]);                  // Light LED and play its note
    Serial.print(SWITCHPINS[simonSez[i]]);
    if( i != gameLevel-1 )
       Serial.print(", ");
    else
       Serial.println(" ");
  }
}

void fadeLed(int theLed,int val,int duration) {  // fade NeoPixels in or out
  int fadeStep  = 256/FADESTEPS;
  int fadeDelay = duration/FADESTEPS;
  for(int i=0; i < 256; i+=fadeStep) {
    if( theLed >= 0 ) {
      lightPixel(theLed, val?i:255-i);
    } 
    else { // ALLLEDS
      for(int j=0; j < NLEDS; j++) {
         lightPixel(j, val?i:255-i);
      }
    }
    delay(fadeDelay);
  }
  // force val (in case fadeStep doesn't divide 256)
  if( theLed >= 0 ) {
      lightPixel(theLed, val);
  }
  else {
    for(int j=0; j < NLEDS; j++) {
      lightPixel(j, val);
    }
  }
}

void playLed(int theLed) {                            // Fade LED and play its note
  if(CircuitPlayground.slideSwitch()) {               // Only play song if slide switch on "+"
     CircuitPlayground.playTone(NOTES[theLed],100);
  }
  fadeLed(theLed,HIGH,FADEINDURATION);               // Fade in LED
  fadeLed(theLed,LOW,FADEOUTDURATION);               // Fade out LED
}

int playerGuess(int gameLevel) {         // Get the user's guess and compare to Simon's sequence
  for (int i=0 ; i < gameLevel ; i++) {
    int guess=getSwitchStroke();
    //Serial.print(guess,DEC);
    //Serial.print(",");
    //Serial.println(simonSez[i]);
    if (guess!=simonSez[i]) {
      return 0;
    } 
    else {
      playLed(guess);                   // Fade LED and play its note
    }
  }
  return 1;
}

void playSong(int song[][3]) {         // Play a predefined song sequence on CP speaker
  for (int note=0; song[note][NOTETONE]!=ENDOFSONG; note++) {
    int theDuration=SINGLEBEAT*song[note][NOTEDURATION];
    int theTone=song[note][NOTETONE];
    if ( theTone && CircuitPlayground.slideSwitch() ) {
       CircuitPlayground.playTone(theTone, 500);
    }
    int theLed=song[note][NOTELED];
    fadeLed(theLed,HIGH,theDuration);            // Fade in
    fadeLed(theLed,LOW,theDuration*PAUSEFACTOR); // Fade out + silence between note
  }
}

int playWinSequence() {     // Play the winning song and light sequence
  playSong(WINSONG);
  Serial.println("Win!");
}

int playLoseSequence() {     // Play the loosing song and light sequence
  playSong(LOSESONG);
  Serial.println("Loss");
}

int playCorrectSequence() {  // Play the song and light sequence for a correct move
  playSong(CORRECTSONG);
  Serial.println("Correct Repeat");
}

int getSwitchStroke() {     // Code to get a switch entry
  while (get1stPressedSwitch()>=0) {
    // flush everything until no switch is pressed
    delay(50);
  }
  while (get1stPressedSwitch()<0) {
    // wait for next press
    delay(50);
  }
  return get1stPressedSwitch();
}

int get1stPressedSwitch() {   // Poll the switches, return the switch being pressed or -1 for none
  for (int i=0; i<NLEDS; i++) {
    if(CircuitPlayground.readCap(SWITCHPINS[i], CAP_SAMPLES) >= CAP_THRESHOLD) { // read CP capacitive
//      Serial.print("Switch ");                                                 // switchpad
//      Serial.print(SWITCHPINS[i]); // print switch presses if you like 
//      Serial.print(", ");
//      Serial.println(i);
      return i;                      // return number of the switch pressed
    }
  }
  return -1;                         // no switch pressed this poll cycle
}

void lightPixel(uint8_t ledToLight, uint8_t intensity) {  // light the NeoPixel at passed intensity
   switch(ledToLight) {
     case(0):
       CircuitPlayground.setPixelColor(LEDPINS[ledToLight],intensity,0,0);  // red
       break;
     case(1):
       CircuitPlayground.setPixelColor(LEDPINS[ledToLight],0,intensity,0);  // green
       break;
     case(2):
       CircuitPlayground.setPixelColor(LEDPINS[ledToLight],0,0,intensity);  // blue
       break;
     case(3):
       CircuitPlayground.setPixelColor(LEDPINS[ledToLight],intensity,intensity,intensity);  // white
       break;
   }
}

Game Play:

Press the left or right button pushbutton to start the round.  Simon will play a sequence.  You then mimic the sequence by touching the pads next to the Neopixels Simon lit.  The four buttons are SDA #2, RX #0, #6 and #9.  If you repeat the sequence right, the lights flash and Circuit Playground plays a brief tone. The game will then increase the difficulty level by one more light. This variation keeps the lower sequences the same, adding a random light to each round. If the tone is annoying, move the slideswitch to "-" to silence the tones. If you get to the maximum level and repeat it right, you've won and you get a long winning song!  If you miss a sequence, the game plays a losing tone. To play a whole new game, press the RESET button.

The code uses the capacitive touch pads on Pins marked #2, #0, #6, and #9.  When the LED next to these pads blinks (and plays a unique tone), you need to memorize the sequence.  Then you play back the sequence by touching the above numbered gold pads next to the LEDs you saw.   Get through the maximum level and you're the big winner.  Great at keeping people entertained.

You can see I places small crews and nuts on the button pads - it makes it easier to press the right pads as you play. One more is put on ground. The capacitive switch effect works best with one hand on ground and one hand's finger touching the button pads. Your body acts as a high resistance to electricity making a good capacitive sensing capability, similar to touchscreens on many phones and tablets.

You can make the game harder (like the original author @TheRealDod did for an Arduino version). Change initGameSequence to make each level unique - that modification requires alot more memorization.

Having the tones play makes the game easier to remember as you go through the sequences - memory association is stronger with multiple senses, in this case position, light, and sound. You can turn the sound off with the slide switch (like in prior examples) but you may find playing requires more concentration with "just" your eyes on the lights. The power of association and music. Viva Circuit Playground's speaker.

Last updated on 2016-08-14 at 03.40.26 AM Published on 2016-08-17 at 02.09.02 PM