How To Build a Simon Game with Raspberry Pi Pico

Simon Game with Raspberry Pi Pico
(Image credit: Tom's Hardware)

Simon says, Happy Birthday Raspberry Pi Pico! Released on January 21 2021, Raspberry Pi’s first chip, the RP2040 became the must have microcontroller for 2021. The $0.70 chip is powering projects across the world. From simple flashing LEDs to 3D printer control boards the RP2040 is a versatile chip. Announced via the Raspberry Pi Pico, a $4 microcontroller development board which resembles the DIL package used on modern Arduino boards, the Pico is low cost, first party hardware with great first and third party support.

We love the Pico, it is the right mix of low cost and ease of use that many makers crave. The ability to reuse Raspberry Pi add-ons and general electronic components means that we don’t have to go out and buy more accessories. Not that the Pico is lacking in that department. Companies such as Pimoroni, Adafruit and Kitronik have released awesome accessories designed to enhance your Pico experience. 

To celebrate Raspberry Pi Pico’s first birthday we made a game. It’s not just any game though, but our own version of Simon, the memory game made famous in the late 1970s. The aim of the game is to match the randomly chosen color sequence. If we get it right, we get a lightshow. But get it wrong, and we will be seeing red.

The hardware build for this project is all breadboard based, a nod to the simplicity afforded by the Raspberry Pi Pico. The wiring is simple: we have one output, an eight LED NeoPixel stick, and four inputs in the form of color coded buttons. The Pico is programmed using CircuitPython, Adafruit’s excellent fork of MicroPython with easy to use hardware libraries.

So let's get this party started!

For this project you will need 

Building Simon Game with Raspberry Pi Pico

There are two parts to the build.The four push buttons form the user interface, and the NeoPixel stick is our output.

(Image credit: Tom's Hardware)

The NeoPixel is connected to GPIO 28 (GP28 in CircuitPython) for data, and uses the 5V (VBUS) pin for power directly from the USB bus. GND connects to any GND on the Pico.

(Image credit: Tom's Hardware)

The four buttons connect to four GPIO pins on the Pico.

Swipe to scroll horizontally
Button ColorRaspberry Pi Pico GPIO
RedGPIO 27
GreenGPIO 26
BlueGPIO 22
YellowGPIO 21

And using the negative rail (marked with a -) on the button breadboard, we take one GND connection (Black wire) from the Pico and create a GND rail. From there, we spur four GND connections to the buttons. This means our buttons will pull to GND (Ground) when pressed. 

(Image credit: Tom's Hardware)

The final circuit looks like the above. Check your wiring before moving onwards.

Install CircuitPython and libraries

Installing CircuitPython is extremely simple and Adafruit has a fantastic guide on how to do this. Follow Adafruit’s instructions to install the latest version. 

(Image credit: Tom's Hardware)

Our Raspberry Pi Pico now appears as a drive in the file manager. The drive is called CIRCUITPY and there we find a series of files and folders. Right now, we need to focus on lib. We need to download a ZIP file full of libraries, and install the correct files for our projects.

1. Download the latest bundle of libraries from the CircuitPython site.

(Image credit: Tom's Hardware)

2. Extract the files to your Desktop.

3. Copy adafruit_pixelbuf.mpy and neopixel.mpy from the extracted files to the lib folder on your CIRCUITPY drive. 

(Image credit: Tom's Hardware)

Writing the Code for Simon Game on Raspberry Pi Pico

1. Open the code.py file found in the root of the CIRCUITPY drive in your preferred editor.

2. Delete any code in the file.

3. Import libraries to control the speed of the game, and for access to the GPIO. Time and Board are used common CircuitPython libraries. Time contains functions to pause the code, whereas Board provides basic GPIO access.

import time
import board

4. Import two more libraries for advanced control of NeoPixels. Colorwheel is an animation tool that we will use to create a rainbow cycle effect. NeoPixel is the library used to control the eight RGB LEDs.

from rainbowio import colorwheel
import neopixel

5. Import two final libraries. From random import choice and uniform, and from digitailio import DigitalInOut, Direction and Pull to handle input / output, pin direction and support for pull up resistors (see resistor color codes for help identifying different sizes). Random has many functions but choice enables us to pick “randomly” from lists, tuples, dictionaries etc. Uniform is a random number generator for float numbers. DigitalIO works with the Board library to finetune how we use the GPIO. In this case we want to use inputs (buttons) and set the internal resistor of the GPIO pins so that it pulls up (3.3V).

from random import choice, uniform
from digitalio import DigitalInOut, Direction, Pull

6. Setup GPIO 28 as the pin used to control our NeoPixels, then create a variable called num_pixels to store the number of NeoPixel LEDs.

pixel_pin = board.GP28
num_pixels = 8

7. Create an object, “pixels” and use it to connect to the NeoPixels using the two variables that identify the GPIO pin and the number of pixels. We also set the brightness to 0.3 (30%) to reduce eye strain.

pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.3, auto_write=False)

8. Create an object, red, which represents the red button connected to GPIO 27.

red = DigitalInOut(board.GP27)

9. Set the GPIO pin for the red button as an input.

red.direction = Direction.INPUT

10. Set the GPIO pin for the red button so that it pulls high. Using a pull up resistor we pull GPIO 27 high to 3.3V. The way we have connected the button to the Pico will pull this pin to GND (low) when the red button is pressed.

red.pull = Pull.UP

11. Repeat the process for the green button.

green = DigitalInOut(board.GP26)
green.direction = Direction.INPUT
green.pull = Pull.UP

12. Repeat again for blue.

blue = DigitalInOut(board.GP22)
blue.direction = Direction.INPUT
blue.pull = Pull.UP

13. Repeat again for yellow.

yellow = DigitalInOut(board.GP21)
yellow.direction = Direction.INPUT
yellow.pull = Pull.UP

14. Create five tuples (data storage objects) to store the RGB color values for the buttons and to turn the LEDs off.

RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 150, 0)
OFF = (0, 0, 0)

15. Create a function, rainbow_cycle which will cycle through all of the NeoPixels, displaying a rainbow effect. This function is part of Adafruit’s standard “strand test” demo.

def rainbow_cycle(wait):
   for j in range(255):
       for i in range(num_pixels):
           rc_index = (i * 256 // num_pixels) + j
           pixels[i] = colorwheel(rc_index & 255)
       pixels.show()
       time.sleep(wait)

16. Create a list called “colors,” and use it to store the color tuples we created earlier.

colors = [RED, GREEN, BLUE, YELLOW]

17. Create a for loop which will repeat 10 times.  Each time the loop iterates it uses the rainbow_cycle function to create a light show when the game starts.

for i in range(10):
   rainbow_cycle(0)

18. The main body of the project is a while True, which will run the game code. Create a while True loop and call the pixels object to turn off the LEDs.

while True:
   pixels.fill(OFF)
   pixels.show()

19. Create two blank lists to store the randomly chosen color sequence, and to store the players guesses.

   rnd_colors = []
   player = []

20. Create an if conditional test to trigger the game to start. To start the game we need to press the green button. As this pin is pulled high using a pull up resistor, we need to trigger the game to start when the pin is low (False). The buttons are connected to GPIO pins pulled high, and to GND. So when the button is pressed the pins are pulled low.

   if green.value == False:

21. Create a variable, “delay,” to store a randomly chosen float value between 0.3 and 1.0. Wrap the round() function around this to format the output to one decimal place. Print the output to the Python shell for debug. This will generate a random number between 0.3 and 1, used to create a pause between the color sequence shown to the player.

       delay = round(uniform(0.3,1.0),1)
       print(delay)

22. Use a for loop to add four randomly chosen colors to the rnd_colors list and print them to the Python shell for debug. These will be the colors that the player has to guess.

       for i in range(4):
           rnd_colors.append(choice(colors))
           print(rnd_colors)

23. Show the color sequence to the player. This for loop iterates over the four colors stored in the rnd_colors list. Each time the loop iterates the RGB value is used with pixels.fill() to show that color. 

       for color in rnd_colors:
           print(color)
           pixels.fill(color)
           pixels.show()

24. Use the randomly chosen delay to show the color then set turn the LEDs off, using the same delay.

           time.sleep(delay)
           pixels.fill(OFF)
           pixels.show()
           time.sleep(delay)

25. Use a while loop to check the length of the player list, and add a 0.3 second delay to this loop to reduce the chance of double button presses (debounce) The indented code will only run while there are less than four guesses by the player.

       while len(player) <4:
           time.sleep(0

26. Create an if statement to read the state of each button, store the color pressed and show the value for debug. First is the red button. When pressed it will append the RGB color value to the player list. Then print the value to the Python shell for debug.

           if red.value == False:
               player.append((255,0,0))
               print(player)

27. To give the player feedback, set the NeoPixels to their chosen color for 0.1 seconds before turning the pixels off.

               time.sleep(0.1)
               pixels.fill(OFF)
               pixels.show()

28. Repeat the process for the green button. Use an elif (else if) to check if this condition is true.

          elif green.value == False:
               player.append((0,255,0))
               print(player)
               pixels.fill(GREEN)
               pixels.show()
               time.sleep(0.1)
               pixels.fill(OFF)
               pixels.show()

29. Repeat again for the blue button.

           elif blue.value == False:
               player.append((0,0,255))
               print(player)
               pixels.fill(BLUE)
               pixels.show()
               time.sleep(0.1)
               pixels.fill(OFF)
               pixels.show()

30. Repeat again for the yellow button.

           elif yellow.value == False:
               player.append((255, 150, 0))
               print(player)
               pixels.fill(YELLOW)
               pixels.show()
               time.sleep(0.1)
               pixels.fill(OFF)
               pixels.show()

31. Use an if conditional test to check the players guesses against the randomly chosen values. Once the player has entered their four guesses, they are compared to the values chosen at the start of the game.

       if player == rnd_colors:

32. Use a for loop and set it to iterate 10 times to run the rainbow_cycle() signifying that the player has won.

           for i in range(10):
               rainbow_cycle(0)

33. Create an else condition which will flash the NeoPixels three times. The player will see the NeoPixels flash red if they guess incorrectly.

       else:
           for i in range(3):
               pixels.fill(RED)
               pixels.show()
               time.sleep(0.2)
               pixels.fill(OFF)
               pixels.show()
               time.sleep(0.1)

34. Save the code to your Raspberry Pi Pico as code.py and the game will start by running the rainbow_cycle() function.

35. Start the game by pressing the green button.

Complete Code Listing for Simon Game on Raspberry Pi Pico

import time
import board
from rainbowio import colorwheel
import neopixel
from random import choice, uniform
from digitalio import DigitalInOut, Direction, Pull

#NeoPixel setup
pixel_pin = board.GP28
num_pixels = 8
pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.3, auto_write=False)
#Button Setup
red = DigitalInOut(board.GP27)
red.direction = Direction.INPUT
red.pull = Pull.UP

green = DigitalInOut(board.GP26)
green.direction = Direction.INPUT
green.pull = Pull.UP

blue = DigitalInOut(board.GP22)
blue.direction = Direction.INPUT
blue.pull = Pull.UP

yellow = DigitalInOut(board.GP21)
yellow.direction = Direction.INPUT
yellow.pull = Pull.UP

# RGB Color Codes
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 150, 0)
OFF = (0, 0, 0)

#Rainbow Animation
def rainbow_cycle(wait):
    for j in range(255):
        for i in range(num_pixels):
            rc_index = (i * 256 // num_pixels) + j
            pixels[i] = colorwheel(rc_index & 255)
        pixels.show()
        time.sleep(wait)
#List of colors
colors = [RED, GREEN, BLUE, YELLOW]
#The "attract sequence to entice people to play
for i in range(10):
    rainbow_cycle(0)

while True:
    pixels.fill(OFF)
    pixels.show()
    rnd_colors = []
    player = []
#Wait for the green button start the game
    if green.value == False:
        delay = round(uniform(0.3,1.0),1)
        print(delay)
        for i in range(4):
            rnd_colors.append(choice(colors))
            print(rnd_colors)
        for color in rnd_colors:
            print(color)
            pixels.fill(color)
            pixels.show()
            time.sleep(delay)
            pixels.fill(OFF)
            pixels.show()
            time.sleep(delay)
#Player has four guesses, each guess saves the color choice to player list
        while len(player) <4:
            time.sleep(0.3)
            if red.value == False:
                player.append((255,0,0))
                print(player)
                pixels.fill(RED)
                pixels.show()
                time.sleep(0.1)
                pixels.fill(OFF)
                pixels.show()
            elif green.value == False:
                player.append((0,255,0))
                print(player)
                pixels.fill(GREEN)
                pixels.show()
                time.sleep(0.1)
                pixels.fill(OFF)
                pixels.show()
            elif blue.value == False:
                player.append((0,0,255))
                print(player)
                pixels.fill(BLUE)
                pixels.show()
                time.sleep(0.1)
                pixels.fill(OFF)
                pixels.show()
            elif yellow.value == False:
                player.append((255, 150, 0))
                print(player)
                pixels.fill(YELLOW)
                pixels.show()
                time.sleep(0.1)
                pixels.fill(OFF)
                pixels.show()
#If the player guesses correctly they get a nice rainbow
        if player == rnd_colors:
            for i in range(10):
                rainbow_cycle(0)
#If you lose, you see red!
        else:
            for i in range(3):
                pixels.fill(RED)
                pixels.show()
                time.sleep(0.2)
                pixels.fill(OFF)
                pixels.show()
                time.sleep(0.1)
Les Pounder

Les Pounder is an associate editor at Tom's Hardware. He is a creative technologist and for seven years has created projects to educate and inspire minds both young and old. He has worked with the Raspberry Pi Foundation to write and deliver their teacher training program "Picademy".