""" Main Game Module This module provides the main game class that handles the game loop, state management, and user interface. """ import pygame import sys from enum import Enum, auto from src.config import ( WINDOW_WIDTH, WINDOW_HEIGHT, FPS, SCORE_FONT_SIZE ) from src.config import GameSettings from src.config.colors import BLACK, GREEN, NEON_GREEN from src.core import Direction, GameSession from src.ui import Menu, GameMode, SettingsMenu, PauseMenu, GameArea class GameState(Enum): """Game states.""" MENU = auto() SETTINGS = auto() PLAYING = auto() GAME_OVER = auto() PAUSED = auto() class Game: """Main game class that manages the game loop and state.""" def __init__(self): """Initialize the game.""" pygame.init() pygame.font.init() # Window setup self.width = WINDOW_WIDTH self.height = WINDOW_HEIGHT self.screen = pygame.display.set_mode((self.width, self.height)) pygame.display.set_caption("Snake Game") # Initialize game components self.clock = pygame.time.Clock() self.font = pygame.font.Font(None, SCORE_FONT_SIZE) self.settings = GameSettings() self.menu = Menu() self.settings_menu = SettingsMenu(rules=self.settings.rules) self.pause_menu = PauseMenu() # Game state self.state = GameState.MENU self.game_session = None self.game_mode = None self.running = True self.reset_game() def reset_game(self): """Initialize or reset the game session.""" if self.game_session: del self.game_session self.game_session = GameSession( grid_width=30, grid_height=30, rules=self.settings.rules, window_width=self.width, window_height=self.height ) def handle_events(self): """Handle game events.""" current_time = pygame.time.get_ticks() for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False elif event.type == pygame.MOUSEBUTTONDOWN: # Handle mouse clicks in menus if self.state == GameState.MENU: selected_mode = self.menu.handle_input(event) if selected_mode is not None: if selected_mode == GameMode.SETTINGS: self.state = GameState.SETTINGS elif selected_mode == GameMode.PLAYER: self.game_mode = GameMode.PLAYER self.state = GameState.PLAYING self.reset_game() elif selected_mode in (GameMode.AI_EASY, GameMode.AI_MEDIUM, GameMode.AI_HARD): self.game_mode = selected_mode self.state = GameState.PLAYING self.reset_game() else: # Quit selected self.running = False elif self.state == GameState.SETTINGS: result = self.settings_menu.handle_input(event) if result == 'back': self.state = GameState.MENU elif self.state == GameState.PAUSED: result = self.pause_menu.handle_input(event) if result == 'resume': self.state = GameState.PLAYING elif result == 'menu': self.state = GameState.MENU elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: if self.state == GameState.PLAYING: self.state = GameState.PAUSED elif self.state == GameState.PAUSED: self.state = GameState.PLAYING elif self.state == GameState.MENU: self.running = False elif self.state == GameState.SETTINGS: self.state = GameState.MENU elif event.key == pygame.K_F3: # Toggle debug mode self.settings.debug_mode = not self.settings.debug_mode if self.game_session: self.game_session.show_grid = self.settings.debug_mode elif self.state == GameState.PLAYING: # Handle snake direction if event.key == pygame.K_UP: self.game_session.step(Direction.UP, current_time) elif event.key == pygame.K_DOWN: self.game_session.step(Direction.DOWN, current_time) elif event.key == pygame.K_LEFT: self.game_session.step(Direction.LEFT, current_time) elif event.key == pygame.K_RIGHT: self.game_session.step(Direction.RIGHT, current_time) elif self.state == GameState.GAME_OVER: # Any key to return to menu in game over state self.state = GameState.MENU def update(self): """Update game state.""" current_time = pygame.time.get_ticks() if self.state == GameState.MENU: self.menu.update() elif self.state == GameState.SETTINGS: self.settings_menu.update() elif self.state == GameState.PAUSED: self.pause_menu.update() elif self.state == GameState.PLAYING: # Update game session _, _, done = self.game_session.step(current_time=current_time) if done: self.state = GameState.GAME_OVER def draw_debug_info(self): """Draw debug information.""" if not self.game_session: return state = self.game_session.get_state() font = pygame.font.Font(None, 24) debug_info = [ f"FPS: {int(self.clock.get_fps())}", f"Snake Length: {len(state['snake_body'])}", f"Snake Head: {state['snake_body'][0]}", f"Food Pos: {state['food_position']}", f"Game State: {self.state.name}", f"Game Mode: {self.game_mode.name if self.game_mode else 'None'}", f"Move Cooldown: {state['move_cooldown']}ms" ] for i, text in enumerate(debug_info): surface = font.render(text, True, GREEN) self.screen.blit(surface, (10, self.height - 200 + i * 25)) def draw_overlay(self, text): """Draw a semi-transparent overlay with text.""" overlay = pygame.Surface((self.width, self.height)) overlay.fill(BLACK) overlay.set_alpha(128) self.screen.blit(overlay, (0, 0)) text_surface = self.font.render(text, True, NEON_GREEN) text_rect = text_surface.get_rect(center=(self.width // 2, self.height // 2)) self.screen.blit(text_surface, text_rect) def render(self): """Render the current game state.""" # Clear screen self.screen.fill(BLACK) if self.state == GameState.MENU: self.menu.draw(self.screen) elif self.state == GameState.SETTINGS: self.settings_menu.draw(self.screen) elif self.state in [GameState.PLAYING, GameState.PAUSED, GameState.GAME_OVER]: # Draw game session self.game_session.render(self.screen) # Draw score panel score_text = f"Score: {self.game_session.score}" score_surface = self.font.render(score_text, True, GREEN) score_rect = score_surface.get_rect(midtop=(self.width // 2, 20)) self.screen.blit(score_surface, score_rect) # Draw overlays if self.state == GameState.PAUSED: self.pause_menu.draw(self.screen) elif self.state == GameState.GAME_OVER: self.draw_overlay("GAME OVER") # Draw debug info if enabled if self.settings.debug_mode: self.draw_debug_info() # Update display pygame.display.flip() def run(self): """Run the main game loop.""" self.running = True while self.running: self.handle_events() self.update() self.render() self.clock.tick(FPS)