Add input buffering, auto-start, and gameplay improvements
Input buffer system: - Added 3-slot direction input buffer to handle rapid key presses - Buffer ignores duplicate inputs (same key pressed multiple times) - Opposite direction replaces last buffered input (e.g., LEFT→RIGHT replaces LEFT) - Buffer overflow replaces last slot when full - Multi-segment snakes skip invalid 180° turns when consuming buffer - Head-only snakes (length=1) can turn 180° for flexibility Gameplay improvements: - Desktop client auto-starts game on connect (no SPACE needed) - Field populates with 3 apples when no players connected - HTTP server now binds to 0.0.0.0 for network access (matches game server) Testing: - Added 7 new tests for input buffer functionality - Added test for zero-player apple spawning - All 19 tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -29,18 +29,20 @@ class TestGameLogic:
|
||||
assert 0 <= food.position.y < GRID_HEIGHT
|
||||
|
||||
def test_update_snake_direction(self) -> None:
|
||||
"""Test updating snake direction."""
|
||||
"""Test updating snake direction via input buffer."""
|
||||
logic = GameLogic()
|
||||
snake = logic.create_snake("player1")
|
||||
logic.state.snakes.append(snake)
|
||||
|
||||
# Valid direction change
|
||||
# Direction changes go into buffer first
|
||||
logic.update_snake_direction("player1", UP)
|
||||
assert snake.direction == UP
|
||||
assert snake.input_buffer == [UP]
|
||||
assert snake.direction == RIGHT # Original direction unchanged
|
||||
|
||||
# Invalid 180-degree turn (should be ignored)
|
||||
logic.update_snake_direction("player1", DOWN)
|
||||
assert snake.direction == UP # Should remain UP
|
||||
# Moving consumes from buffer
|
||||
logic.move_snakes()
|
||||
assert snake.direction == UP # Now changed after movement
|
||||
assert len(snake.input_buffer) == 0
|
||||
|
||||
def test_move_snakes(self) -> None:
|
||||
"""Test snake movement."""
|
||||
@@ -252,3 +254,117 @@ class TestGameLogic:
|
||||
assert len(snake_a.body) == 1
|
||||
assert len(snake_b.body) == 1
|
||||
|
||||
def test_input_buffer_fills_to_three(self) -> None:
|
||||
"""Test input buffer fills up to 3 directions."""
|
||||
logic = GameLogic()
|
||||
snake = logic.create_snake("player1")
|
||||
logic.state.snakes.append(snake)
|
||||
|
||||
# Add 3 different directions
|
||||
logic.update_snake_direction("player1", UP)
|
||||
logic.update_snake_direction("player1", LEFT)
|
||||
logic.update_snake_direction("player1", DOWN)
|
||||
|
||||
assert len(snake.input_buffer) == 3
|
||||
assert snake.input_buffer == [UP, LEFT, DOWN]
|
||||
|
||||
def test_input_buffer_ignores_duplicates(self) -> None:
|
||||
"""Test input buffer ignores duplicate inputs."""
|
||||
logic = GameLogic()
|
||||
snake = logic.create_snake("player1")
|
||||
logic.state.snakes.append(snake)
|
||||
|
||||
logic.update_snake_direction("player1", UP)
|
||||
logic.update_snake_direction("player1", UP) # Duplicate
|
||||
|
||||
assert len(snake.input_buffer) == 1
|
||||
assert snake.input_buffer == [UP]
|
||||
|
||||
def test_input_buffer_opposite_replacement(self) -> None:
|
||||
"""Test opposite direction replaces last in buffer."""
|
||||
logic = GameLogic()
|
||||
snake = logic.create_snake("player1")
|
||||
logic.state.snakes.append(snake)
|
||||
|
||||
logic.update_snake_direction("player1", UP)
|
||||
logic.update_snake_direction("player1", DOWN) # Opposite to UP
|
||||
|
||||
# DOWN should replace UP
|
||||
assert len(snake.input_buffer) == 1
|
||||
assert snake.input_buffer == [DOWN]
|
||||
|
||||
def test_input_buffer_overflow_replacement(self) -> None:
|
||||
"""Test 4th input replaces last slot when buffer is full."""
|
||||
logic = GameLogic()
|
||||
snake = logic.create_snake("player1")
|
||||
logic.state.snakes.append(snake)
|
||||
|
||||
# Fill buffer with 3 directions
|
||||
logic.update_snake_direction("player1", UP)
|
||||
logic.update_snake_direction("player1", LEFT)
|
||||
logic.update_snake_direction("player1", DOWN)
|
||||
|
||||
# 4th input should replace last slot
|
||||
logic.update_snake_direction("player1", RIGHT)
|
||||
|
||||
assert len(snake.input_buffer) == 3
|
||||
assert snake.input_buffer == [UP, LEFT, RIGHT] # DOWN replaced by RIGHT
|
||||
|
||||
def test_input_buffer_consumption(self) -> None:
|
||||
"""Test buffer is consumed during movement."""
|
||||
logic = GameLogic()
|
||||
snake = Snake(player_id="player1", body=[
|
||||
Position(5, 5),
|
||||
Position(4, 5),
|
||||
Position(3, 5),
|
||||
], direction=RIGHT)
|
||||
logic.state.snakes.append(snake)
|
||||
|
||||
# Add direction to buffer
|
||||
snake.input_buffer = [UP]
|
||||
|
||||
logic.move_snakes()
|
||||
|
||||
# Buffer should be consumed and direction applied
|
||||
assert len(snake.input_buffer) == 0
|
||||
assert snake.direction == UP
|
||||
assert snake.get_head().y == 4 # Moved up
|
||||
|
||||
def test_input_buffer_skips_180_turn(self) -> None:
|
||||
"""Test buffer skips 180-degree turns for multi-segment snakes."""
|
||||
logic = GameLogic()
|
||||
snake = Snake(player_id="player1", body=[
|
||||
Position(5, 5),
|
||||
Position(4, 5),
|
||||
Position(3, 5),
|
||||
], direction=RIGHT)
|
||||
logic.state.snakes.append(snake)
|
||||
|
||||
# Buffer has opposite direction then valid direction
|
||||
snake.input_buffer = [LEFT, UP] # LEFT is 180° from RIGHT
|
||||
|
||||
logic.move_snakes()
|
||||
|
||||
# LEFT should be skipped, UP should be applied
|
||||
assert len(snake.input_buffer) == 0
|
||||
assert snake.direction == UP
|
||||
assert snake.get_head().y == 4 # Moved up
|
||||
|
||||
def test_zero_players_spawns_three_apples(self) -> None:
|
||||
"""Test field populates with 3 apples when no players."""
|
||||
logic = GameLogic()
|
||||
logic.state.game_running = True
|
||||
|
||||
# Start with no snakes and no food
|
||||
logic.state.snakes = []
|
||||
logic.state.food = []
|
||||
|
||||
# Update should populate 3 apples
|
||||
logic.update()
|
||||
|
||||
assert len(logic.state.food) == 3
|
||||
|
||||
# Subsequent updates should maintain 3 apples
|
||||
logic.update()
|
||||
assert len(logic.state.food) == 3
|
||||
|
||||
|
||||
Reference in New Issue
Block a user