Add testing framework and debug mode
This commit is contained in:
parent
33cca6cd3c
commit
cb626dfe7d
6
pytest.ini
Normal file
6
pytest.ini
Normal 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
34
run_game.py
Normal 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()
|
36
src/game.py
36
src/game.py
@ -33,6 +33,9 @@ class Game:
|
||||
self.running = True
|
||||
self.game_mode = None
|
||||
|
||||
# Debug mode
|
||||
self.debug = False
|
||||
|
||||
def reset_game(self):
|
||||
"""Reset the game state for a new game"""
|
||||
self.snake = Snake((self.width // 2, self.height // 2), self.block_size)
|
||||
@ -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
29
tests/conftest.py
Normal 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
58
tests/test_food.py
Normal 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
74
tests/test_snake.py
Normal 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
|
Loading…
Reference in New Issue
Block a user