Christmas memories

We’re back with another techy hack! Today is my 5-LED christmassy take on the classic memory game Simon.

simon_hero

You’ll need:

  • 5 5mm LEDs, different colours
  • 5 pushbuttons
  • 1 piezo
  • Arduino UNO
  • Breadboard + jump wires

1) The LEDs
This hack is quite code heavy, so I’ll separate it into bits to make it more digestible. First, wire up 5 buttons and 5 LEDs (different colours). If you need some help with the circuit, check out oomlout, they’ve got good instructions.

Now, the Arduino code.
First, we define our variables:

const int btnGPin = 10;
const int ledGPin = 11;
const int btnRPin = 2;
const int ledRPin = 3;
const int btnWPin = 4;
const int ledWPin = 5;
const int btnBPin = 6;
const int ledBPin = 7;
const int btnYPin = 8;
const int ledYPin = 9;

const int loopNumber = 5;

int buttons[5] = {btnGPin, btnRPin, btnWPin, btnBPin, btnYPin};
int leds[5] = {ledGPin, ledRPin, ledWPin, ledBPin, ledYPin};

int buttonState = 0;

int sequence[26];

boolean playersTurn = false;
boolean lost = false;
int selectedButton = 6; // we know the index is never going to be greater than 5.
int playerCounter = 0;
int counter = 0;

It is easier to put the LED pins, and the button pins in arrays, to have better access to them.
sequence is an empty array for now, but we know its length is going to be 26 (that’s the number of notes in the tune we are going to use later).
Also, I do not recommend using pins 0 and 1 (they’re the RX/TX pins, the LEDs might light up while uploading, for example).

void setup() {
  for (int i = 0; i < loopNumber; i++) {
     pinMode(leds[i], OUTPUT);
     pinMode(buttons[i], INPUT);
  }
  setupSequence();  
}


Here we set up the buttons and leds, and we fill sequence, as mentioned above.

void setupSequence(){
 for(int i = 0; i< 26; i++) {
  sequence[i] = leds[random(100)%5];
 }
}


We draw 26 random LEDs (it isn’t easy in Arduino just to push() an array, so we draw the random sequence at the start of the game).

void loop(){
  
  if(playersTurn) {
     for(int i = 0; i< loopNumber; i++){
      buttonState = digitalRead(buttons[i]);
      if (buttonState == LOW) { 
        digitalWrite(leds[i], HIGH);
        selectedButton = i;
        
      } else {
        digitalWrite(leds[i], LOW);
        if(selectedButton == i) {
          //We wait for the button to be released to trigger the check.
          checkPlayerInput(leds[i]);
          selectedButton = 6; 
        }
      }
    }
    
  } else {
   playSequence(); 
  }
}


playSequence is designed to play the sequence the player has to repeat; the rest of the loop function contains the buttons’ logic for when it is the player’s turn to make a move. Note that we wait for a status change on the pressed button before calling checkPlayerInput, otherwise, the function would be called repeatedly while the button is down, and not just once.

void playSequence() {
  counter++;

  for (int i = 0; i< counter; i++) {
    //Turn all the LEDs off after each turn
    for(int j = 0; j < loopNumber; j++) {
      digitalWrite(leds[j], LOW);
    }
    delay(250);
    
    digitalWrite(sequence[i], HIGH);

    delay(250);     
  }
 
  playersTurn = true; 
}


We turn on a certain amount of LEDs that are part of the sequence, depending on the number of turns that have been played (counter). And we increment it for next turn, then we allow the player to make a move.

void checkPlayerInput(int input) {
 if(input == sequence[playerCounter]) {
  playerCounter++;
 } else {
   lost = true;
   playLostSequence();
 }

  if(playerCounter == counter && !lost) {
    if(counter == 26){
      playWinSequence();
    } else {
      playerCounter = 0;
      playersTurn = false;
      delay(1000); 
    }
  } 
}


If the player matches the sequence, let them continue, else declare lost as true and act accordingly.
If the sequence is correct, we move to the next. If we’ve reached the length of the sequence (26), we declare the game as won.

