Add testing framework and debug mode

This commit is contained in:
Rbanh 2025-02-23 12:20:23 -05:00
parent 33cca6cd3c
commit cb626dfe7d
6 changed files with 237 additions and 0 deletions

6
pytest.ini Normal file
View File

@ -0,0 +1,6 @@
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short

34
run_game.py Normal file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env python3
import argparse
from src.game import Game
def main():
parser = argparse.ArgumentParser(description='Run AI Snake Game')
parser.add_argument('--test', action='store_true',
help='Run game in test mode (smaller window, faster speed)')
parser.add_argument('--debug', action='store_true',
help='Run game in debug mode (shows grid and additional info)')
parser.add_argument('--ai-only', action='store_true',
help='Start directly in AI mode (skips menu)')
args = parser.parse_args()
# Create game instance
game = Game()
# Apply test mode settings
if args.test:
game.width = 400
game.height = 300
game.block_size = 20
game.fps = 30
# Apply debug mode settings
if args.debug:
game.debug = True # Will need to add debug support to Game class
# Run the game
game.run()
if __name__ == "__main__":
main()

View File

@ -32,6 +32,9 @@ class Game:
self.state = GameState.MENU # Start in menu state
self.running = True
self.game_mode = None
# Debug mode
self.debug = False
def reset_game(self):
"""Reset the game state for a new game"""
@ -40,6 +43,29 @@ class Game:
self.food.spawn(self.width, self.height, self.snake.body)
self.score = 0
def draw_grid(self):
"""Draw the game grid (debug mode)"""
for x in range(0, self.width, self.block_size):
pygame.draw.line(self.screen, (50, 50, 50), (x, 0), (x, self.height))
for y in range(0, self.height, self.block_size):
pygame.draw.line(self.screen, (50, 50, 50), (0, y), (self.width, y))
def draw_debug_info(self):
"""Draw debug information"""
font = pygame.font.Font(None, 24)
debug_info = [
f"FPS: {int(self.clock.get_fps())}",
f"Snake Length: {len(self.snake.body)}",
f"Snake Head: {self.snake.body[0]}",
f"Food Pos: {self.food.position}",
f"Game State: {self.state.name}",
f"Game Mode: {self.game_mode.name if self.game_mode else 'None'}"
]
for i, text in enumerate(debug_info):
surface = font.render(text, True, (0, 255, 0))
self.screen.blit(surface, (10, self.height - 150 + i * 25))
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
@ -52,6 +78,8 @@ class Game:
self.state = GameState.PLAYING
elif self.state == GameState.MENU:
self.running = False
elif event.key == pygame.K_F3: # Toggle debug mode
self.debug = not self.debug
if self.state == GameState.MENU:
# Handle menu input
@ -101,6 +129,10 @@ class Game:
# Clear screen
self.screen.fill((0, 0, 0)) # Black background
# Draw grid in debug mode
if self.debug and self.state != GameState.MENU:
self.draw_grid()
if self.state == GameState.MENU:
self.menu.draw(self.screen)
@ -138,6 +170,10 @@ class Game:
self.screen.blit(score_text, score_rect)
self.screen.blit(restart_text, restart_rect)
# Draw debug info
if self.debug:
self.draw_debug_info()
# Update display
pygame.display.flip()

29
tests/conftest.py Normal file
View File

@ -0,0 +1,29 @@
import pytest
import pygame
import sys
import os
# Add src directory to Python path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
@pytest.fixture(autouse=True)
def pygame_init():
"""Initialize pygame for all tests"""
pygame.init()
yield
pygame.quit()
@pytest.fixture
def game_config():
"""Basic game configuration"""
return {
'width': 800,
'height': 600,
'block_size': 20,
'fps': 60
}
@pytest.fixture
def mock_screen(game_config):
"""Create a mock pygame screen"""
return pygame.Surface((game_config['width'], game_config['height']))

58
tests/test_food.py Normal file
View File

@ -0,0 +1,58 @@
import pytest
from src.food import Food
def test_food_initialization():
"""Test food initialization"""
food = Food(20)
assert food.block_size == 20
assert food.color == (255, 0, 0) # Default red color
assert food.position == (0, 0) # Default position
def test_food_spawn():
"""Test food spawning mechanics"""
food = Food(20)
width, height = 800, 600
snake_body = [(100, 100), (120, 100)] # Mock snake body
# Test spawning
food.spawn(width, height, snake_body)
# Check if food is within bounds
assert 0 <= food.position[0] < width
assert 0 <= food.position[1] < height
# Check if position aligns with grid
assert food.position[0] % food.block_size == 0
assert food.position[1] % food.block_size == 0
# Check if food doesn't spawn on snake
assert food.position not in snake_body
def test_food_collision():
"""Test food collision detection"""
food = Food(20)
food.position = (100, 100)
# Test collision
assert food.check_collision((100, 100)) == True
# Test no collision
assert food.check_collision((120, 120)) == False
def test_food_spawn_full_grid():
"""Test food spawning when grid is mostly occupied"""
food = Food(20)
width, height = 60, 60 # Small grid
# Create a snake that occupies most of the grid
snake_body = [(x, y) for x in range(0, 60, 20) for y in range(0, 40, 20)]
# Leave one spot free at (40, 40)
free_spot = (40, 40)
assert free_spot not in snake_body
# Spawn food
food.spawn(width, height, snake_body)
# Food should spawn in the only free spot
assert food.position == free_spot

74
tests/test_snake.py Normal file
View File

@ -0,0 +1,74 @@
import pytest
from src.snake import Snake, Direction
def test_snake_initialization():
"""Test snake initialization with default values"""
snake = Snake((100, 100), 20)
assert snake.block_size == 20
assert snake.direction == Direction.RIGHT
assert len(snake.body) == 1
assert snake.body[0] == (100, 100)
assert not snake.growing
def test_snake_movement():
"""Test snake movement in different directions"""
snake = Snake((100, 100), 20)
# Test right movement (default direction)
snake.move(100) # time parameter
assert snake.body[0] == (120, 100)
# Test down movement
snake.change_direction(Direction.DOWN)
snake.move(200)
assert snake.body[0] == (120, 120)
# Test left movement
snake.change_direction(Direction.LEFT)
snake.move(300)
assert snake.body[0] == (100, 120)
# Test up movement
snake.change_direction(Direction.UP)
snake.move(400)
assert snake.body[0] == (100, 100)
def test_snake_growth():
"""Test snake growth when eating food"""
snake = Snake((100, 100), 20)
original_length = len(snake.body)
snake.grow()
snake.move(100)
assert len(snake.body) == original_length + 1
def test_snake_collision():
"""Test snake collision detection"""
snake = Snake((100, 100), 20)
# Test wall collision
assert snake.check_collision(200, 200) == False # No collision
snake.body[0] = (-20, 100) # Move outside left wall
assert snake.check_collision(200, 200) == True
# Test self collision
snake = Snake((100, 100), 20)
snake.body = [(100, 100), (120, 100), (120, 120), (100, 120), (100, 100)]
assert snake.check_collision(200, 200) == True
def test_invalid_direction_change():
"""Test that snake cannot reverse direction"""
snake = Snake((100, 100), 20)
# Try to change to opposite direction (LEFT while going RIGHT)
snake.change_direction(Direction.LEFT)
assert snake.direction == Direction.RIGHT
# Valid direction change
snake.change_direction(Direction.DOWN)
assert snake.direction == Direction.DOWN
# Try to change to opposite direction (UP while going DOWN)
snake.change_direction(Direction.UP)
assert snake.direction == Direction.DOWN