Skip to content

Commit 1bebd9a

Browse files
authored
Merge pull request #2992 from FoamyGuy/metro_rp2350_snakegame
Metro rp2350 snake game
2 parents c322fbd + e346383 commit 1bebd9a

File tree

4 files changed

+613
-0
lines changed

4 files changed

+613
-0
lines changed

Metro/Metro_RP2350_Snake/code.py

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

0 commit comments

Comments
 (0)