"""
A simon memory game for the Pi Pico.
The user pushes buttons in a specific order shown
by the LEDs.
"""
import random
import time
from machine import Pin
# The pin numbers can be adjusted as needed
BUTTONS = [
Pin(0, Pin.IN, Pin.PULL_UP), # Red
Pin(28, Pin.IN, Pin.PULL_UP), # Green
Pin(27, Pin.IN, Pin.PULL_UP), # Blue
Pin(1, Pin.IN, Pin.PULL_UP), # Yellow
]
# The number of leds/buttons can also be adjusted
LEDS = [
Pin(14, Pin.OUT), # Red
Pin(17, Pin.OUT), # Green
Pin(16, Pin.OUT), # Blue
Pin(15, Pin.OUT), # Yellow
]
time.sleep_ms(100) # Wait for USB to become ready
def wait_for_button(buttons: list[Pin], timeout_ms=None) -> int | None:
"""
Waits for any of the buttons in the list to be pressed. Returns the
index of the button pressed in the list if one is pressed.
If `timeout_ms` is provided, will return None after the
timeout is over.
"""
timer = time.ticks_ms()
while True:
for i, button in enumerate(buttons):
if not button.value():
print("Button", i, "pressed")
return i
if (
timeout_ms is not None
and time.ticks_diff(time.ticks_ms(), timer) > timeout_ms
):
return None
time.sleep_ms(1) # Speeds up simulation
def wait_for_release(button: Pin, debounce_ms: int = 20):
"""
Waits for the button to be released. The button release will be
debounced.
"""
timer = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), timer) < debounce_ms:
if not button.value():
timer = time.ticks_ms()
time.sleep_ms(1) # Speeds up simulation
def set_leds(leds: list[Pin], value: bool):
"""
Turns off all leds in the list.
"""
for led in leds:
led.value(value)
class GameState:
"""
A list of different states the game can be in.
"""
Start = 1
Game = 2
class Game:
"""
Handles the game logic.
"""
sequence: list[int]
state: GameState
def __init__(self):
self.sequence = []
self.state = GameState.Start
def main_loop(self):
"""
This main loop runs forever and figures out what needs to run
depending on the game state.
"""
while True:
if self.state == GameState.Start:
self.start_state()
elif self.state == GameState.Game:
self.game_state()
else:
raise NotImplementedError()
def start_state(self):
"""
Handles the start state. This state basically waits for a
button to be pressed, then starts the game.
"""
led = 0
LEDS[led].on()
button = None
while button is None:
button = wait_for_button(BUTTONS, timeout_ms=200)
LEDS[led].off()
led += 1
if led >= len(LEDS):
led = 0
LEDS[led].on()
LEDS[led].off()
wait_for_release(BUTTONS[button])
time.sleep_ms(1000)
self.start_game()
def start_game(self):
"""
Picks the first random sequence, and initializes the
game state.
"""
print("Start game")
random.seed(time.ticks_cpu())
self.sequence = [random.randint(0, len(LEDS) - 1) for _ in range(3)]
self.state = GameState.Game
def game_state(self):
"""
Handles the game state. This state displays the pattern,
recieves the pattern from the user, and ends or continues
the game.
"""
self.display_sequence()
if self.read_sequence():
self.sequence.append(random.randint(0, len(LEDS) - 1))
time.sleep_ms(100)
else:
self.game_end()
def display_sequence(self, min_speed_ms: int = 100):
"""
Displays the sequence with increasing speed.
"""
speed = max(min_speed_ms, 1500 - len(self.sequence) * 100)
ms_on = speed // 2
ms_off = speed // 2 + speed // 4
for led in self.sequence:
time.sleep_ms(ms_off)
LEDS[led].on()
time.sleep_ms(ms_on)
LEDS[led].off()
def read_sequence(self, flash_ms: int = 500, timeout_ms: int = 9_500) -> bool:
"""
Receives the sequence from the user. Returns true
if the user correctly enters the sequence.
The user has flash_ms + timeout_ms time to enter the
button or false will be returned.
"""
button = wait_for_button(BUTTONS, flash_ms)
for correct_button in self.sequence:
if button is None:
button = wait_for_button(BUTTONS, timeout_ms)
if button != correct_button:
return False
LEDS[correct_button].on()
wait_for_release(BUTTONS[correct_button])
button = wait_for_button(BUTTONS, flash_ms)
LEDS[correct_button].off()
return True
def game_end(self):
"""
Ends the game.
"""
print("Game end")
print("Sequence:", self.sequence)
set_leds(LEDS, 1)
time.sleep_ms(1500)
set_leds(LEDS, 0)
time.sleep_ms(1000)
self.display_sequence(1000)
time.sleep_ms(1000)
for _ in range(3):
set_leds(LEDS, 0)
time.sleep_ms(250)
set_leds(LEDS, 1)
time.sleep_ms(250)
set_leds(LEDS, 0)
time.sleep_ms(500)
self.state = GameState.Start
if __name__ == "__main__":
game = Game()
game.main_loop()