From 0bbcee88478c803211b16d106a26c623e72ddfe6 Mon Sep 17 00:00:00 2001 From: Vincent Rodley Date: Wed, 27 Aug 2025 20:13:10 +1200 Subject: [PATCH] new menu_manager system (far cleaner), polished win + draw detection --- gui/game.py | 230 ++++++++++++++++++-------------------------- gui/menu_manager.py | 52 ++++++---- 2 files changed, 130 insertions(+), 152 deletions(-) diff --git a/gui/game.py b/gui/game.py index 6d74b75..1f0443e 100644 --- a/gui/game.py +++ b/gui/game.py @@ -1,4 +1,3 @@ -# imports import pygame from button import Button from menu_manager import MenuManager @@ -12,50 +11,37 @@ TILE_SIZE, TILE_SPACING = 50, 20 GRID_WIDTH = COLS * TILE_SIZE + (COLS - 1) * TILE_SPACING GRID_HEIGHT = ROWS * TILE_SIZE + (ROWS - 1) * TILE_SPACING -# init the pygame +# inits pygame.init() font = pygame.font.Font("Baloo2-Bold.ttf", 40) display = pygame.display.set_mode(WINDOW_SIZE) clock = pygame.time.Clock() -# colourss -primary_colour = (70, 130, 180) # button background -hover_colour = (51, 102, 145) # button hover -text_colour = (245, 245, 245) # button text -tile_colour = (200, 200, 200) # tile main -tile_hover = (170, 170, 170) # tile hover -tile_text = (50, 50, 50) # tile text -bg_colour = (30, 30, 40) # main background +# coloursss +primary_colour = (70, 130, 180) +hover_colour = (51, 102, 145) +text_colour = (245, 245, 245) +tile_colour = (200, 200, 200) +tile_hover = (170, 170, 170) +tile_text = (50, 50, 50) +bg_colour = (30, 30, 40) -red_tile = (255, 0, 0) # the red that a red tile is -red_tile_hover = (220, 0, 0) # the red that a hovered red tile is -yellow_tile = (255, 255, 0) # the yellow that a yellow tile is -yellow_tile_hover = (220, 220, 0) # the yellow that a hovered yellow tile is +red_tile = (255, 0, 0) +red_tile_hover = (220, 0, 0) +yellow_tile = (255, 255, 0) +yellow_tile_hover = (220, 220, 0) -# variables +# game state inits tiles = [] -menu_manager = MenuManager(display, bg_colour) # background colour player = "red" board_full = False +winner = None # "red", "yellow", or None for draw -# menu functions -def start_game_func(*_): - global board_full - board_full = False - create_tiles() - menu_manager.change_menu("game") - -def settings_menu(*_): - menu_manager.change_menu("settings") - -def go_back(*_): - menu_manager.change_menu("start") - -# tile stuff +# tile + board logic def create_tiles(): global tiles tiles = [] - + start_x = (WINDOW_SIZE[0] - GRID_WIDTH) // 2 start_y = (WINDOW_SIZE[1] - GRID_HEIGHT) // 2 @@ -64,21 +50,33 @@ def create_tiles(): for r in range(ROWS): x = start_x + c * (TILE_SIZE + TILE_SPACING) y = start_y + r * (TILE_SIZE + TILE_SPACING) - - id = str(c * ROWS + r) - tile = Button( x, y, TILE_SIZE, TILE_SIZE, "", - tile_colour, tile_hover, tile_text, tile_press, None, 30, (id, c, r), + tile_colour, tile_hover, tile_text, + tile_press, None, 30, (c, r), rounding=30 ) col.append(tile) tiles.append(col) -def checkWin(): +def drop_tile(col_index): + column = tiles[col_index] + for r in reversed(range(ROWS)): + target_tile = column[r] + if target_tile.colour == tile_colour: + if player == "red": + target_tile.colour = red_tile + target_tile.hover_colour = red_tile_hover + else: + target_tile.colour = yellow_tile + target_tile.hover_colour = yellow_tile_hover + return r # row where tile landed + return None # column full + +def check_win(): global tiles, player - colours = [yellow_tile, yellow_tile_hover] if player == "red" else [red_tile, red_tile_hover] # this is backwards on purpose + colours = [yellow_tile, yellow_tile_hover] if player == "yellow" else [red_tile, red_tile_hover] rows, cols = (6, 7) winCount = 4 @@ -99,150 +97,114 @@ def checkWin(): if all(tiles[col + i][row - i].colour in colours for i in range(winCount)): return [(col + i, row - i) for i in range(winCount)] -def drop_tile(col_index): - global player - column = tiles[col_index] - - # find the lowest unoccupied tile in this column - for r in reversed(range(ROWS)): - target_tile = column[r] - - # check if already taken (coloured by a player) - if target_tile.colour == tile_colour: - # claim this tile for the current player - target_tile.colour = red_tile if player == "red" else yellow_tile - target_tile.hover_colour = red_tile_hover if player == "red" else yellow_tile_hover - - # print(f"Player {player} placed at col {col_index}, row {r}") - - # switch turn - player = "yellow" if player == "red" else "red" - # print(f"Next turn: {player}") - break - else: - # column is full - print(f"Column {col_index} is full!") - def is_board_full(): - global tiles, board_full for col in tiles: - for row in reversed(range(ROWS)): - target = col[row] - - if target.colour == tile_colour: + for tile in col: + if tile.colour == tile_colour: return False - return True def tile_press(tile: Button): - global board_full + global board_full, winner, player + if board_full: + return - id, col_index, row_index = tile.extra_data # we only care about column - drop_tile(col_index) + col_index, row_index = tile.extra_data + row_dropped = drop_tile(col_index) + if row_dropped is None: + return # column full - win = checkWin() - - print(f"Win tiles: {win}") + win = check_win() + # check for win if win: - print("Somebody won fr") - - if is_board_full(): + winner = player board_full = True - print(f"Draw! The board is full.") - + elif is_board_full(): + winner = None # draw + board_full = True + else: + player = "yellow" if player == "red" else "red" -# button stuff +# buttons width, height = 280, 75 x = WINDOW_SIZE[0] / 2 - width / 2 y = WINDOW_SIZE[1] / 2 - height / 2 start_button = Button(x, y - 100, width, height, "Start Game", primary_colour, hover_colour, text_colour, - start_game_func, "Baloo2-Bold.ttf", 50, rounding=8) + lambda *_: start_game(), "Baloo2-Bold.ttf", 50, rounding=8) settings_button = Button(x, y - 100 + height * 2, width, height, "Settings", primary_colour, hover_colour, text_colour, - settings_menu, "Baloo2-Bold.ttf", 50, rounding=8) + lambda *_: menu_manager.change_menu("settings"), + "Baloo2-Bold.ttf", 50, rounding=8) go_back_button = Button(x, y, width, height, "Go back", primary_colour, hover_colour, text_colour, - go_back, "Baloo2-Bold.ttf", 50, rounding=8) + lambda *_: menu_manager.change_menu("start"), + "Baloo2-Bold.ttf", 50, rounding=8) -game_over_button = Button(x, y*2-50, width, height, "Go back", - primary_colour, hover_colour, text_colour, - go_back, "Baloo2-Bold.ttf", 50, rounding=8) +game_over_button = Button(x, y * 2 - 50, width, height, "Go back", + primary_colour, hover_colour, text_colour, + lambda *_: menu_manager.change_menu("start"), + "Baloo2-Bold.ttf", 50, rounding=8) -game_over_text = Button(x, 50, width, height/1.5, "text", +game_over_text = Button(x, 50, width, height / 1.5, "text", bg_colour, (0, 0, 0), text_colour, None, "Baloo2-Bold.ttf", 50, rounding=8) -# menu handlers -# start -def start_menu_events(event): - start_button.handle_event(event) - settings_button.handle_event(event) +# menu callbacks +def start_game(): + global board_full, player, winner + board_full = False + winner = None + player = "red" + create_tiles() + menu_manager.change_menu("game") -def start_menu_draw(): - start_button.draw(display) - settings_button.draw(display) - -# settings -def settings_menu_events(event): - go_back_button.handle_event(event) - -def settings_menu_draw(): +def draw_settings(display): text_surface = font.render("No settings yet :(", True, text_colour) text_rect = text_surface.get_rect(center=(WINDOW_SIZE[0] / 2, WINDOW_SIZE[1] / 2 - 100)) display.blit(text_surface, text_rect) - go_back_button.draw(display) - -# game -def game_menu_events(event): +def draw_game(display): if board_full: - game_over_button.handle_event(event) - else: - for col in tiles: - for tile in col: - tile: Button - tile.handle_event(event) - -def game_menu_draw(): - global player - for col in tiles: - for tile in col: - tile: Button - tile.draw(display) - - if board_full: - game_over_text.text = f"{'RED' if player == 'red' else 'YELLOW'} WINS" + if winner: + game_over_text.text = f"{winner.upper()} wins!" + else: + game_over_text.text = "Draw!" game_over_button.draw(display) game_over_text.draw(display) -# register the menus -menu_manager.register_menu("start", start_menu_events, start_menu_draw) -menu_manager.register_menu("settings", settings_menu_events, settings_menu_draw) -menu_manager.register_menu("game", game_menu_events, game_menu_draw) +# menu manager init + setup +menu_manager = MenuManager(display, bg_colour) + +menu_manager.register_menu("start", + buttons=[start_button, settings_button] +) + +menu_manager.register_menu("settings", + buttons=[go_back_button], + draw=draw_settings +) + +menu_manager.register_menu("game", + buttons=lambda: [tile for col in tiles for tile in col] + ([game_over_button] if board_full else []), + draw=draw_game +) menu_manager.change_menu("start") - -# main loopy loopy +# main loop if __name__ == "__main__": running = True - while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False - if event.type == pygame.KEYDOWN: - if event.key == pygame.K_ESCAPE: - running = False - - if event.key == pygame.K_d: - board_full = True - + if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: + running = False menu_manager.handle_event(event) menu_manager.draw() diff --git a/gui/menu_manager.py b/gui/menu_manager.py index f405a5f..674c836 100644 --- a/gui/menu_manager.py +++ b/gui/menu_manager.py @@ -1,31 +1,47 @@ import pygame class MenuManager: - def __init__(self, display, bg_color): + def __init__(self, display, bg_colour, menus=None): self.display = display - self.bg_color = bg_color + self.bg_colour = bg_colour + self.menus = menus or {} self.current_menu = None - self.event_handlers = {} - self.draw_handlers = {} - # switch menu, and clear the screen def change_menu(self, name): - if name not in self.draw_handlers: - raise ValueError(f"Menu: '{name} not registered") + if name not in self.menus: + raise ValueError(f"Menu '{name}' not registered") self.current_menu = name - self.display.fill(self.bg_color) + self.display.fill(self.bg_colour) - # register a menus event and draw functions - def register_menu(self, name, event_handler, draw_handler): - self.event_handlers[name] = event_handler - self.draw_handlers[name] = draw_handler + def register_menu(self, name, buttons=None, draw=None): + self.menus[name] = { + "buttons": buttons or [], + "draw": draw + } - # pass event to the menu handler def handle_event(self, event): - if self.current_menu in self.event_handlers: - self.event_handlers[self.current_menu](event) + if not self.current_menu: + return + buttons = self.menus[self.current_menu]["buttons"] + + # handle dynamic buttons (function returning list) + if callable(buttons): + buttons = buttons() + + for b in buttons: + b.handle_event(event) - # draw the current menu def draw(self): - if self.current_menu in self.draw_handlers: - self.draw_handlers[self.current_menu]() + if not self.current_menu: + return + buttons = self.menus[self.current_menu]["buttons"] + draw_callback = self.menus[self.current_menu]["draw"] + + if callable(buttons): + buttons = buttons() + + for b in buttons: + b.draw(self.display) + + if draw_callback: + draw_callback(self.display)