222 lines
8.4 KiB
Python
222 lines
8.4 KiB
Python
"""
|
|
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) |