From 33cca6cd3c924c6825ab9e28bad8fb415d2d9839 Mon Sep 17 00:00:00 2001 From: Rbanh Date: Sun, 23 Feb 2025 12:16:14 -0500 Subject: [PATCH] Add main menu system with game mode selection --- ROADMAP.md | 30 +++++++++------- src/game.py | 37 ++++++++++++++++--- src/menu.py | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 18 deletions(-) create mode 100644 src/menu.py diff --git a/ROADMAP.md b/ROADMAP.md index f10cd81..ea0c5d3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -3,11 +3,15 @@ This is a living document that tracks the development progress of the AI Snake Game project. ## Current Status -Last Updated: [Restart Functionality Implementation] -- Added game restart functionality -- Added "Press any key to restart" message -- Implemented game state reset +Last Updated: [Menu System Implementation] +- Added main menu interface with game mode selection +- Implemented menu navigation and selection +- Added game mode display during gameplay +- Integrated menu system with game states - Previous updates: + - Added game restart functionality + - Added "Press any key to restart" message + - Implemented game state reset - Added Food class with spawning mechanics - Integrated food system with main game loop - Implemented basic scoring system @@ -36,9 +40,9 @@ Last Updated: [Restart Functionality Implementation] - [x] Add restart functionality ## Phase 3: Game Polish and UI 🔄 -- [ ] Add main menu interface - - [ ] Create menu layout - - [ ] Add game mode selection +- [x] Add main menu interface + - [x] Create menu layout + - [x] Add game mode selection - [ ] Add settings options - [x] Implement pause functionality - [ ] Add visual effects and animations @@ -51,9 +55,9 @@ Last Updated: [Restart Functionality Implementation] - [ ] Add background music - [ ] Add menu sounds - [ ] Display score and high score -- [ ] Add game over screen with restart option +- [x] Add game over screen with restart option -## Phase 4: AI Implementation +## Phase 4: AI Implementation 🔄 - [ ] Design AI algorithm (pathfinding) - [ ] Research and select appropriate algorithm - [ ] Implement basic pathfinding @@ -64,7 +68,7 @@ Last Updated: [Restart Functionality Implementation] - [ ] Easy mode (basic pathfinding) - [ ] Medium mode (improved decision making) - [ ] Hard mode (optimal pathfinding) -- [ ] Create mode selection (Player vs AI) +- [x] Create mode selection (Player vs AI) - [ ] Optimize AI performance ## Phase 5: Final Polish @@ -81,9 +85,9 @@ Last Updated: [Restart Functionality Implementation] - [ ] Final testing and refinements ## Next Steps -1. Create main menu interface - - Design menu layout - - Implement menu navigation +1. Implement AI snake movement + - Design pathfinding algorithm + - Create AI controller class 2. Add high score system - Implement score persistence - Add high score display diff --git a/src/game.py b/src/game.py index 9727fde..2fc3a49 100644 --- a/src/game.py +++ b/src/game.py @@ -3,6 +3,7 @@ import sys from enum import Enum, auto from snake import Snake, Direction from food import Food +from menu import Menu, GameMode class GameState(Enum): MENU = auto() @@ -24,11 +25,13 @@ class Game: # Game objects self.block_size = 20 + self.menu = Menu(self.width, self.height) self.reset_game() # Game state - self.state = GameState.PLAYING # Changed to start in playing state for now + self.state = GameState.MENU # Start in menu state self.running = True + self.game_mode = None def reset_game(self): """Reset the game state for a new game""" @@ -47,6 +50,22 @@ class Game: self.state = GameState.PAUSED elif self.state == GameState.PAUSED: self.state = GameState.PLAYING + elif self.state == GameState.MENU: + self.running = False + + if self.state == GameState.MENU: + # Handle menu input + selected_mode = self.menu.handle_input(event) + if selected_mode is not None: + if selected_mode == GameMode.PLAYER: + self.game_mode = GameMode.PLAYER + self.state = GameState.PLAYING + elif selected_mode in (GameMode.AI_EASY, GameMode.AI_MEDIUM, GameMode.AI_HARD): + self.game_mode = selected_mode + self.state = GameState.PLAYING + else: # Quit selected + self.running = False + elif self.state == GameState.PLAYING: # Handle snake direction if event.key == pygame.K_UP: @@ -57,10 +76,11 @@ class Game: self.snake.change_direction(Direction.LEFT) elif event.key == pygame.K_RIGHT: self.snake.change_direction(Direction.RIGHT) + elif self.state == GameState.GAME_OVER: - # Any key to restart in game over state + # Any key to return to menu in game over state + self.state = GameState.MENU self.reset_game() - self.state = GameState.PLAYING def update(self): if self.state == GameState.PLAYING: @@ -81,7 +101,10 @@ class Game: # Clear screen self.screen.fill((0, 0, 0)) # Black background - if self.state == GameState.PLAYING or self.state == GameState.PAUSED: + if self.state == GameState.MENU: + self.menu.draw(self.screen) + + elif self.state == GameState.PLAYING or self.state == GameState.PAUSED: # Draw game objects self.snake.draw(self.screen) self.food.draw(self.screen) @@ -91,6 +114,10 @@ class Game: score_text = font.render(f'Score: {self.score}', True, (255, 255, 255)) self.screen.blit(score_text, (10, 10)) + # Draw game mode + mode_text = font.render(f'Mode: {self.game_mode.name}', True, (255, 255, 255)) + self.screen.blit(mode_text, (10, 50)) + # Draw pause indicator if self.state == GameState.PAUSED: pause_text = font.render('PAUSED', True, (255, 255, 255)) @@ -101,7 +128,7 @@ class Game: font = pygame.font.Font(None, 74) text = font.render('Game Over!', True, (255, 0, 0)) score_text = font.render(f'Score: {self.score}', True, (255, 255, 255)) - restart_text = font.render('Press any key to restart', True, (255, 255, 255)) + restart_text = font.render('Press any key for menu', True, (255, 255, 255)) text_rect = text.get_rect(center=(self.width//2, self.height//2 - 50)) score_rect = score_text.get_rect(center=(self.width//2, self.height//2 + 50)) diff --git a/src/menu.py b/src/menu.py new file mode 100644 index 0000000..019b1c4 --- /dev/null +++ b/src/menu.py @@ -0,0 +1,101 @@ +import pygame +from enum import Enum, auto +from typing import List, Tuple, Callable + +class MenuItem: + def __init__(self, text: str, action: Callable, position: Tuple[int, int] = (0, 0)): + self.text = text + self.action = action + self.position = position + self.is_selected = False + self.color = (255, 255, 255) # Default white + self.selected_color = (0, 255, 0) # Green when selected + self.font = pygame.font.Font(None, 64) + + def draw(self, screen: pygame.Surface): + """Draw the menu item""" + color = self.selected_color if self.is_selected else self.color + text_surface = self.font.render(self.text, True, color) + text_rect = text_surface.get_rect(center=self.position) + screen.blit(text_surface, text_rect) + +class GameMode(Enum): + PLAYER = auto() + AI_EASY = auto() + AI_MEDIUM = auto() + AI_HARD = auto() + +class Menu: + def __init__(self, screen_width: int, screen_height: int): + self.width = screen_width + self.height = screen_height + self.selected_index = 0 + self.items: List[MenuItem] = [] + self.setup_menu_items() + + def setup_menu_items(self): + """Initialize menu items with their positions""" + # Calculate vertical spacing + start_y = self.height // 3 + spacing = 80 + center_x = self.width // 2 + + # Create menu items + items_data = [ + ("Player Game", lambda: GameMode.PLAYER), + ("AI Easy", lambda: GameMode.AI_EASY), + ("AI Medium", lambda: GameMode.AI_MEDIUM), + ("AI Hard", lambda: GameMode.AI_HARD), + ("Quit", lambda: None) + ] + + for i, (text, action) in enumerate(items_data): + position = (center_x, start_y + i * spacing) + self.items.append(MenuItem(text, action, position)) + + # Select first item + self.items[0].is_selected = True + + def handle_input(self, event: pygame.event.Event) -> GameMode: + """Handle menu navigation and selection""" + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_UP: + # Move selection up + self.items[self.selected_index].is_selected = False + self.selected_index = (self.selected_index - 1) % len(self.items) + self.items[self.selected_index].is_selected = True + elif event.key == pygame.K_DOWN: + # Move selection down + self.items[self.selected_index].is_selected = False + self.selected_index = (self.selected_index + 1) % len(self.items) + self.items[self.selected_index].is_selected = True + elif event.key == pygame.K_RETURN: + # Execute selected item's action + return self.items[self.selected_index].action() + return None + + def draw(self, screen: pygame.Surface): + """Draw the menu""" + # Draw title + title_font = pygame.font.Font(None, 100) + title_text = title_font.render("AI Snake Game", True, (0, 255, 0)) + title_rect = title_text.get_rect(center=(self.width // 2, 100)) + screen.blit(title_text, title_rect) + + # Draw menu items + for item in self.items: + item.draw(screen) + + # Draw instructions + instruction_font = pygame.font.Font(None, 36) + instructions = [ + "Use UP/DOWN arrows to navigate", + "Press ENTER to select", + "Press ESC to quit" + ] + + start_y = self.height - 150 + for i, instruction in enumerate(instructions): + text = instruction_font.render(instruction, True, (128, 128, 128)) + rect = text.get_rect(center=(self.width // 2, start_y + i * 30)) + screen.blit(text, rect) \ No newline at end of file