void playLostSequence() {
  for (int i = 0; i< loopNumber*3; i++) {
    for(int j = 0; j < loopNumber; j++) {
      digitalWrite(leds[j], LOW);
    }
    delay(50);
    digitalWrite(leds[i%5], HIGH);
    delay(150);     
  }
  
  resetGame();
}


If the game is lost, we play an animation with the LEDs before resetting the game.

void playWinSequence() {
 boolean on = false;
  
  for (int i = 0; i< 9; i++) {
    for(int j = 0; j < loopNumber; j++) {
      if(on) {
        digitalWrite(leds[j], HIGH);   
      } else {
        digitalWrite(leds[j], LOW); 
      }
    }
   
    on = !on;
    delay(200);    
  }
  
  resetGame();
   
}


If the game is won, we play a different animation before resetting the game.

void resetGame() {
  for(int i = 0; i < loopNumber; i++) {
    digitalWrite(leds[i], LOW);
  }
  setupSequence(); 
  playersTurn = false;
  lost = false;
  counter = 0;
  playerCounter = 0;
  delay(100);
}


And finally, we reset the game and draw a new random sequence.

2) Let’s add sound!
music-decor
I’ve based this sketch on the toneMelody example available in Arduino. You will have to include the pitches.h file included with the example.

int melody[] = {NOTE_D4, NOTE_F4, NOTE_G4, NOTE_G4,NOTE_G4, NOTE_A4, NOTE_B4, NOTE_C5, NOTE_C5,NOTE_D4, NOTE_F4, NOTE_G4, NOTE_G4, NOTE_G4,NOTE_A4, NOTE_G4, NOTE_F4, NOTE_F4,NOTE_E4, NOTE_G4, NOTE_C4, NOTE_E4,NOTE_D4, NOTE_F4, NOTE_B3, NOTE_C4};
// note durations: 4 = quarter note, 8 = eighth note, etc.:
int noteDurations[] = {8, 8, 4, 3,8, 8, 8, 4, 2,8, 8, 4, 4, 4,8, 8, 4, 2,4, 4, 4, 4,4, 2, 4, 2 };

The melody has been modified to play Santa Claus is coming to town instead of the default tune. And we’ll also use pin 12 instead of 8 for our piezo.

void playSound(int thisNote) {
    int noteDuration = 1000/noteDurations[thisNote];
    tone(12, melody[thisNote],noteDuration);
    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);

    noTone(12);
}

This function will be called in the playSequence() method of our main sketch and will associate a note with an LED lighting up.

void playSoundSequence() {
  for (int thisNote = 0; thisNote < 26; thisNote++) {

    // to calculate the note duration, take one second 
    // divided by the note type.
    //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
    int noteDuration = 1000/noteDurations[thisNote];
    tone(12, melody[thisNote],noteDuration);
    digitalWrite(sequence[thisNote], HIGH);
    
    delay(noteDuration);
    digitalWrite(sequence[thisNote], LOW);

    int pauseBetweenNotes = noteDuration * 0.30;
    delay(pauseBetweenNotes);
    // stop the tone playing:
    noTone(12);
  }
  
  resetGame();
}


This method is called in the winning function of the main sketch and plays back the whole light sequence along with the tune.

void playWrongSound() {
  int noteDuration = 1000/1;
  tone(12, NOTE_C1,noteDuration);
  int pauseBetweenNotes = noteDuration * 1.30;
  delay(pauseBetweenNotes);
  noTone(12);
}


Finally, if you get it wrong, we play a long buzzing sound before resetting the game.

That’s it! I hope I didn’t lose you there… Hopefully I’ll be back tomorrow with something less technical.

Advertisements

4 thoughts on “Christmas memories

    • Hi Marco!
      Thanks for pointing this out. One of the code snippets was actually missing “void checkPlayerInput(int input) {” – it is fixed now!
      (6th snippet from the top)

      Have fun with the game!
      Lily

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s