The best tools to make your project dreams come true

Login or Signup


Circuit Playground Simple Simon

By Adafruit Industries

Courtesy of Adafruit

Guide by Carter Nelson

 

This is a Circuit Playground rendition of the classic Simon game from the late 70s and early 80s. The idea is to repeat an ever growing sequence of lights (each with a tone). Here we use the NeoPixels to represent the colors, the capacitive touch pads provide our buttons, and the speaker to play the distinctive tones. The Circuit Playground is even round like the classic game!

Required Parts

This project uses the hardware already included on the Circuit Playground so no additional electronics or soldering are required. If you want to play the game without being attached to your computer, you will also need some batteries and a holder for the batteries.

Parts List

Required parts

Required Parts

Before Starting

If you are new to the Circuit Playground, you may want to first read these overview guides.

This project will use the Arduino IDE. Make sure you have added the board support for the Circuit Playground as well as installed the Circuit Playground library. MUST DO BOTH. This is covered in the guides linked above.

Playing the Game

Here's the code for the Simple Simon game. Download the zip file, extract it, and load the sketch to your Circuit Playground using the Arduino IDE.

Simple Simon zip file

Starting a New Game

You can press the RESET button at anytime to start a new game. When the game starts, you must first select a game difficulty level using the SKILL LEVEL button. Then press the GAME START button to start playing.

  • SKILL LEVEL: Use to select game difficulty
  • GAME START: Use to start a game at selected skill level

Simple Simon - Skill level and Game start buttons

Simple Simon - Skill level and Game start buttons

The SKILL LEVEL sets the total length (N) of the game sequence.

  • • 1 -> N=8
  • • 2 -> N=14
  • • 3 -> N=20
  • • 4 -> N=31

Simple Simon - Choosing your skill level

Simple Simon - Choosing your skill level

The game is played by repeating the sequence of lights shown using the associated touch pads. There are four different sections of lights, each with its own color and tone.

 Touch pad color map

Use the touch pads shown for each color. You can press one or both, it doesn't matter.

The sequence starts with only one light. It then grows by one light each time you successfully complete the sequence. Thus the game starts easy, and gets more difficult as it progresses. To make it even more difficult, the sequence playback speeds up when the sequence becomes longer.

Winning

To game is won by completing the entire sequence for the chosen skill level without making any mistakes. A super special sound and light show called a 'razz' is displayed for the victor.

Losing

There are two was to lose the game:

  1. Pick an incorrect light in the sequence.
  2.  Take too long (>3 secs) to pick the next light.

If either of the above happens, the game ends. The light that was suppose to be next is shown.

Playing Again

Simply press RESET to start a new game.

Example Game Play

The video below demonstrates selecting a skill level, starting a new game, general game play, and successfully completing the sequence.

 

Code Walk Through

Let's go through the code chunk by chunk to see how it works.

chooseSkillLevel()

Copy Code
void chooseSkillLevel() {
while (!CircuitPlayground.rightButton()) {
if (CircuitPlayground.leftButton()) {
skillLevel = skillLevel 1;
if (skillLevel > 4) skillLevel = 1;

CircuitPlayground.clearPixels();
for (int p=0; p<skillLevel; p ) {
CircuitPlayground.setPixelColor(p, 0xFFFFFF);
}

delay(DEBOUNCE);
}
}
}

The outer while() loop looks for the right button press, which starts the new game. Inside the loop is an if statement that looks for the left button press and increments the skill level for each press. The value is checked and reset back to 1 if it exceeds the max level, which is 4. The current level is shown using the first 4 NeoPixels.

newGame()

Copy Code
void newGame() {
// Set game sequence length based on skill level
switch (skillLevel) {
case 1:
sequenceLength = 8;
break;
case 2:
sequenceLength = 14;
break;
case 3:
sequenceLength = 20;
break;
case 4:
sequenceLength = 31;
break;
}

// Populate the game sequence
for (int i=0; i<sequenceLength; i ) {
simonSequence[i] = random(4);
}

// We start with the first step in the sequence
currentStep = 1;
}

The entire game sequence is created before the game is started. The game sequence length is set based on the skill level in the switch block. The game sequence is then filled with random numbers between 0-3 to represent one of the four game pads. The currentStep is set to the starting position of 1.

indicateButton()

Copy Code
void indicateButton(uint8_t b, uint16_t duration) {
CircuitPlayground.clearPixels();
for (int p=0; p<3; p ) {
CircuitPlayground.setPixelColor(simonButton[b].pixel[p], simonButton[b].color);
}
CircuitPlayground.playTone(simonButton[b].freq, duration);
CircuitPlayground.clearPixels();
}

