strat-gameplay-webapp/backend/app/core/ai_opponent.py
Cal Corum 95d8703f56 CLAUDE: Implement Week 7 Task 1 - Strategic Decision Integration
Enhanced game engine with async decision workflow and AI opponent integration:

GameState Model Enhancements:
- Added pending_defensive_decision and pending_offensive_decision fields
- Added decision_phase tracking (idle, awaiting_defensive, awaiting_offensive, resolving, completed)
- Added decision_deadline field for timeout handling
- Added is_batting_team_ai() and is_fielding_team_ai() helper methods
- Added validator for decision_phase

StateManager Enhancements:
- Added _pending_decisions dict for asyncio.Future-based decision queue
- Added set_pending_decision() to create decision futures
- Added await_decision() to wait for decision submission
- Added submit_decision() to resolve pending futures
- Added cancel_pending_decision() for cleanup

GameEngine Enhancements:
- Added await_defensive_decision() with AI/human branching and timeout
- Added await_offensive_decision() with AI/human branching and timeout
- Enhanced submit_defensive_decision() to resolve pending futures
- Enhanced submit_offensive_decision() to resolve pending futures
- Added DECISION_TIMEOUT constant (30 seconds)

AI Opponent (Stub):
- Created ai_opponent.py with stub implementations
- generate_defensive_decision() returns default "normal" positioning
- generate_offensive_decision() returns default "normal" approach
- TODO markers for Week 9 full AI logic implementation

Integration:
- Backward compatible with existing terminal client workflow
- New async methods ready for WebSocket integration (Week 7 Task 4)
- AI teams get instant decisions, human teams wait with timeout
- Default decisions applied on timeout (no game blocking)

Testing:
- Config tests: 58/58 passing 
- Terminal client: Working perfectly 
- Existing workflows: Fully compatible 

Week 7 Task 1: Complete
Next: Task 2 - Decision Validators

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 21:38:11 -05:00

160 lines
4.8 KiB
Python

"""
AI Opponent - Automated decision-making for AI-controlled teams.
Provides strategic decision generation for AI opponents in single-player
and AI vs AI game modes.
This is a stub implementation for Week 7. Full AI logic will be developed
in Week 9 as part of Phase 3 AI opponent integration.
Author: Claude
Date: 2025-10-29
"""
import logging
from typing import Optional
from app.models.game_models import GameState, DefensiveDecision, OffensiveDecision
logger = logging.getLogger(f'{__name__}.AIOpponent')
class AIOpponent:
"""
AI opponent decision-making engine.
Generates defensive and offensive decisions for AI-controlled teams.
Current implementation uses simple heuristics. Full AI logic will be
implemented in Week 9.
"""
def __init__(self, difficulty: str = "balanced"):
"""
Initialize AI opponent.
Args:
difficulty: AI difficulty level
- "balanced": Standard decision-making
- "yolo": Aggressive playstyle (more risks)
- "safe": Conservative playstyle (fewer risks)
"""
self.difficulty = difficulty
logger.info(f"AIOpponent initialized with difficulty: {difficulty}")
async def generate_defensive_decision(
self,
state: GameState
) -> DefensiveDecision:
"""
Generate defensive decision for AI-controlled fielding team.
Week 7 stub: Returns default "normal" positioning.
Week 9: Implement full decision logic based on game situation.
Args:
state: Current game state
Returns:
DefensiveDecision with AI-generated strategy
TODO (Week 9):
- Analyze batter tendencies (pull/opposite field)
- Consider runner speed for hold decisions
- Evaluate double play opportunities
- Adjust positioning based on inning/score
"""
logger.debug(f"Generating defensive decision for game {state.game_id}")
# Week 7 stub: Simple default decision
decision = DefensiveDecision(
alignment="normal",
infield_depth="normal",
outfield_depth="normal",
hold_runners=[]
)
# TODO Week 9: Add actual AI logic
# if state.outs < 2 and state.is_runner_on_first():
# decision.infield_depth = "double_play"
# if state.is_runner_on_third() and state.outs < 2:
# decision.infield_depth = "in"
logger.info(f"AI defensive decision: {decision.alignment}, IF: {decision.infield_depth}")
return decision
async def generate_offensive_decision(
self,
state: GameState
) -> OffensiveDecision:
"""
Generate offensive decision for AI-controlled batting team.
Week 7 stub: Returns default "normal" approach.
Week 9: Implement full decision logic based on game situation.
Args:
state: Current game state
Returns:
OffensiveDecision with AI-generated strategy
TODO (Week 9):
- Evaluate stealing opportunities (runner speed, pitcher, catcher)
- Consider bunting in appropriate situations
- Adjust batting approach based on score/inning
- Implement hit-and-run logic
"""
logger.debug(f"Generating offensive decision for game {state.game_id}")
# Week 7 stub: Simple default decision
decision = OffensiveDecision(
approach="normal",
steal_attempts=[],
hit_and_run=False,
bunt_attempt=False
)
# TODO Week 9: Add actual AI logic
# if state.is_runner_on_first() and not state.is_runner_on_second():
# # Consider stealing second
# if self._should_attempt_steal(state):
# decision.steal_attempts = [2]
logger.info(f"AI offensive decision: {decision.approach}")
return decision
def _should_attempt_steal(self, state: GameState) -> bool:
"""
Determine if AI should attempt a steal (Week 9).
TODO: Implement steal decision logic
- Evaluate runner speed
- Evaluate pitcher hold rating
- Evaluate catcher arm
- Consider game situation (score, inning, outs)
Returns:
True if steal should be attempted
"""
# Week 7 stub: Never steal
return False
def _should_attempt_bunt(self, state: GameState) -> bool:
"""
Determine if AI should attempt a bunt (Week 9).
TODO: Implement bunt decision logic
- Consider sacrifice bunt situations
- Evaluate batter bunting ability
- Assess defensive positioning
Returns:
True if bunt should be attempted
"""
# Week 7 stub: Never bunt
return False
# Singleton instance for global access
ai_opponent = AIOpponent()