AI-Snake-Game/src/game.py

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)