From 3eb7575ba23cf9b8f557cfae526ba7566f09352d Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 22 Nov 2024 22:30:57 +0800 Subject: [PATCH] python: Migrate snake to pygame Signed-off-by: Daniel Schaefer --- python/inputmodule/gui/__init__.py | 6 +- python/inputmodule/gui/games/__init__.py | 115 ---------- python/inputmodule/gui/games/snake.py | 254 +++++++++++++++++++++++ 3 files changed, 259 insertions(+), 116 deletions(-) create mode 100644 python/inputmodule/gui/games/snake.py diff --git a/python/inputmodule/gui/__init__.py b/python/inputmodule/gui/__init__.py index af8179c..7e4fe4a 100644 --- a/python/inputmodule/gui/__init__.py +++ b/python/inputmodule/gui/__init__.py @@ -109,6 +109,7 @@ def run_gui(devices): # Games tab games_frame = ttk.LabelFrame(tab_games, text="Games", style="TLabelframe") games_frame.pack(fill="x", padx=10, pady=5) + ttk.Button(games_frame, text="Snake", command=lambda: perform_action(devices, 'game_snake'), style="TButton").pack(side="left", padx=5, pady=5) ttk.Button(games_frame, text="Ledris", command=lambda: perform_action(devices, 'game_ledris'), style="TButton").pack(side="left", padx=5, pady=5) # Countdown Timer @@ -168,11 +169,14 @@ def run_gui(devices): for text, action in control_buttons.items(): ttk.Button(device_control_frame, text=text, command=lambda a=action: perform_action(devices, a), style="TButton").pack(side="left", padx=5, pady=5) + snake.main_devices(devices) + root.mainloop() def perform_action(devices, action): action_map = { - "game_ledris": ledris.main_devices + "game_snake": snake.main_devices, + "game_ledris": ledris.main_devices, } if action in action_map: threading.Thread(target=action_map[action], args=(devices,), daemon=True).start(), diff --git a/python/inputmodule/gui/games/__init__.py b/python/inputmodule/gui/games/__init__.py index 46df99d..c3d01d5 100644 --- a/python/inputmodule/gui/games/__init__.py +++ b/python/inputmodule/gui/games/__init__.py @@ -26,36 +26,6 @@ ARG_2LEFT = 5 ARG_2RIGHT = 6 -# Variables -direction = None -body = [] - - -def opposite_direction(direction): - if direction == keys.RIGHT: - return keys.LEFT - elif direction == keys.LEFT: - return keys.RIGHT - elif direction == keys.UP: - return keys.DOWN - elif direction == keys.DOWN: - return keys.UP - return direction - - -def snake_keyscan(): - global direction - global body - - while True: - current_dir = direction - key = getkey() - if key in [keys.RIGHT, keys.UP, keys.LEFT, keys.DOWN]: - # Don't allow accidental suicide if we have a body - if key == opposite_direction(current_dir) and body: - continue - direction = key - def snake_embedded_keyscan(dev): while True: @@ -76,18 +46,6 @@ def snake_embedded_keyscan(dev): send_command(dev, CommandVals.GameControl, [key_arg]) -def game_over(dev): - global body - while True: - show_string(dev, "GAME ") - time.sleep(0.75) - show_string(dev, "OVER!") - time.sleep(0.75) - score = len(body) - show_string(dev, f"{score:>3} P") - time.sleep(0.75) - - def pong_embedded(dev): # Start game send_command(dev, CommandVals.StartGame, [Game.Pong]) @@ -124,79 +82,6 @@ def snake_embedded(dev): snake_embedded_keyscan(dev) -def snake(dev): - global direction - global body - head = (0, 0) - direction = keys.DOWN - food = (0, 0) - while food == head: - food = (random.randint(0, WIDTH - 1), random.randint(0, HEIGHT - 1)) - - # Setting - WRAP = False - - thread = threading.Thread(target=snake_keyscan, args=(), daemon=True) - thread.start() - - prev = datetime.now() - while True: - now = datetime.now() - delta = (now - prev) / timedelta(milliseconds=1) - - if delta > 200: - prev = now - else: - continue - - # Update position - (x, y) = head - oldhead = head - if direction == keys.RIGHT: - head = (x + 1, y) - elif direction == keys.LEFT: - head = (x - 1, y) - elif direction == keys.UP: - head = (x, y - 1) - elif direction == keys.DOWN: - head = (x, y + 1) - - # Detect edge condition - (x, y) = head - if head in body: - return game_over(dev) - elif x >= WIDTH or x < 0 or y >= HEIGHT or y < 0: - if WRAP: - if x >= WIDTH: - x = 0 - elif x < 0: - x = WIDTH - 1 - elif y >= HEIGHT: - y = 0 - elif y < 0: - y = HEIGHT - 1 - head = (x, y) - else: - return game_over(dev) - elif head == food: - body.insert(0, oldhead) - while food == head: - food = (random.randint(0, WIDTH - 1), - random.randint(0, HEIGHT - 1)) - elif body: - body.pop() - body.insert(0, oldhead) - - # Draw on screen - matrix = [[0 for _ in range(HEIGHT)] for _ in range(WIDTH)] - matrix[x][y] = 1 - matrix[food[0]][food[1]] = 1 - for bodypart in body: - (x, y) = bodypart - matrix[x][y] = 1 - render_matrix(dev, matrix) - - def wpm_demo(dev): """Capture keypresses and calculate the WPM of the last 10 seconds TODO: I'm not sure my calculation is right.""" diff --git a/python/inputmodule/gui/games/snake.py b/python/inputmodule/gui/games/snake.py new file mode 100644 index 0000000..6568639 --- /dev/null +++ b/python/inputmodule/gui/games/snake.py @@ -0,0 +1,254 @@ +import pygame +import random +import time + +from inputmodule import cli +from inputmodule.inputmodule import ledmatrix + +# Set the screen width and height for a 34 x 9 block game +block_width = 20 +block_height = 20 +COLS = 9 +ROWS = 34 + +WIDTH = COLS * block_width +HEIGHT = ROWS * block_height + +# Colors +black = (0, 0, 0) +white = (255, 255, 255) + +def opposite_direction(direction): + if direction == pygame.K_RIGHT: + return pygame.K_LEFT + elif direction == pygame.K_LEFT: + return pygame.K_RIGHT + elif direction == pygame.K_UP: + return pygame.K_DOWN + elif direction == pygame.K_DOWN: + return pygame.K_UP + return direction + +# Function to get the current board state +def get_board_state(board): + temp_board = [row[:] for row in board] + #off_x, off_y = current_pos + #for y, row in enumerate(current_shape): + # for x, cell in enumerate(row): + # if cell: + # if 0 <= off_y + y < ROWS and 0 <= off_x + x < COLS: + # temp_board[off_y + y][off_x + x] = 1 + return temp_board + +def draw_ledmatrix(board, devices): + for dev in devices: + matrix = [[0 for _ in range(34)] for _ in range(9)] + for y in range(ROWS): + for x in range(COLS): + matrix[x][y] = board[y][x] + ledmatrix.render_matrix(dev, matrix) + #vals = [0 for _ in range(39)] + #send_command(dev, CommandVals.Draw, vals) + +# Function to display the score using blocks +def display_score(board, score): + return + score_str = str(score) + start_x = COLS - len(score_str) * 4 + for i, digit in enumerate(score_str): + if digit.isdigit(): + digit = int(digit) + for y in range(5): + for x in range(3): + if digit_blocks[digit][y][x]: + if y < ROWS and start_x + i * 4 + x < COLS: + board[y][start_x + i * 4 + x] = 1 + +# Digit blocks for representing score +# Each number is represented in a 5x3 block matrix +digit_blocks = [ + [[1, 1, 1], [1, 0, 1], [1, 0, 1], [1, 0, 1], [1, 1, 1]], # 0 + [[0, 1, 0], [1, 1, 0], [0, 1, 0], [0, 1, 0], [1, 1, 1]], # 1 + [[1, 1, 1], [0, 0, 1], [1, 1, 1], [1, 0, 0], [1, 1, 1]], # 2 + [[1, 1, 1], [0, 0, 1], [1, 1, 1], [0, 0, 1], [1, 1, 1]], # 3 + [[1, 0, 1], [1, 0, 1], [1, 1, 1], [0, 0, 1], [0, 0, 1]], # 4 + [[1, 1, 1], [1, 0, 0], [1, 1, 1], [0, 0, 1], [1, 1, 1]], # 5 + [[1, 1, 1], [1, 0, 0], [1, 1, 1], [1, 0, 1], [1, 1, 1]], # 6 + [[1, 1, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1]], # 7 + [[1, 1, 1], [1, 0, 1], [1, 1, 1], [1, 0, 1], [1, 1, 1]], # 8 + [[1, 1, 1], [1, 0, 1], [1, 1, 1], [0, 0, 1], [1, 1, 1]], # 9 +] + + +class Snake: + # Function to draw a grid + def draw_grid(self): + for y in range(ROWS): + for x in range(COLS): + rect = pygame.Rect(x * block_width, y * block_height, block_width, block_height) + pygame.draw.rect(self.screen, black, rect, 1) + + # Function to draw the game based on the board state + def draw_board(self, board, devices): + draw_ledmatrix(board, devices) + self.screen.fill(white) + for y in range(ROWS): + for x in range(COLS): + if board[y][x]: + rect = pygame.Rect(x * block_width, y * block_height, block_width, block_height) + pygame.draw.rect(self.screen, black, rect) + self.draw_grid() + pygame.display.update() + + # Main game function + def gameLoop(self, devices): + board = [[0 for _ in range(COLS)] for _ in range(ROWS)] + + game_over = False + body = [] + score = 0 + head = (0, 0) + direction = pygame.K_DOWN + food = (0, 0) + while food == head: + food = (random.randint(0, COLS - 1), random.randint(0, ROWS - 1)) + move_time = 0 + + # Setting + # Wrap and let the snake come out the other side + WRAP = False + MOVE_PERIOD = 200 + + while not game_over: + # Draw the current board state + board_state = get_board_state(board) + display_score(board_state, score) + self.draw_board(board_state, devices) + + # Event handling + for event in pygame.event.get(): + if event.type == pygame.QUIT: + game_over = True + + if event.type == pygame.KEYDOWN: + if event.key == opposite_direction(direction) and body: + continue + if event.key in [pygame.K_LEFT, pygame.K_h]: + direction = pygame.K_LEFT + elif event.key in [pygame.K_RIGHT, pygame.K_l]: + direction = pygame.K_RIGHT + elif event.key in [pygame.K_DOWN, pygame.K_j]: + direction = pygame.K_DOWN + elif event.key in [pygame.K_UP, pygame.K_k]: + direction = pygame.K_UP + + move_time += self.clock.get_time() + if move_time >= MOVE_PERIOD: + move_time = 0 + + # Update position + (x, y) = head + oldhead = head + if direction == pygame.K_LEFT: + head = (x - 1, y) + elif direction == pygame.K_RIGHT: + head = (x + 1, y) + elif direction == pygame.K_DOWN: + head = (x, y + 1) + elif direction == pygame.K_UP: + head = (x, y - 1) + + # Detect edge condition + (x, y) = head + if head in body: + game_over = True + elif x >= COLS or x < 0 or y >= ROWS or y < 0: + if WRAP: + if x >= COLS: + x = 0 + elif x < 0: + x = COLS - 1 + elif y >= ROWS: + y = 0 + elif y < 0: + y = ROWS - 1 + head = (x, y) + else: + game_over = True + elif head == food: + body.insert(0, oldhead) + while food == head: + food = (random.randint(0, COLS - 1), + random.randint(0, ROWS - 1)) + elif body: + body.pop() + body.insert(0, oldhead) + + # Draw on screen + if not game_over: + board = [[0 for _ in range(COLS)] for _ in range(ROWS)] + board[y][x] = 1 + board[food[1]][food[0]] = 1 + for bodypart in body: + (x, y) = bodypart + board[y][x] = 1 + + self.clock.tick(30) + + # Flash the screen twice before waiting for restart + for _ in range(2): + for dev in devices: + ledmatrix.percentage(dev, 0) + self.screen.fill(black) + pygame.display.update() + time.sleep(0.3) + + for dev in devices: + ledmatrix.percentage(dev, 100) + self.screen.fill(white) + pygame.display.update() + time.sleep(0.3) + + # Display final score and wait for restart without clearing the screen + board_state = get_board_state(board) + display_score(board_state, score) + self.draw_board(board_state, devices) + + waiting = True + while waiting: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + waiting = False + game_over = True + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_q: + waiting = False + if event.key == pygame.K_r: + board = [[0 for _ in range(COLS)] for _ in range(ROWS)] + gameLoop() + + pygame.quit() + quit() + + def __init__(self): + # Initialize pygame + pygame.init() + + # Create the screen + self.screen = pygame.display.set_mode((WIDTH, HEIGHT)) + + # Clock to control the speed of the game + self.clock = pygame.time.Clock() + +def main_devices(devices): + snake = Snake() + snake.gameLoop(devices) + +def main(): + devices = cli.find_devs() + + snake = Snake() + snake.gameLoop(devices) + +if __name__ == "__main__": + main()