From 2e54a0214d4750a15701b303d42bff750ff553a3 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 13 Mar 2025 13:46:33 -0500 Subject: [PATCH 1/3] metro rp2350 snake game --- Metro/Metro_RP2350_Snake/code.py | 242 ++++++++++++ Metro/Metro_RP2350_Snake/snake_helpers.py | 353 ++++++++++++++++++ Metro/Metro_RP2350_Snake/snake_splash.bmp | Bin 0 -> 82058 bytes .../Metro_RP2350_Snake/snake_spritesheet.bmp | Bin 0 -> 290 bytes 4 files changed, 595 insertions(+) create mode 100644 Metro/Metro_RP2350_Snake/code.py create mode 100644 Metro/Metro_RP2350_Snake/snake_helpers.py create mode 100644 Metro/Metro_RP2350_Snake/snake_splash.bmp create mode 100644 Metro/Metro_RP2350_Snake/snake_spritesheet.bmp diff --git a/Metro/Metro_RP2350_Snake/code.py b/Metro/Metro_RP2350_Snake/code.py new file mode 100644 index 000000000..66881e660 --- /dev/null +++ b/Metro/Metro_RP2350_Snake/code.py @@ -0,0 +1,242 @@ +# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT +import sys +import time +from micropython import const +import supervisor +import displayio +import terminalio +from adafruit_display_text.text_box import TextBox +from snake_helpers import World, Snake, GameOverException, SpeedAdjuster + +# state machine constant +STATE_TITLE = const(0) +STATE_PLAYING = const(1) +STATE_PAUSED = const(2) +STATE_GAME_OVER = const(3) + +# begin in the title state +CURRENT_STATE = STATE_TITLE + +# movement key bindings, change to different letters if you want. +KEY_UP = "w" +KEY_LEFT = "a" +KEY_DOWN = "s" +KEY_RIGHT = "d" +KEY_PAUSE = "t" + +# how many segments the snake will start with +INITIAL_SNAKE_LEN = 3 + +# variable for the players score +score = 0 + +# default HSTX display gets initialized by default by circuitpython +display = supervisor.runtime.display + +# load title splash screen bitmap +title_bmp = displayio.OnDiskBitmap("snake_splash.bmp") +# create a tilegrid for the title splash screen +title_tg = displayio.TileGrid(bitmap=title_bmp, pixel_shader=title_bmp.pixel_shader) + +instructions_txt = TextBox( + terminalio.FONT, + text=f"Move: {KEY_UP}{KEY_LEFT}{KEY_DOWN}{KEY_RIGHT} Pause: {KEY_PAUSE}".upper(), + width=title_bmp.width, + height=16, + align=TextBox.ALIGN_CENTER, +) +instructions_txt.anchor_point = (0, 0) +instructions_txt.anchored_position = (0, title_bmp.height + 1) + +# create a group for the title splash screen and put it in the center of the display +title_group = displayio.Group() +title_group.append(title_tg) +title_group.append(instructions_txt) +title_group.x = display.width // 2 - title_bmp.width // 2 +title_group.y = display.height // 2 - title_bmp.height // 2 + +# initialize SpeedAdjuster to control how fast the snake is moving +speed_adjuster = SpeedAdjuster(12) + +# initialize the world with enough room unused at the top for the score bar +world = World(height=28, width=40) +# move the world down to make room for the score bar +world.y = 16 + +# initialize a Snake instance and grow it to the appropriate size +snake = Snake(starting_location=[10, 10]) +for i in range(INITIAL_SNAKE_LEN - 1): + snake.grow() + +# add one of each type of apple to the world +world.add_apple(snake=snake, apple_sprite_index=World.APPLE_RED_SPRITE_INDEX) +world.add_apple(snake=snake, apple_sprite_index=World.APPLE_GREEN_SPRITE_INDEX) + +# create a group to hold everything for the game +game_group = displayio.Group() + +# add the world to the game group +game_group.append(world) + +# create TextBox to hold the score in a bar at the top of the display +score_txt = TextBox( + terminalio.FONT, text=f"Score: {score}", color=0xFFFFFF, width=320, height=16 +) +score_txt.anchor_point = (0, 0) +score_txt.anchored_position = (0, 2) + +# add the score text to the game group +game_group.append(score_txt) + +# create a TextBox to hold the game over message +game_over_label = TextBox( + terminalio.FONT, + text="", + color=0xFFFFFF, + background_color=0x000000, + width=display.width // 2, + height=80, + align=TextBox.ALIGN_CENTER, +) +# move it to the center of the display +game_over_label.anchor_point = (0, 0) +game_over_label.anchored_position = ( + display.width // 2 - game_over_label.width // 2, + 40, +) + +# make it hidden, we'll show it when the game is over. +game_over_label.hidden = True + +# add it to the game group +game_group.append(game_over_label) + +# set the title group to show on the display +display.root_group = title_group + +# draw the snake in it's starting location +world.draw_snake(snake) + +# timpstamp of the game step render +prev_step_time = time.monotonic() + +# variable to hold string read from the keyboard to get button presses +cur_btn_val = None + +while True: + # current timestamp + now = time.monotonic() + + # check if there is any keyboard input + available = supervisor.runtime.serial_bytes_available + + # if there is some keyboard input + if available: + # read it into cur_btn_val + cur_btn_val = sys.stdin.read(available) + + else: # no keyboard input + # set to None to clear out previous value + cur_btn_val = None + + # if the current state is title screen + if CURRENT_STATE == STATE_TITLE: + # if any button was pressed + if cur_btn_val is not None: + # set the visible group on the display to the game group + display.root_group = game_group + # update the current state to playing + CURRENT_STATE = STATE_PLAYING + + # if game is being played + elif CURRENT_STATE == STATE_PLAYING: + # if up button was pressed + if cur_btn_val == KEY_UP: + # if the snake is not already moving up or down + if snake.direction not in (snake.DIRECTION_DOWN, snake.DIRECTION_UP): + # change the direction to up + snake.direction = snake.DIRECTION_UP + # if down button was pressed + if cur_btn_val == KEY_DOWN: + # if the snake is not already moving up or down + if snake.direction not in (snake.DIRECTION_DOWN, snake.DIRECTION_UP): + # change the direction to down + snake.direction = snake.DIRECTION_DOWN + # if right button was pressed + if cur_btn_val == KEY_RIGHT: + # if the snake is not already moving left or right + if snake.direction not in (snake.DIRECTION_LEFT, snake.DIRECTION_RIGHT): + # change the direction to right + snake.direction = snake.DIRECTION_RIGHT + # if left button was pressed + if cur_btn_val == KEY_LEFT: + # if the snake is not already moving left or right + if snake.direction not in (snake.DIRECTION_LEFT, snake.DIRECTION_RIGHT): + # change direction to left + snake.direction = snake.DIRECTION_LEFT + # if the pause button was pressed + if cur_btn_val == KEY_PAUSE: + # change the state to paused + CURRENT_STATE = STATE_PAUSED + + # if it's time to render a step of the game + if now >= prev_step_time + speed_adjuster.delay: + try: + # move the snake in the direction it's going + result = world.move_snake(snake) + + # if a red apple was eaten + if result == World.APPLE_RED_SPRITE_INDEX: + # decrease the speed to slow down movement + speed_adjuster.decrease_speed() + # award score based on current speed and snake size + score += ((20 - speed_adjuster.speed) // 3) + snake.size + # update the score text in the top bar + score_txt.text = f"Score: {score}" + + # if a green apple was eaten + elif result == World.APPLE_GREEN_SPRITE_INDEX: + # increase the speed to speed up movement + speed_adjuster.increase_speed() + # award score based on current speed and snake + # size plus bonus points for green apple + score += ((20 - speed_adjuster.speed) // 3) + 3 + snake.size + # update the score text in the top bar + score_txt.text = f"Score: {score}" + + # if the game is over due to snake running into the edge or itself + except GameOverException as e: + # update the game over message with the score + output_str = ( + f"Game Over\nScore: {score}\nPress P to play again\nPress Q to quit" + ) + # set the message into the game over label + game_over_label.text = output_str + # make the game over label visible + game_over_label.hidden = False + # update the state to game over + CURRENT_STATE = STATE_GAME_OVER + + # store the timestamp to compare with next iteration + prev_step_time = now + + # if the game is paused + elif CURRENT_STATE == STATE_PAUSED: + # if the pause button was pressed + if cur_btn_val == KEY_PAUSE: + # change the state to playing so the game resumes + CURRENT_STATE = STATE_PLAYING + + # if the current state is game over + elif CURRENT_STATE == STATE_GAME_OVER: + # if the p button is pressed for play again + if cur_btn_val == "p": + # set next code file to this one + supervisor.set_next_code_file(__file__) + # reload + supervisor.reload() + # if the q button is pressed for exit + if cur_btn_val == "q": + # break out of main while True loop. + break diff --git a/Metro/Metro_RP2350_Snake/snake_helpers.py b/Metro/Metro_RP2350_Snake/snake_helpers.py new file mode 100644 index 000000000..52a7ad393 --- /dev/null +++ b/Metro/Metro_RP2350_Snake/snake_helpers.py @@ -0,0 +1,353 @@ +# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT +import random +import adafruit_imageload +from displayio import TileGrid + +X = 0 +Y = 1 + + +class Snake: + """ + Snake helper class keeps track of the current direction of the snake + and the x,y coordinates of all segments within the snake. + """ + + # direction constants + DIRECTION_UP = 0 + DIRECTION_DOWN = 1 + DIRECTION_RIGHT = 2 + DIRECTION_LEFT = 3 + + def __init__(self, starting_location=(10, 10)): + + # this list will hold locations of all segments + # we have only 1 segment when first initialized + self.locations = [list(starting_location)] + + # start in a random direction + self.direction = random.randint(0, 3) + + def grow(self): + """ + Grow the snake by 1 segment + """ + # check which direction we're currently going + if self.direction == self.DIRECTION_UP: + # new segment below the tail + new_segment = [self.tail[X], self.tail[Y] + 1] + elif self.direction == self.DIRECTION_DOWN: + # new segment above the tail + new_segment = [self.tail[X], self.tail[Y] - 1] + elif self.direction == self.DIRECTION_LEFT: + # new segment to the right of the tail + new_segment = [self.tail[X] + 1, self.tail[Y]] + elif self.direction == self.DIRECTION_RIGHT: + # new segment to the left of the tail + new_segment = [self.tail[X] - 1, self.tail[Y]] + else: + raise RuntimeError("Invalid Direction") + # add the new segment to our list + self.locations.append(new_segment) + + @property + def size(self): + """ + Length of the snake in segments + """ + return len(self.locations) + + def __len__(self): + """ + Length of the snake in segments + """ + return len(self.locations) + + @property + def head(self): + """ + The x,y coordinates of the head segment of the snake + """ + return self.locations[0] + + @property + def tail(self): + """ + The x,y coordinates of the tail segment of the snake + """ + return self.locations[-1] + + +class World(TileGrid): + """ + World helper class draws the world as a TileGrid and plots + apples and snakes within it. + """ + + # sprite tile indexes within the spritesheet + EMPTY_SPRITE_INDEX = 0 + SNAKE_SPRITE_INDEX = 1 + APPLE_RED_SPRITE_INDEX = 2 + APPLE_GREEN_SPRITE_INDEX = 3 + + def __init__(self, width=20, height=16): + + # load the spritesheet + self.spritesheet_bmp, self.spritesheet_palette = adafruit_imageload.load( + "snake_spritesheet.bmp" + ) + + # initialize the superclass TileGrid + super().__init__( + bitmap=self.spritesheet_bmp, + pixel_shader=self.spritesheet_palette, + width=width, + height=height, + tile_width=8, + tile_height=8, + default_tile=0, + ) + + def draw_snake(self, snake): + """ + Set the snake segments into the TileGrid at their current locations. + """ + # loop over all segment location x,y pairs + for location in snake.locations: + # update the TileGrid to show the snake sprite at this location + self[location] = self.SNAKE_SPRITE_INDEX + + def move_snake(self, snake): + # pylint: disable=too-many-branches + """ + Move the snake one step in its current direction + """ + # variable to hold what we will return + return_val = None + + # store the previous head location + prev_loc = tuple(snake.head) + + # variable for the next loc, starts on prev_loc and will be modified below + next_loc = [prev_loc[X], prev_loc[Y]] + + # if the snake is going up + if snake.direction == snake.DIRECTION_UP: + # if the snake is not at the top edge + if snake.head[Y] > 0: + # move the next_loc Y value north by 1 + next_loc[Y] -= 1 + + else: # snake is at top edge + raise GameOverException("Game Over - OOB Top") + + # if the snake is going down + if snake.direction == snake.DIRECTION_DOWN: + # if the snake is not at the bottom edge + if snake.head[Y] < self.height - 1: + # move the next_loc Y value south by 1 + next_loc[Y] += 1 + + else: # snake is at bottom edge + raise GameOverException("Game Over - OOB Bottom") + + # if the snake is going left + if snake.direction == snake.DIRECTION_LEFT: + # if the snake is not at the left edge + if snake.head[X] > 0: + # move the next_loc Y value west by 1 + next_loc[X] -= 1 + + else: # snake is at left edge + raise GameOverException("Game Over - OOB Left") + + # if snake is going right + if snake.direction == snake.DIRECTION_RIGHT: + # if snake is not at the right edge + if snake.head[X] < self.width - 1: + # move the next_loc Y value east by 1 + next_loc[X] += 1 + + else: # snake is at right edge + raise GameOverException("Game Over - OOB Right") + + # Check if there is an apple at the next_loc + if self[tuple(next_loc)] in ( + self.APPLE_RED_SPRITE_INDEX, + self.APPLE_GREEN_SPRITE_INDEX, + ): + # add the next_loc as a new segment to the snake + # it goes at the beginning of the list, making + # it the new head segment + snake.locations.insert(0, next_loc) + + # if it's a red apple + if self[tuple(next_loc)] == self.APPLE_RED_SPRITE_INDEX: + # add a new red apple to replace it + self.add_apple( + snake=snake, apple_sprite_index=self.APPLE_RED_SPRITE_INDEX + ) + # update the return_val to indicate red apple was eaten + return_val = self.APPLE_RED_SPRITE_INDEX + + # if it's a green apple + elif self[tuple(next_loc)] == self.APPLE_GREEN_SPRITE_INDEX: + # add a new green apple to replace it + self.add_apple( + snake=snake, apple_sprite_index=self.APPLE_GREEN_SPRITE_INDEX + ) + # update the return_val to indicate green apple was eaten + return_val = self.APPLE_GREEN_SPRITE_INDEX + + # check if a snake segment is at the next_loc + elif self[tuple(next_loc)] == self.SNAKE_SPRITE_INDEX: + # snake ran into itself + raise GameOverException("Game Over - Ran Into Self") + + # there is nothing at the next_loc + else: + # change the head segment location to next_loc + snake.locations[0] = next_loc + + # loop over all segments in the snake + for i, location in enumerate(snake.locations): + # skip the head segment, it's already been handled + if i != 0: + # temp var with this segment location + _tmp = tuple(location) + # update the X value of this segment to the X from prev_loc + snake.locations[i][X] = prev_loc[X] + # update the Y value of this segment to the Y from prev_loc + snake.locations[i][Y] = prev_loc[Y] + + # update prev_loc to the temp var + prev_loc = _tmp + + # if this is the tail segment + if i == len(snake.locations) - 1: # tail + # set this location to an empty tile + # it's now the empty tile behind the snake + self[prev_loc] = self.EMPTY_SPRITE_INDEX + + # draw the snake at the new location + self.draw_snake(snake) + + # return the apple that was eaten or None + return return_val + + def add_apple( + self, location=None, snake=None, apple_sprite_index=APPLE_RED_SPRITE_INDEX + ): + """ + Add a new apple to the world + """ + # check if snake was passed in + if snake is None: + # we need the snake so that we avoid putting an apple + # in the same spot as any of its segments. + raise AttributeError("Must pass snake") + + # loop until the location contains an empty tile + while location is None or self[location] != self.EMPTY_SPRITE_INDEX: + + # select a random location in the world + location = [ + random.randint(0, self.width - 1), + random.randint(0, self.height - 1), + ] + + # set the tile at the lcoation to the appropriate apple sprite tile index + self[tuple(location)] = apple_sprite_index + + +class SpeedAdjuster: + """ + SpeedAdjuster helper class keeps track of the speed of the snake + and provides easy to use increase and decrease speed functions. + + Speed is controlled by altering the delay time between + game steps. + """ + + # min and max delay values in seconds + MIN = 0.05 + MAX = 0.4 + + def __init__(self, speed): + """ + :param speed: speed as a value 0-20, lower is faster. + """ + # limit speed to the range 0-20 + if 0 > speed or speed > 20: + raise ValueError("Speed must be between 0 and 20") + + # store speed value on self + self.speed = speed + + # map the speed value to a delay value between the min and max + self.delay = SpeedAdjuster.map_range( + self.speed, 0, 20, SpeedAdjuster.MIN, SpeedAdjuster.MAX + ) + + def increase_speed(self): + """ + Speed up by 1 speed value + """ + + # if we aren't at max speed already + if self.speed > 0: + # speed value goes down by one because lower is faster + self.speed -= 1 + # update the delay with the new speed value mapped between min and max + self.delay = SpeedAdjuster.map_range( + self.speed, 0, 20, SpeedAdjuster.MIN, SpeedAdjuster.MAX + ) + + def decrease_speed(self): + """ + Slow down by 1 speed value + """ + # if we aren't at min speed already + if self.speed < 20: + # speed value goes up by one because higher is slower + self.speed += 1 + # update the delay with the new speed value mapped between min and max + self.delay = SpeedAdjuster.map_range( + self.speed, 0, 20, SpeedAdjuster.MIN, SpeedAdjuster.MAX + ) + + @staticmethod + def map_range(x, in_min, in_max, out_min, out_max): + """ + Maps a number from one range to another. + Note: This implementation handles values < in_min + differently than arduino's map function does. + Copied from circuitpython simpleio + + :return: Returns value mapped to new range + :rtype: float + """ + in_range = in_max - in_min + in_delta = x - in_min + if in_range != 0: + mapped = in_delta / in_range + elif in_delta != 0: + mapped = in_delta + else: + mapped = 0.5 + mapped *= out_max - out_min + mapped += out_min + if out_min <= out_max: + return max(min(mapped, out_max), out_min) + return min(max(mapped, out_max), out_min) + + +class GameOverException(Exception): + """ + Exception that indicates the game is over. + Message will contain the reason. + """ + + def __init__(self, message): + super().__init__(message) diff --git a/Metro/Metro_RP2350_Snake/snake_splash.bmp b/Metro/Metro_RP2350_Snake/snake_splash.bmp new file mode 100644 index 0000000000000000000000000000000000000000..d90fddb673a242cad4940686433f33055e47f425 GIT binary patch literal 82058 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 literal 0 HcmV?d00001 From a2e7224702a4b78b41615df148cb8006582bc474 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 13 Mar 2025 13:53:23 -0500 Subject: [PATCH 2/3] update display init for current released version --- Metro/Metro_RP2350_Snake/code.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Metro/Metro_RP2350_Snake/code.py b/Metro/Metro_RP2350_Snake/code.py index 66881e660..3c25a99da 100644 --- a/Metro/Metro_RP2350_Snake/code.py +++ b/Metro/Metro_RP2350_Snake/code.py @@ -3,6 +3,9 @@ import sys import time from micropython import const +import board +import picodvi +import framebufferio import supervisor import displayio import terminalio @@ -31,8 +34,26 @@ # variable for the players score score = 0 -# default HSTX display gets initialized by default by circuitpython -display = supervisor.runtime.display +# initialize display +displayio.release_displays() +fb = picodvi.Framebuffer( + 320, + 240, + clk_dp=board.CKP, + clk_dn=board.CKN, + red_dp=board.D0P, + red_dn=board.D0N, + green_dp=board.D1P, + green_dn=board.D1N, + blue_dp=board.D2P, + blue_dn=board.D2N, + color_depth=16, +) +display = framebufferio.FramebufferDisplay(fb) + +# In future release the default HSTX display +# will get initialized by default by circuitpython +# display = supervisor.runtime.display # load title splash screen bitmap title_bmp = displayio.OnDiskBitmap("snake_splash.bmp") From e346383916f47c721888493564c3198efde3d3e5 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 13 Mar 2025 14:29:05 -0500 Subject: [PATCH 3/3] change order of condition. remove init function from custom exception. --- Metro/Metro_RP2350_Snake/snake_helpers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Metro/Metro_RP2350_Snake/snake_helpers.py b/Metro/Metro_RP2350_Snake/snake_helpers.py index 52a7ad393..40b90c9eb 100644 --- a/Metro/Metro_RP2350_Snake/snake_helpers.py +++ b/Metro/Metro_RP2350_Snake/snake_helpers.py @@ -279,7 +279,7 @@ def __init__(self, speed): :param speed: speed as a value 0-20, lower is faster. """ # limit speed to the range 0-20 - if 0 > speed or speed > 20: + if speed < 0 or speed > 20: raise ValueError("Speed must be between 0 and 20") # store speed value on self @@ -348,6 +348,3 @@ class GameOverException(Exception): Exception that indicates the game is over. Message will contain the reason. """ - - def __init__(self, message): - super().__init__(message)