This function displays the NeoPixels associated with the "button" passed in by b, plays the tone associated with this "button" for the length of time passed in by duration. After the tone is played, the lights are turned off.

showSequence()

Copy Code
void showSequence() {
// Set tone playback duration based on current sequence length
uint16_t toneDuration;
if (currentStep<=5) {
toneDuration = 420;
} else if (currentStep<=13) {
toneDuration = 320;
} else {
toneDuration = 220;
}

// Play back sequence up to current step
for (int i=0; i<currentStep; i ) {
delay(50);
indicateButton(simonSequence[i], toneDuration);
}
}

The game sequence is played back up to the current game play location. The duration of the tone is set in the if statement based on the current location. This sets the play back speed of the sequence. Then, the sequence is played back up to its current location.

getButtonPress()

Copy Code
uint8_t getButtonPress() {
for (int b=0; b<4; b ) {
for (int p=0; p<2; p ) {
if (CircuitPlayground.readCap(simonButton[b].capPad[p]) > CAP_THRESHOLD) {
indicateButton(b, DEBOUNCE);
return b;
}
}
}
return NO_BUTTON;
}

 The 2 touch pads associated with each "button" are scanned to see if either are currently being touched. If so, it is indicated and its index is returned, otherwise NO_BUTTON is returned.

gameLost()

Copy Code
void gameLost(int b) {
// Show button that should have been pressed
for (int p=0; p<3; p ) {
CircuitPlayground.setPixelColor(simonButton[b].pixel[p], simonButton[b].color);
}

// Play sad sound :(
CircuitPlayground.playTone(FAILURE_TONE, 1500);

// And just sit here until reset
while (true) {}
}

The "button" that is passed in via b is shown, the sad tone is played, and the game ends at an infinite loop.

gameWon()

Copy Code
void gameWon() {
// Play 'razz' special victory signal
for (int i=0; i<3; i ) {
indicateButton(3, 100); // RED
indicateButton(1, 100); // YELLOW
indicateButton(2, 100); // BLUE
indicateButton(0, 100); // GREEN
}
indicateButton(3, 100); // RED
indicateButton(1, 100); // YELLOW

// Change tones to failure tone
for (int b=0; b<4; b ) simonButton[b].freq = FAILURE_TONE;

// Continue for another 0.8 seconds
for (int i=0; i<2; i ) {
indicateButton(2, 100); // BLUE
indicateButton(0, 100); // GREEN
indicateButton(3, 100); // RED
indicateButton(1, 100); // YELLOW
}

// Change tones to silence
for (int b=0; b<4; b ) simonButton[b].freq = 0;

// Loop lights forever
while (true) {
indicateButton(2, 100); // BLUE
indicateButton(0, 100); // GREEN
indicateButton(3, 100); // RED
indicateButton(1, 100); // YELLOW
}
}

 Plays the super special victory 'razz' and ends with an infinite loop of blinking lights.

setup()

Copy Code
void setup() {
// Initialize the Circuit Playground
CircuitPlayground.begin();

// Set play level
skillLevel = 1;
CircuitPlayground.clearPixels();
CircuitPlayground.setPixelColor(0, 0xFFFFFF);
chooseSkillLevel();

// Sowing the seeds of random
randomSeed(millis());

// Create game
newGame();
}

Per the rules of Arduino, this is the first thing called. It initializes the Circuit Playground, gets the skill level, seeds the pseudo random number generator, and sets up a new game.

loop()

Copy Code
void loop() {
// Show sequence up to current step
showSequence();

// Read player button presses
for (int s=0; s<currentStep; s ) {
startGuessTime = millis();
guess = NO_BUTTON;
while ((millis() - startGuessTime < GUESS_TIMEOUT) && (guess==NO_BUTTON)) {
guess = getButtonPress();
}
if (guess != simonSequence[s]) {
gameLost(simonSequence[s]);
}
}
currentStep ;
if (currentStep > sequenceLength) {
delay(SEQUENCE_DELAY);
gameWon();
}
delay(SEQUENCE_DELAY);
}

Per the rules of Arduino, this is called over and over and over and over. First, the current sequence is played back. Then the player interaction is dealt with. For each step in the sequence, a button is read. If it's the wrong button, or the player took too long to press the button, the value is set to NO_BUTTON. The button value is then compared to the current sequence value. If it's correct, the game continues. If it's wrong, the game is over. Once the step values exceeds the game sequence length, the game is won.

The Structure of struct

Have fun playing for a while before reading this

