Skip to content

Commit 2e54a02

Browse files
committed
metro rp2350 snake game
1 parent c322fbd commit 2e54a02

File tree

4 files changed

+595
-0
lines changed

4 files changed

+595
-0
lines changed

Metro/Metro_RP2350_Snake/code.py

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
import sys
4+
import time
5+
from micropython import const
6+
import supervisor
7+
import displayio
8+
import terminalio
9+
from adafruit_display_text.text_box import TextBox
10+
from snake_helpers import World, Snake, GameOverException, SpeedAdjuster
11+
12+
# state machine constant
13+
STATE_TITLE = const(0)
14+
STATE_PLAYING = const(1)
15+
STATE_PAUSED = const(2)
16+
STATE_GAME_OVER = const(3)
17+
18+
# begin in the title state
19+
CURRENT_STATE = STATE_TITLE
20+
21+
# movement key bindings, change to different letters if you want.
22+
KEY_UP = "w"
23+
KEY_LEFT = "a"
24+
KEY_DOWN = "s"
25+
KEY_RIGHT = "d"
26+
KEY_PAUSE = "t"
27+
28+
# how many segments the snake will start with
29+
INITIAL_SNAKE_LEN = 3
30+
31+
# variable for the players score
32+
score = 0
33+
34+
# default HSTX display gets initialized by default by circuitpython
35+
display = supervisor.runtime.display
36+
37+
# load title splash screen bitmap
38+
title_bmp = displayio.OnDiskBitmap("snake_splash.bmp")
39+
# create a tilegrid for the title splash screen
40+
title_tg = displayio.TileGrid(bitmap=title_bmp, pixel_shader=title_bmp.pixel_shader)
41+
42+
instructions_txt = TextBox(
43+
terminalio.FONT,
44+
text=f"Move: {KEY_UP}{KEY_LEFT}{KEY_DOWN}{KEY_RIGHT} Pause: {KEY_PAUSE}".upper(),
45+
width=title_bmp.width,
46+
height=16,
47+
align=TextBox.ALIGN_CENTER,
48+
)
49+
instructions_txt.anchor_point = (0, 0)
50+
instructions_txt.anchored_position = (0, title_bmp.height + 1)
51+
52+
# create a group for the title splash screen and put it in the center of the display
53+
title_group = displayio.Group()
54+
title_group.append(title_tg)
55+
title_group.append(instructions_txt)
56+
title_group.x = display.width // 2 - title_bmp.width // 2
57+
title_group.y = display.height // 2 - title_bmp.height // 2
58+
59+
# initialize SpeedAdjuster to control how fast the snake is moving
60+
speed_adjuster = SpeedAdjuster(12)
61+
62+
# initialize the world with enough room unused at the top for the score bar
63+
world = World(height=28, width=40)
64+
# move the world down to make room for the score bar
65+
world.y = 16
66+
67+
# initialize a Snake instance and grow it to the appropriate size
68+
snake = Snake(starting_location=[10, 10])
69+
for i in range(INITIAL_SNAKE_LEN - 1):
70+
snake.grow()
71+
72+
# add one of each type of apple to the world
73+
world.add_apple(snake=snake, apple_sprite_index=World.APPLE_RED_SPRITE_INDEX)
74+
world.add_apple(snake=snake, apple_sprite_index=World.APPLE_GREEN_SPRITE_INDEX)
75+
76+
# create a group to hold everything for the game
77+
game_group = displayio.Group()
78+
79+
# add the world to the game group
80+
game_group.append(world)
81+
82+
# create TextBox to hold the score in a bar at the top of the display
83+
score_txt = TextBox(
84+
terminalio.FONT, text=f"Score: {score}", color=0xFFFFFF, width=320, height=16
85+
)
86+
score_txt.anchor_point = (0, 0)
87+
score_txt.anchored_position = (0, 2)
88+
89+
# add the score text to the game group
90+
game_group.append(score_txt)
91+
92+
# create a TextBox to hold the game over message
93+
game_over_label = TextBox(
94+
terminalio.FONT,
95+
text="",
96+
color=0xFFFFFF,
97+
background_color=0x000000,
98+
width=display.width // 2,
99+
height=80,
100+
align=TextBox.ALIGN_CENTER,
101+
)
102+
# move it to the center of the display
103+
game_over_label.anchor_point = (0, 0)
104+
game_over_label.anchored_position = (
105+
display.width // 2 - game_over_label.width // 2,
106+
40,
107+
)
108+
109+
# make it hidden, we'll show it when the game is over.
110+
game_over_label.hidden = True
111+
112+
# add it to the game group
113+
game_group.append(game_over_label)
114+
115+
# set the title group to show on the display
116+
display.root_group = title_group
117+
118+
# draw the snake in it's starting location
119+
world.draw_snake(snake)
120+
121+
# timpstamp of the game step render
122+
prev_step_time = time.monotonic()
123+
124+
# variable to hold string read from the keyboard to get button presses
125+
cur_btn_val = None
126+
127+
while True:
128+
# current timestamp
129+
now = time.monotonic()
130+
131+
# check if there is any keyboard input
132+
available = supervisor.runtime.serial_bytes_available
133+
134+
# if there is some keyboard input
135+
if available:
136+
# read it into cur_btn_val
137+
cur_btn_val = sys.stdin.read(available)
138+
139+
else: # no keyboard input
140+
# set to None to clear out previous value
141+
cur_btn_val = None
142+
143+
# if the current state is title screen
144+
if CURRENT_STATE == STATE_TITLE:
145+
# if any button was pressed
146+
if cur_btn_val is not None:
147+
# set the visible group on the display to the game group
148+
display.root_group = game_group
149+
# update the current state to playing
150+
CURRENT_STATE = STATE_PLAYING
151+
152+
# if game is being played
153+
elif CURRENT_STATE == STATE_PLAYING:
154+
# if up button was pressed
155+
if cur_btn_val == KEY_UP:
156+
# if the snake is not already moving up or down
157+
if snake.direction not in (snake.DIRECTION_DOWN, snake.DIRECTION_UP):
158+
# change the direction to up
159+
snake.direction = snake.DIRECTION_UP
160+
# if down button was pressed
161+
if cur_btn_val == KEY_DOWN:
162+
# if the snake is not already moving up or down
163+
if snake.direction not in (snake.DIRECTION_DOWN, snake.DIRECTION_UP):
164+
# change the direction to down
165+
snake.direction = snake.DIRECTION_DOWN
166+
# if right button was pressed
167+
if cur_btn_val == KEY_RIGHT:
168+
# if the snake is not already moving left or right
169+
if snake.direction not in (snake.DIRECTION_LEFT, snake.DIRECTION_RIGHT):
170+
# change the direction to right
171+
snake.direction = snake.DIRECTION_RIGHT
172+
# if left button was pressed
173+
if cur_btn_val == KEY_LEFT:
174+
# if the snake is not already moving left or right
175+
if snake.direction not in (snake.DIRECTION_LEFT, snake.DIRECTION_RIGHT):
176+
# change direction to left
177+
snake.direction = snake.DIRECTION_LEFT
178+
# if the pause button was pressed
179+
if cur_btn_val == KEY_PAUSE:
180+
# change the state to paused
181+
CURRENT_STATE = STATE_PAUSED
182+
183+
# if it's time to render a step of the game
184+
if now >= prev_step_time + speed_adjuster.delay:
185+
try:
186+
# move the snake in the direction it's going
187+
result = world.move_snake(snake)
188+
189+
# if a red apple was eaten
190+
if result == World.APPLE_RED_SPRITE_INDEX:
191+
# decrease the speed to slow down movement
192+
speed_adjuster.decrease_speed()
193+
# award score based on current speed and snake size
194+
score += ((20 - speed_adjuster.speed) // 3) + snake.size
195+
# update the score text in the top bar
196+
score_txt.text = f"Score: {score}"
197+
198+
# if a green apple was eaten
199+
elif result == World.APPLE_GREEN_SPRITE_INDEX:
200+
# increase the speed to speed up movement
201+
speed_adjuster.increase_speed()
202+
# award score based on current speed and snake
203+
# size plus bonus points for green apple
204+
score += ((20 - speed_adjuster.speed) // 3) + 3 + snake.size
205+
# update the score text in the top bar
206+
score_txt.text = f"Score: {score}"
207+
208+
# if the game is over due to snake running into the edge or itself
209+
except GameOverException as e:
210+
# update the game over message with the score
211+
output_str = (
212+
f"Game Over\nScore: {score}\nPress P to play again\nPress Q to quit"
213+
)
214+
# set the message into the game over label
215+
game_over_label.text = output_str
216+
# make the game over label visible
217+
game_over_label.hidden = False
218+
# update the state to game over
219+
CURRENT_STATE = STATE_GAME_OVER
220+
221+
# store the timestamp to compare with next iteration
222+
prev_step_time = now
223+
224+
# if the game is paused
225+
elif CURRENT_STATE == STATE_PAUSED:
226+
# if the pause button was pressed
227+
if cur_btn_val == KEY_PAUSE:
228+
# change the state to playing so the game resumes
229+
CURRENT_STATE = STATE_PLAYING
230+
231+
# if the current state is game over
232+
elif CURRENT_STATE == STATE_GAME_OVER:
233+
# if the p button is pressed for play again
234+
if cur_btn_val == "p":
235+
# set next code file to this one
236+
supervisor.set_next_code_file(__file__)
237+
# reload
238+
supervisor.reload()
239+
# if the q button is pressed for exit
240+
if cur_btn_val == "q":
241+
# break out of main while True loop.
242+
break

0 commit comments

Comments
 (0)