Color Sequence Memory Game
This project recreates the Simon memory game using an Arduino, push buttons, and LEDs. At the start of each round, the LEDs display a color sequence. The length of the color sequence starts at one, and it increases by one each round. After the sequence is displayed, the user then must push the buttons in the same order that the lights flashed. If the player succeeds, they move on to the next round. If they fail, the game will end and their score will scroll on the seven segment display.
Supplies
Components
- 1 Arduino Uno R3
- 1 common cathode 7-segment display
- 1 BCD to 7-segment display decoder
- 2 hex inverter chips
- 4 differently colored LEDs
- 4 push-buttons
- 6 1kΩ resistors
Software
- Arduino IDE
Connect Power and Ground to Arduino
The entire project is powered from the Arduino. Connect the positive power rails of the breadboard to the 5V pin of the Arduino and connect the negative power rail to any of the ground pins of the Arduino. Then, add jumper wires between the top and bottom power rails.
Note: For each part of the circuit, I'm going to add an image of the circuit made using TinkerCAD. There is also a schematic at the end if you are more comfortable reading that. Also, you should not add/remove circuit components while the circuit is being powered (this can damage the components). Always double-check your wiring before turning the Arduino on.
Add Buttons to Breadboard
For each of the four push-buttons, one terminal should be connected to digital pins 2 - 5 of the Arduino, and the other terminal should be connected to the ground pin of the Arduino. The reason to skip over digital pins 0 (RX) and 1 (TX) are because they are used for uploading code to the Arduino. Having them connected would interfere with uploading, so it's easier to just leave them unconnected. In order from pin 2 to 5, the buttons are red, blue, yellow, and green.
Add LEDs to Breadboard
Add the four LEDs to the breadboard in the same order of the push-buttons. The cathode of each LED should be connected to digital pins 5 through 9, and the anode should be connected to ground using a 1 kΩ resistor.
Add 7-Segment Decoder
Connect the power and ground pins (top left and bottom right respectively) of the 7-segment decoder to the power/ground rails. You also need to set the configuration pins of the seven segment decoder. For the real life circuit, I used an HD74LS48 chip, but any BCD to seven-segment display decoder should do. TinkerCAD only has a CD4511 decoder chip, which has a slightly different pinout. The lamp test pin (used to turn on all segments) should be held at HIGH (so it's disabled). The blanking input pin should be held at HIGH so it's disabled. The CD4511 also has a latch enable pin (for displaying the same value regardless of inputs) should be held at LOW.
Add Seven Segment Display
The FND500 seven-segment display is a common-cathode display. This means that the common pins at the middle-top and middle-button should be connected to LOW. When an input is HIGH, the corresponding segment turns on, and when an input is LOW, the corresponding segment is off. Connect each output from the decoder to the corresponding input of the the display. The pinouts are pretty confusing, so make sure you connect everything correctly. Since the decimal point is never used, connected to ground with a wire. The common cathode pins should be connected to ground with two 1 kΩ resistors. After that, connect the four input pins of the decoder to digital pins 10 - 13 in order of least-significant to most-significant bit.
Note: If you experience inconsistent brightness problems, try adding a separate resistor for each pin and just using wires for the common cathode.
Downloads
Label Pins in Code
In Arduino IDE, create a new sketch. Above the setup function, add the following code. This code is used for mapping each digital pin to its corresponding function. The enum /* name */ : uint8_t syntax means that each value in the enum is an unsigned, 8-bit integer. Throughout this project, I will use uint8_t over unsigned int because uint8_t takes up less memory, and the range of an int isn't necessary for this project.
Note: Code formatting on Instructables doesn't work well. I spent an hour or so trying different methods, but none of them worked reliably. Look at the code.ino file in the Github repository if you are ever confused.
It will also be helpful to have a set of constants for representing specific delays.
Set Up Each Pin
Configure each pin using the builtin pinMode() function. For the button pins, they should be configured as INPUT_PULLUP. When a pin is not connected to power or ground, it is called a "floating pin." Trying to read from a floating pin will result in random behavior from surrounding electrical noise. INPUT_PULLUP pins fix this by configuring the Arduino's internal pull-up resistors. Because of the pull-up resistors, floating inputs will be treated as HIGH, which is why the buttons connect to ground (LOW). The LED and display pins should be configured as OUTPUT. See Digital Pins | Arduino Documentation for more details.
Add Color Type
It is useful to have a way of representing colors agnostic of the button and LED pins. At the top of the file, add this enum.
Add Game State
Now, add a struct for representing the game state. The struct will have three fields: a boolean representing if the player has lost, the sequence of colors, and the current round (which corresponds the length of the sequence). Also create a instance of the Gamestate struct called game_state and a function for initializing that instance.
Add Game Logic
The logic for the game is fairly simple. In the loop() function, add the following code.
During each round, a random color is generated and added to the sequence. In order to generate a new random sequence each time the user plays, you need to call randomSeed() in the setup function. One good way to seed the random number generation is by calling analogRead() on an unconnected analog pin.
After a new color is generated and added to the sequence, the sequence is displayed to the user using the LEDs.
For each color in the sequence, the user needs to push a button. The button can be pressed or held and it will still count as one button press. In the the Buttons namespace, add an array containing all of the buttons and an int containing the number of buttons. The "constexpr" keyword is used because it can be optimized more than a simple const.
Finally, put all of this logic into a start_round() function.
Add Loss Display
When the player loses, all of the LEDs should turn on, then the player's score will be displayed using the seven-segment display. The display can only show one digit at a time, so the score has to be displayed digit by digit with a pause at the end. In the Display namespace, add a function for converting a binary digit to a the corresponding output pins.
Then, add a function for displaying each digit of the number. The last digit of the number can be found using the modulo (remainder) operator. To display the digits in order, it's necessary to "shift" the number to the right by dividing by a power of 10.
Now, add the display_loss function above setup()
Add Startup Sequence
To test that the LEDs are all working and to give the player time to prepare, there is a startup sequence. All the LEDs will turn on in ascending order, then they will alternate between being on and off.
And that's it. The game is now fully functional. You can find the full source code in the Github repository.