In the previous section, I didn't mention any of the lines of code at the top of the sketch. Most of these are just normal global variables and constants that are fairly common. However, this section of code may be new to you.

Copy Code
struct button {
uint8_t capPad[2];
uint8_t pixel[3];
uint32_t color;
uint16_t freq;
} simonButton[] = {
{ {3,2}, {0,1,2}, 0x00FF00, 415 }, // GREEN
{ {0,1}, {2,3,4}, 0xFFFF00, 252 }, // YELLOW
{ {12, 6}, {5,6,7}, 0x0000FF, 209 }, // BLUE
{ {9, 10}, {7,8,9}, 0xFF0000, 310 }, // RED
};

This is defining, and initializing, an array (simonButton[]) of something called a struct. A struct comes from the C programming language, but is also supported in C and thus Arduino. What it basically does is let us create a new variable that is a collection of information associated with something, like a Simon "button". This let's us pass around and access this information from a single reference. We don't need to pass around each individual piece of information separately.

Talking about structs can get pretty complex, and they're a precursor to the more complex class of C and the world of OOP. But they are very useful, and we are using it in fairly simple way in our Simple Simon code. To better understand why a struct is useful, let's look at a more simple example - a 2D point.

2D Point Example

A 2D point is represented by an x and y value. We could create these as separate variables:

Copy Code
float x;
float y;

and then use them as normal:

Copy Code
x = 45.3;
y = -108.6;

But what if I wanted to pass this 2D point to a function? Or what if I wanted to create another 2D point? Things start getting a little messy. This is where a struct would help out. Instead of two separate variable, we create a single struct to represent the 2D point, and put the variables in there.

Copy Code
struct point {
float x;
float y;
}

And then to create a new point variable:

Copy Code
struct point someLocation;

And assign it values:

Copy Code
someLocation.x = 45.3;
someLocation.y = -108.6;

Now all one needs to do is pass the variable someLocation around. The values come along for the ride. And they don't get confused with some other values. If we need another 2D point, we just create another variable, or better yet, an array of them.

The one fancy thing being done in the Simple Simon code is to create and initialize the values of the struct in one step. For our 2D point example, this would look like:

Copy Code
struct point {
float x;
float y;
} someLocation = {
45.3, -108.6
}

It's just a shortcut way of creating the variable and assigning it some initial values.

The Simple Simon "button"

Getting back to our Simple Simon game, think about what each of the four "buttons" has. Most obvious is a color. But they also make a sound when they are pressed, and each has its own tone. Since we are trying to implement these on our Circuit Playground, we will use a set of NeoPixels to show off the color. Also, we plan to use capacitive touch for the buttons, with each button having a couple of pads. So, we have the following items for each "button":

  • 2 x capacitive touch pads
  • 3 x NeoPixels
  • a colora
  • tone

Hey, that should look familiar. Look again at the struct definition:

Copy Code
struct button {
uint8_t capPad[2];
uint8_t pixel[3];
uint32_t color;
uint16_t freq;
}

Just like the list. For each "button" we have the indices for 2 capacitive touch pads, the indices for 3 NeoPixels, the color to use for the NeoPixels, and the frequency of the tone for the button.

We have more than one "button", so we create an array of them and initialize their values:

Copy Code
  simonButton[] = {
{ {3,2}, {0,1,2}, 0x00FF00, 415 }, // GREEN
{ {0,1}, {2,3,4}, 0xFFFF00, 252 }, // YELLOW
{ {12, 6}, {5,6,7}, 0x0000FF, 209 }, // BLUE
{ {9, 10}, {7,8,9}, 0xFF0000, 310 }, // RED
};

Each line is an entry in the array and represents one button. Each item in each line corresponds to the value of the struct and is used to define the specific buttons. For example, the first line defines the GREEN button as having the capacitive touch pads #3 and #2, the NeoPixels #0, #1, and #2, the color green (in hex), and the frequency 415Hz.

Then, if we need the frequency for the tone of the GREEN button, the syntax is:

Copy Code
simonButton[0].freq

The color for the BLUE button?

Copy Code
simonButton[2].color

The index of the 2nd NeoPixel for the RED button?

Copy Code
simonButton[3].pixel[1]

etc. etc.

Hopefully that makes some sense and you see how it is useful. If not, meh, just have fun playing the game....for now.

So Why Didn't You Just Create a simonButton Class?

Just trying to keep it simple. But this is totally doable, since Arduino uses C under the hood.

Key Parts and Components

Add all Digi-Key Parts to Cart
  • 1528-1640-ND
  • 1528-1151-ND