From f476d0a6c62d3b205b620f638dc9aab030dc9f6e Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Wed, 4 Jun 2025 14:34:54 -0700 Subject: [PATCH] Fix many bugs --- Metro/Metro_RP2350_Matching_Game/code.py | 55 +++++------ Metro/Metro_RP2350_Matching_Game/gamelogic.py | 98 +++++++++++++------ 2 files changed, 91 insertions(+), 62 deletions(-) diff --git a/Metro/Metro_RP2350_Matching_Game/code.py b/Metro/Metro_RP2350_Matching_Game/code.py index 79aad98f3..f6c903489 100755 --- a/Metro/Metro_RP2350_Matching_Game/code.py +++ b/Metro/Metro_RP2350_Matching_Game/code.py @@ -4,6 +4,7 @@ An implementation of a match3 jewel swap game. The idea is to move one character at a time to line up at least 3 characters. """ +import time from displayio import Group, OnDiskBitmap, TileGrid, Bitmap, Palette from adafruit_display_text.bitmap_label import Label from adafruit_display_text.text_box import TextBox @@ -141,22 +142,25 @@ def get_color_index(color, shader=None): ui_group = Group() main_group.append(ui_group) +# Create the mouse graphics and add to the main group +time.sleep(1) # Allow time for USB host to initialize +mouse = find_and_init_boot_mouse("/bitmaps/mouse_cursor.bmp") +if mouse is None: + raise RuntimeError("No mouse found connected to USB Host") +main_group.append(mouse.tilegrid) + # Create the game logic object # pylint: disable=no-value-for-parameter, too-many-function-args game_logic = GameLogic( display, + mouse, game_grid, swap_piece, selected_piece_group, - GAME_PIECES + GAME_PIECES, + HINT_TIMEOUT ) -# Create the mouse graphics and add to the main group -mouse = find_and_init_boot_mouse("/bitmaps/mouse_cursor.bmp") -if mouse is None: - raise RuntimeError("No mouse found connected to USB Host") -main_group.append(mouse.tilegrid) - def update_ui(): # Update the UI elements with the current game state score_label.text = f"Score:\n{game_logic.score}" @@ -232,38 +236,29 @@ def hide_group(group): while True: update_ui() # update mouse - pressed_btns = mouse.update() - - if waiting_for_release and not pressed_btns: - # If both buttons are released, we can process the next click - waiting_for_release = False + game_logic.update_mouse() if not message_dialog.hidden: - if message_button.handle_mouse((mouse.x, mouse.y), - pressed_btns and "left" in pressed_btns, - waiting_for_release): - waiting_for_release = True + if message_button.handle_mouse( + (mouse.x, mouse.y), + game_logic.pressed_btns and "left" in game_logic.pressed_btns, + waiting_for_release + ): + game_logic.waiting_for_release = True continue - if reset_button.handle_mouse((mouse.x, mouse.y), - pressed_btns and "left" in pressed_btns, - waiting_for_release): - waiting_for_release = True + if reset_button.handle_mouse( + (mouse.x, mouse.y), + game_logic.pressed_btns is not None and "left" in game_logic.pressed_btns, + game_logic.waiting_for_release + ): + game_logic.waiting_for_release = True # process gameboard click if no menu - game_board = game_logic.game_board - if (game_board.x <= mouse.x <= game_board.x + game_board.columns * 32 and - game_board.y <= mouse.y <= game_board.y + game_board.rows * 32 and - not waiting_for_release): - piece_coords = ((mouse.x - game_board.x) // 32, (mouse.y - game_board.y) // 32) - if pressed_btns and "left" in pressed_btns: - game_logic.piece_clicked(piece_coords) - waiting_for_release = True + game_logic.update() game_over = game_logic.check_for_game_over() if game_over and not game_over_shown: message_label.text = ("No more moves available. your final score is:\n" + str(game_logic.score)) message_dialog.hidden = False game_over_shown = True - if game_logic.time_since_last_update > HINT_TIMEOUT: - game_logic.show_hint() diff --git a/Metro/Metro_RP2350_Matching_Game/gamelogic.py b/Metro/Metro_RP2350_Matching_Game/gamelogic.py index c0ec7da33..d89f47ea9 100755 --- a/Metro/Metro_RP2350_Matching_Game/gamelogic.py +++ b/Metro/Metro_RP2350_Matching_Game/gamelogic.py @@ -9,7 +9,7 @@ SELECTOR_SPRITE = 9 EMPTY_SPRITE = 10 -DEBOUNCE_TIME = 0.1 # seconds for debouncing mouse clicks +DEBOUNCE_TIME = 0.2 # seconds for debouncing mouse clicks class GameBoard: "Contains the game board" @@ -42,6 +42,10 @@ def reset(self): for row in range(self.rows): if self._game_grid[(column, row)] != EMPTY_SPRITE: self.remove_game_piece(column, row) + # Hide the animation TileGrids + self._selector.hidden = True + self._swap_piece.hidden = True + self.selected_piece_group.hidden = True def move_game_piece(self, old_x, old_y, new_x, new_y): if 0 <= old_x < self.columns and 0 <= old_y < self.rows: @@ -153,18 +157,41 @@ def game_grid_copy(self): class GameLogic: "Contains the Logic to examine the game board and determine if a move is valid." - def __init__(self, display, game_grid, swap_piece, selected_piece_group, game_pieces): + def __init__(self, display, mouse, game_grid, swap_piece, + selected_piece_group, game_pieces, hint_timeout): self._display = display + self._mouse = mouse self.game_board = GameBoard(game_grid, swap_piece, selected_piece_group) self._score = 0 self._available_moves = [] if not 3 <= game_pieces <= 8: raise ValueError("game_pieces must be between 3 and 8") self._game_pieces = game_pieces # Number of different game pieces + self._hint_timeout = hint_timeout self._last_update_time = ticks_ms() # For hint timing self._last_click_time = ticks_ms() # For debouncing mouse clicks + self.pressed_btns = None + self.waiting_for_release = False - def piece_clicked(self, coords): + def update_mouse(self): + self.pressed_btns = self._mouse.update() + if self.waiting_for_release and not self.pressed_btns: + # If both buttons are released, we can process the next click + self.waiting_for_release = False + + def update(self): + gb = self.game_board + if (gb.x <= self._mouse.x <= gb.x + gb.columns * 32 and + gb.y <= self._mouse.y <= gb.y + gb.rows * 32 and + not self.waiting_for_release): + piece_coords = ((self._mouse.x - gb.x) // 32, (self._mouse.y - gb.y) // 32) + if self.pressed_btns and "left" in self.pressed_btns: + self._piece_clicked(piece_coords) + self.waiting_for_release = True + if self.time_since_last_update > self._hint_timeout: + self.show_hint() + + def _piece_clicked(self, coords): """ Handle a piece click event. """ if ticks_ms() <= self._last_click_time: self._last_click_time -= 2**29 # ticks_ms() wraps around after 2**29 ms @@ -206,7 +233,7 @@ def piece_clicked(self, coords): if ((abs(previous_x - column) == 1 and previous_y == row) or (previous_x == column and abs(previous_y - row) == 1)): # Swap the pieces - self.swap_selected_piece(column, row) + self._swap_selected_piece(column, row) def show_hint(self): """ Show a hint by selecting a random available @@ -216,21 +243,21 @@ def show_hint(self): from_coords = move['from'] to_coords = move['to'] self.game_board.select_piece(from_coords[0], from_coords[1]) - self.animate_swap(to_coords[0], to_coords[1]) + self._animate_swap(to_coords[0], to_coords[1]) self.game_board.select_piece(from_coords[0], from_coords[1]) - self.animate_swap(to_coords[0], to_coords[1]) + self._animate_swap(to_coords[0], to_coords[1]) self._last_update_time = ticks_ms() # Reset hint timer - def swap_selected_piece(self, column, row): + def _swap_selected_piece(self, column, row): """ Swap the selected piece with the piece at the specified column and row. If the swap is not valid, revert to the previous selection. """ old_coords = self.game_board.selected_coords - self.animate_swap(column, row) - if not self.update(): + self._animate_swap(column, row) + if not self._update_board(): self.game_board.select_piece(column, row, show_selector=False) - self.animate_swap(old_coords[0], old_coords[1]) + self._animate_swap(old_coords[0], old_coords[1]) - def animate_swap(self, column, row): + def _animate_swap(self, column, row): """ Copy the pieces to separate tilegrids, animate the swap, and update the game board. """ if 0 <= column < self.game_board.columns and 0 <= row < self.game_board.rows: selected_coords = self.game_board.selected_coords @@ -271,11 +298,12 @@ def animate_swap(self, column, row): # Deselect the selected piece (which sets the value) self.game_board.select_piece(column, row) - def apply_gravity(self): + def _apply_gravity(self): """ Go through each column from the bottom up and move pieces down continue until there are no more pieces to move """ # pylint:disable=too-many-nested-blocks while True: + self.pressed_btns = self._mouse.update() moved = False for x in range(self.game_board.columns): for y in range(self.game_board.rows - 1, -1, -1): @@ -295,7 +323,7 @@ def apply_gravity(self): if not moved: break - def check_for_matches(self): + def _check_for_matches(self): """ Scan the game board for matches of 3 or more in a row or column """ matches = [] for x in range(self.game_board.columns): @@ -325,11 +353,11 @@ def check_for_matches(self): matches.append(vertical_match) return matches - def update(self): + def _update_board(self): """ Update the game logic, check for matches, and apply gravity. """ matches_found = False multiplier = 1 - matches = self.check_for_matches() + matches = self._check_for_matches() while matches: if matches: for match in matches: @@ -337,23 +365,25 @@ def update(self): self.game_board.remove_game_piece(x, y) self._score += 10 * multiplier * len(matches) * (len(match) - 2) time.sleep(0.5) # Pause to show the match removal - self.apply_gravity() + self._apply_gravity() matches_found = True - matches = self.check_for_matches() + matches = self._check_for_matches() multiplier += 1 - self._available_moves = self.find_all_possible_matches() + self._available_moves = self._find_all_possible_matches() print(f"{len(self._available_moves)} available moves found.") return matches_found def reset(self): """ Reset the game board and score. """ + print("Reset started") self.game_board.reset() self._score = 0 self._last_update_time = ticks_ms() - self.apply_gravity() - self.update() + self._apply_gravity() + self._update_board() + print("Reset completed") - def check_match_after_move(self, row, column, direction, move_type='horizontal'): + def _check_match_after_move(self, row, column, direction, move_type='horizontal'): """ Move the piece in a copy of the board to see if it creates a match.""" if move_type == 'horizontal': new_row, new_column = row, column + direction @@ -371,15 +401,15 @@ def check_match_after_move(self, row, column, direction, move_type='horizontal') new_grid[row][column], new_grid[new_row][new_column] = new_grid[new_row][new_column], piece # Check for horizontal matches at the new position - horizontal_match = self.check_horizontal_match(new_grid, new_row, new_column, piece) + horizontal_match = self._check_horizontal_match(new_grid, new_row, new_column, piece) # Check for vertical matches at the new position - vertical_match = self.check_vertical_match(new_grid, new_row, new_column, piece) + vertical_match = self._check_vertical_match(new_grid, new_row, new_column, piece) # Also check the original position for matches after the swap original_piece = new_grid[row][column] - horizontal_match_orig = self.check_horizontal_match(new_grid, row, column, original_piece) - vertical_match_orig = self.check_vertical_match(new_grid, row, column, original_piece) + horizontal_match_orig = self._check_horizontal_match(new_grid, row, column, original_piece) + vertical_match_orig = self._check_vertical_match(new_grid, row, column, original_piece) all_matches = (horizontal_match + vertical_match + horizontal_match_orig + vertical_match_orig) @@ -387,7 +417,7 @@ def check_match_after_move(self, row, column, direction, move_type='horizontal') return True, len(all_matches) > 0 @staticmethod - def check_horizontal_match(grid, row, column, piece): + def _check_horizontal_match(grid, row, column, piece): """Check for horizontal 3-in-a-row matches centered around or including the given position.""" matches = [] @@ -406,7 +436,7 @@ def check_horizontal_match(grid, row, column, piece): return matches @staticmethod - def check_vertical_match(grid, row, column, piece): + def _check_vertical_match(grid, row, column, piece): """Check for vertical 3-in-a-row matches centered around or including the given position.""" matches = [] rows = len(grid) @@ -429,7 +459,7 @@ def check_for_game_over(self): return True return False - def find_all_possible_matches(self): + def _find_all_possible_matches(self): """ Scan the entire game board to find all possible moves that would create a 3-in-a-row match. """ @@ -438,7 +468,8 @@ def find_all_possible_matches(self): for row in range(self.game_board.rows): for column in range(self.game_board.columns): # Check move right - can_move, creates_match = self.check_match_after_move(row, column, 1, 'horizontal') + can_move, creates_match = self._check_match_after_move( + row, column, 1, 'horizontal') if can_move and creates_match: possible_moves.append({ 'from': (column, row), @@ -446,7 +477,8 @@ def find_all_possible_matches(self): }) # Check move left - can_move, creates_match = self.check_match_after_move(row, column, -1, 'horizontal') + can_move, creates_match = self._check_match_after_move( + row, column, -1, 'horizontal') if can_move and creates_match: possible_moves.append({ 'from': (column, row), @@ -454,7 +486,8 @@ def find_all_possible_matches(self): }) # Check move down - can_move, creates_match = self.check_match_after_move(row, column, 1, 'vertical') + can_move, creates_match = self._check_match_after_move( + row, column, 1, 'vertical') if can_move and creates_match: possible_moves.append({ 'from': (column, row), @@ -462,7 +495,8 @@ def find_all_possible_matches(self): }) # Check move up - can_move, creates_match = self.check_match_after_move(row, column, -1, 'vertical') + can_move, creates_match = self._check_match_after_move( + row, column, -1, 'vertical') if can_move and creates_match: possible_moves.append({ 'from': (column, row),