The code for the game is thoroughly commented with explanations of what each line or section are for. This page will provide a higher level summary of the major components.
Hardware Principals
This game is designed around two primary hardware peripherals: the HSTX connector with a DVI breakout for the display, and a basic USB keyboard for the player control input.
HSTX Display
To initialize the display the built-in core modules picodvi
, and framebufferio
are used. These modules support a few different resolutions and color depths. This project is made for the 320x240 resolution with 16 bit color depth. The pixels are automatically doubled before being pushed to the display so it will come out as 640x480, depending on your monitor or TV, it may further upscale it to fit the screen.
USB Keyboard
USB Host is relatively new to CircuitPython, first coming on Raspberry Pi RP2040-based boards.
Typically, one would access a USB port by:
- Establishing the USB connection
- Reading USB Reports, sections of bytes sent when an action occurs on the peripheral like a key is pressed or joystick moved.
- Parsing the reports and providing meaningful input to the program.
Python has the concept of standard input and output streams, similar to those in Linux/Unix and other operating systems. CircuitPython has this capability and through a lot of behind the scenes code, presents a USB keyboard as a stdin input device. The code to get USB Host Keyboard characters and echo them to serial out is as follows:
import supervisor import sys while True: available = supervisor.runtime.serial_bytes_available if available: c = sys.stdin.read(available) print(c, end='')
Helper Classes
The game code has 4 helper classes which contain behavior for various parts of the game bundled together as easy to use component objects.
Snake
The Snake
class doesn't extend any other class and isn't responsible for any visual elements directly. It holds a list of x,y locations within the world grid for each segment of the snake's body, and the direction that the snake is currently moving in. It has a function to grow by one segment size. It also has some convenience functions to access the snake length, and the head and tail segments.
World
The World
class extends TileGrid and represents the playing field for this game. It handles all of the visual elements directly related to game play. It is responsible for:
- Spawning apples into the world.
- Drawing the snake by setting the appropriate sprite indexes for each snake segment.
- Moving the snake by updating the list of segment locations.
- Checking whether the snake has hit an edge or an apple.
SpeedAdjuster
SpeedAdjuster
is responsible for mapping a speed rating from 0
-20
to a timing delay ranging from 0.05
to 0.4
. Smaller speed ratings map to lower delay times which in turn make the snake move faster. The delay time is how long the game will wait before rendering each from, so less delay means faster rendering and movement. SpeedAdjuster
contains convenience functions for increasing and decreasing the speed by 1 unit, which are used when the two different colors of apples are eaten.
GameOverException
This is a basic custom exception that will raise when the player loses by running into the edge of the world, or wrapping around and running the snake into itself. code.py will catch this exception and show the game over message.
code.py
The code.py script is based upon a state machine. There are 4 states that it can be in:
- Title - This is the state that the code begins in. It displays the title splash and the controls that are currently mapped. While this state is active, the code waits for any key to be pressed and when that occurs it changes the displays
root_group
to thegame_group
and changes the state to playing. - Playing - The playing state is for active game play the snake moves around and the keyboard input is polled looking for the movement keys or the pause key. When movement keys are detected the direction of the snake is updated, when the pause key is pressed the state is changed to paused. If the snake runs into an edge or itself then the
GameOverException
is raised and the state is changed to gameover. - Paused - During the
paused
state the main game loop does not run, instead the code just waits for keyboard input, and when the pause key is pressed the state is changed back to playing. - Gameover - When the game has ended the game over message is updated to include the players score and then made visible on the screen. Keyboard input is polled, if the P key is pressed the game launches itself again, if the Q key is pressed then the code exits by breaking out of the main loop.
Display Elements
The graphical elements are split into two main Group
s. The title_group
holds the title splash screen image and the instructions text that go beneath it. It's set as the root group when the code first runs to make it visible.
The game_group
contains the World
instance which is the TileGrid
representing the game play area. It also holds the score_txt
for the top bar, and the game_over_label
which is made visible when the player loses. When the state is changed from title state to playing state the root_group
of the display is updated to the game_group
.
Page last edited March 14, 2025
Text editor powered by tinymce.