One of the biggest challenges with this game was getting the keyboard input correct. Normally, the way a game handles keyboard input is by keeping track of which keys are pressed by monitoring Key Down and Key Up events. The way that CircuitPython handles the game is by allowing only a single keypress at a time and if you continue holding the key, it waits about half a second to send additional keypresses. Because of this limitation, you can move a bit faster and more accurately by rapidly tapping the arrow key that you want to use.
Additionally, for many of the keys such as the arrows, it adds a multibyte key sequence to the keyboard buffer. I wrote a Keyboard Input class that allows a set of valid key sequences to be set and it will only return the key once it has a full packet and it will discard anything else. It works quite well and includes the ability to even work with control characters. In order to keep the game simple, I decided to only use hotkeys instead of a menu system, so all input is done using the keyboard.
# SPDX-FileCopyrightText: 2025 Melissa LeBlanc-Williams # # SPDX-License-Identifier: MIT import sys import supervisor class KeyboardBuffer: def __init__(self, valid_sequences): self.key_buffer = "" self._valid_sequences = valid_sequences def update(self): while supervisor.runtime.serial_bytes_available: self.key_buffer += sys.stdin.read(1) def print(self): print("buffer", end=": ") for key in self.key_buffer: print(hex(ord(key)), end=" ") def set_valid_sequences(self, valid_sequences): self._valid_sequences = valid_sequences def clear(self): self.key_buffer = "" def get_key(self): """ Check for keyboard input and return the first valid key sequence. """ # Check if serial data is available self.update() if self.key_buffer: for sequence in self._valid_sequences: if self.key_buffer.startswith(sequence): key = sequence self.key_buffer = self.key_buffer[len(sequence):] return key # Remove first character self.key_buffer = self.key_buffer[1:] return None
I created 3 sets of valid inputs which map the key sequence to the return value and the game switches between them as needed. These are found in the definitions.py file and include:Â
- GAMEPLAY_COMMANDS: Used for normal gameplay and includes the arrow keys as well as any hotkeys.
- MESSAGE_COMMANDS: Only allows either the enter key or spacebar and is used to dismiss informational dialogs.
- PASSWORD_COMMANDS: Used for typing in the level number or password into the the password boxes.
# Command Constants UP = const(0) LEFT = const(1) DOWN = const(2) RIGHT = const(3) NEXT_LEVEL = const(4) PREVIOUS_LEVEL = const(5) RESTART_LEVEL = const(6) GOTO_LEVEL = const(7) PAUSE = const(8) QUIT = const(9) OK = const(10) CANCEL = const(11) CHANGE_FIELDS = const(12) DELCHAR = const(13) # Keycode Constants UP_ARROW = const("\x1b[A") DOWN_ARROW = const("\x1b[B") RIGHT_ARROW = const("\x1b[C") LEFT_ARROW = const("\x1b[D") SPACE = const(" ") CTRL_G = const("\x07") # Ctrl+G CTRL_N = const("\x0E") # Ctrl+N CTRL_P = const("\x10") # Ctrl+P CTRL_Q = const("\x11") # Ctrl+Q CTRL_R = const("\x12") # Ctrl+R BACKSPACE = const("\x08") TAB = const("\x09") ENTER = const("\n") ESC = const("\x1b") # Mapping Buttons to Commands for different modes GAMEPLAY_COMMANDS = { UP_ARROW: UP, LEFT_ARROW: LEFT, DOWN_ARROW: DOWN, RIGHT_ARROW: RIGHT, SPACE: PAUSE, CTRL_G: GOTO_LEVEL, CTRL_N: NEXT_LEVEL, CTRL_P: PREVIOUS_LEVEL, CTRL_Q: QUIT, CTRL_R: RESTART_LEVEL, } MESSAGE_COMMANDS = { ENTER: OK, SPACE: OK, } # Password commands include only letters, enter, tab, and backspace PASSWORD_COMMANDS = { ESC: CANCEL, TAB: CHANGE_FIELDS, ENTER: OK, BACKSPACE: DELCHAR, } # The rest are input characters for i in range(65, 91): PASSWORD_COMMANDS[chr(i)] = chr(i) for i in range(97, 123): PASSWORD_COMMANDS[chr(i)] = chr(i) for i in range(48, 58): PASSWORD_COMMANDS[chr(i)] = chr(i)
Page last edited April 07, 2025
Text editor powered by tinymce.