Conversation
Co-authored-by: durangogt <10979447+durangogt@users.noreply.github.com>
Co-authored-by: durangogt <10979447+durangogt@users.noreply.github.com>
Co-authored-by: durangogt <10979447+durangogt@users.noreply.github.com>
…dling Co-authored-by: durangogt <10979447+durangogt@users.noreply.github.com>
Co-authored-by: durangogt <10979447+durangogt@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This pull request adds AI player capabilities to the Aggravation board game, enabling solo play and AI vs AI simulation. The implementation leverages the existing game engine's get_valid_moves() and execute_move() interface to provide three distinct AI strategies with different difficulty levels.
Changes:
- Adds three AI strategies (Random/Easy, Aggressive/Medium, Defensive/Hard) with distinct decision-making algorithms
- Implements interactive player selection screen for configuring Human vs AI players
- Provides three game modes: interactive player selection, watch AI vs AI, and headless simulation
- Includes comprehensive test suite with 19 tests covering all strategies and integration scenarios
- Updates documentation with detailed usage examples and strategy descriptions
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| ai_player.py | Implements base AIPlayer class and three concrete strategies (Random, Aggressive, Defensive) with move selection logic |
| player_selection.py | Provides pygame-based UI for interactive player type and strategy configuration |
| aggravation_ai.py | Main launcher supporting interactive, watch, and headless game modes with AI integration |
| test_ai_player.py | Comprehensive test suite with 19 tests covering factory creation, strategy behavior, and integration |
| demo_ai.py | Interactive demonstration script showing AI decision-making and move execution |
| README.md | Documentation updates with AI feature description, usage examples, and strategy explanations |
| AI_IMPLEMENTATION.md | Detailed implementation summary documenting architecture, requirements, and limitations |
| screenshots/player_selection.png | Screenshot of the player selection UI (binary file) |
| def _calculate_safety_score(self, game: AggravationGame, position: Tuple[int, int]) -> float: | ||
| """Calculate how safe a position is (higher = safer).""" | ||
| # Positions in final home are completely safe | ||
| if position in PLAYER_FINAL_HOMES[self.player_number]: | ||
| return 100 | ||
|
|
||
| # Positions in home stretch are safer | ||
| if position in PLAYER_HOME_STRETCHES[self.player_number]: | ||
| return 50 | ||
|
|
||
| # Check if any opponent could land on this position | ||
| danger_score = 30 # Base safety score | ||
|
|
||
| # Check all opponent marbles to see if they could reach this position | ||
| for opp_player in range(1, 5): | ||
| if opp_player == self.player_number: | ||
| continue | ||
|
|
||
| opp_data = game._get_player_data(opp_player) | ||
| opp_marbles = opp_data['marbles'] | ||
|
|
||
| for opp_marble in opp_marbles: | ||
| if opp_marble is None or opp_marble == (None, None): | ||
| continue | ||
|
|
||
| # Check if opponent could reach this position with any dice roll (1-6) | ||
| for dice in range(1, 7): | ||
| test_pos = opp_marble | ||
| for _ in range(dice): | ||
| try: | ||
| test_pos = game.get_next_position(test_pos[0], test_pos[1]) | ||
| except (AssertionError, IndexError): | ||
| # get_next_position uses assertions for validation | ||
| # TODO: game_engine should raise specific exceptions instead | ||
| break | ||
| if test_pos is None: | ||
| break | ||
|
|
||
| if test_pos == position: | ||
| danger_score -= 10 # Reduce safety score | ||
| break # No need to check other dice rolls for this marble | ||
|
|
||
| return max(0, danger_score) |
There was a problem hiding this comment.
The _calculate_safety_score method has O(n×m×6×d) complexity where n=number of opponents, m=marbles per player, d=max dice value. For each valid move being evaluated, this checks if any opponent could reach the position with any dice roll (1-6). While functional, this could be slow when evaluating multiple moves. Consider optimizing if performance becomes an issue, perhaps by caching threat calculations or using a simpler heuristic.
| """ | ||
|
|
||
| import sys | ||
| import os |
There was a problem hiding this comment.
The os module is imported but never used in this file. Consider removing this unused import.
| import os |
| import sys | ||
| import os | ||
| import random | ||
| import time |
There was a problem hiding this comment.
The time module is imported but never used in this file. The code uses pygame.time.wait() instead. Consider removing this unused import.
| import time |
|
|
||
| import sys | ||
| import os | ||
| import random |
There was a problem hiding this comment.
The random module is imported but never used in this file. Consider removing this unused import.
| import random |
| result = game.execute_move(current_player, move, dice_roll) | ||
| if result['success']: | ||
| print(f" Moved marble {move} from {result['old_position']} to {result['new_position']}") | ||
| if result.get('aggravation'): |
There was a problem hiding this comment.
The key name in the result dictionary doesn't match the game engine's return value. The execute_move function in game_engine.py returns 'aggravated_opponent' as the key (see game_engine.py:514, 563), but this code checks for 'aggravation'. This will always be falsy, preventing the aggravation message from being displayed.
| if result.get('aggravation'): | |
| if result.get('aggravated_opponent'): |
| result = game.execute_move(current_player, move, dice_roll) | ||
| if result['success']: | ||
| msg = f"Player {current_player}: Moved marble {move}" | ||
| if result.get('aggravation'): |
There was a problem hiding this comment.
The key name in the result dictionary doesn't match the game engine's return value. The execute_move function in game_engine.py returns 'aggravated_opponent' as the key (see game_engine.py:514, 563), but this code checks for 'aggravation'. This will always be falsy, preventing the aggravation message from being displayed.
| if result.get('aggravation'): | |
| if result.get('aggravated_opponent'): |
| print(f" AI moved marble {move}") | ||
| print(f" From: {result['old_position']}") | ||
| print(f" To: {result['new_position']}") | ||
| if result.get('aggravation'): |
There was a problem hiding this comment.
The key name in the result dictionary doesn't match the game engine's return value. The execute_move function in game_engine.py returns 'aggravated_opponent' as the key (see game_engine.py:514, 563), but this code checks for 'aggravation'. This will always be falsy, preventing the aggravation message from being displayed.
| if result.get('aggravation'): | |
| if result.get('aggravated_opponent'): |
Implements AI opponents to enable solo play and AI vs AI simulation. The existing
game_engine.pyarchitecture (get_valid_moves()+execute_move()) provided a clean interface for AI integration.Implementation
AI Strategy System
AIPlayerclass withchoose_move(game, dice_roll) -> Optional[int]interfaceRandomStrategy(easy): Random selection from valid movesAggressiveStrategy(medium): Scores moves by forward progress + aggravation potentialDefensiveStrategy(hard): Scores moves by safety + strategic positioningcreate_ai_player(strategy_name, player_number, difficulty)for instantiationPlayer Configuration
PlayerConfigclass tracking player type (Human/AI) and strategy selectionPlayerSelectionScreenwith pygame UI for interactive configurationGame Modes
aggravation_ai.pylauncher supporting three modes:--watchflag sets all players to AI--headlessflag runs terminal-based simulationUsage
Player Selection UI
Files Added
ai_player.py- Strategy implementations (410 lines)player_selection.py- Configuration UI (347 lines)aggravation_ai.py- Game launcher (245 lines)test_ai_player.py- Test suite (19 tests, all passing)demo_ai.py- Interactive demo (135 lines)Limitations
Original prompt
Title: Add AI Players with Configurable StrategiesLabel: featureBody:
Right now all 4 players are human-controlled. Adding AI opponents with selectable strategies (aggressive, defensive, shortcut-hunter, random) would make the game fun to play solo.
The game_engine.py architecture is already perfectly set up for this — get_valid_moves() + execute_move() is a clean AI interface.
Scope:
• Implement at least 3 AI strategy classes (random, aggressive, defensive)
• Add difficulty levels (easy/medium/hard)
• Support “watch AI vs AI” simulation mode
• Player selection screen to choose human vs AI for each seat</issue_description>
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.