CLAUDE: Implement forced outcome feature for terminal client testing
Add ability to force specific play outcomes instead of random dice rolls, enabling targeted testing of specific game scenarios. Changes: - play_resolver.resolve_play(): Add forced_outcome parameter, bypass dice rolls when provided, create dummy AbRoll with placeholder values - game_engine.resolve_play(): Accept and pass through forced_outcome param - terminal_client/commands.py: Pass forced_outcome to game engine Testing: - Verified TRIPLE, HOMERUN, and STRIKEOUT outcomes work correctly - Dummy AbRoll properly constructed with all required fields - Game state updates correctly with forced outcomes Example usage in REPL: resolve_with triple resolve_with homerun Fixes terminal client testing workflow to allow controlled scenarios. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
16ba30b351
commit
8ecce0f5ad
@ -324,7 +324,7 @@ class GameEngine:
|
|||||||
logger.warning(f"Offensive decision timeout for game {state.game_id}, using default")
|
logger.warning(f"Offensive decision timeout for game {state.game_id}, using default")
|
||||||
return OffensiveDecision() # All defaults
|
return OffensiveDecision() # All defaults
|
||||||
|
|
||||||
async def resolve_play(self, game_id: UUID) -> PlayResult:
|
async def resolve_play(self, game_id: UUID, forced_outcome: Optional[PlayOutcome] = None) -> PlayResult:
|
||||||
"""
|
"""
|
||||||
Resolve the current play with dice roll
|
Resolve the current play with dice roll
|
||||||
|
|
||||||
@ -335,6 +335,13 @@ class GameEngine:
|
|||||||
4. Update game state in DB
|
4. Update game state in DB
|
||||||
5. Check for inning change (outs >= 3)
|
5. Check for inning change (outs >= 3)
|
||||||
6. Prepare next play (always last step)
|
6. Prepare next play (always last step)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
game_id: Game to resolve
|
||||||
|
forced_outcome: If provided, use this outcome instead of rolling dice (for testing)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PlayResult with complete outcome
|
||||||
"""
|
"""
|
||||||
state = state_manager.get_state(game_id)
|
state = state_manager.get_state(game_id)
|
||||||
if not state:
|
if not state:
|
||||||
@ -347,7 +354,7 @@ class GameEngine:
|
|||||||
offensive_decision = OffensiveDecision(**state.decisions_this_play.get('offensive', {}))
|
offensive_decision = OffensiveDecision(**state.decisions_this_play.get('offensive', {}))
|
||||||
|
|
||||||
# STEP 1: Resolve play (this internally calls dice_system.roll_ab)
|
# STEP 1: Resolve play (this internally calls dice_system.roll_ab)
|
||||||
result = play_resolver.resolve_play(state, defensive_decision, offensive_decision)
|
result = play_resolver.resolve_play(state, defensive_decision, offensive_decision, forced_outcome)
|
||||||
|
|
||||||
# Track roll for batch saving at end of inning
|
# Track roll for batch saving at end of inning
|
||||||
if game_id not in self._rolls_this_inning:
|
if game_id not in self._rolls_this_inning:
|
||||||
|
|||||||
@ -11,9 +11,10 @@ Updated: 2025-10-29 - Integrated universal PlayOutcome enum
|
|||||||
import logging
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
|
import pendulum
|
||||||
|
|
||||||
from app.core.dice import dice_system
|
from app.core.dice import dice_system
|
||||||
from app.core.roll_types import AbRoll
|
from app.core.roll_types import AbRoll, RollType
|
||||||
from app.models.game_models import GameState, DefensiveDecision, OffensiveDecision
|
from app.models.game_models import GameState, DefensiveDecision, OffensiveDecision
|
||||||
from app.config import PlayOutcome
|
from app.config import PlayOutcome
|
||||||
|
|
||||||
@ -135,7 +136,8 @@ class PlayResolver:
|
|||||||
self,
|
self,
|
||||||
state: GameState,
|
state: GameState,
|
||||||
defensive_decision: DefensiveDecision,
|
defensive_decision: DefensiveDecision,
|
||||||
offensive_decision: OffensiveDecision
|
offensive_decision: OffensiveDecision,
|
||||||
|
forced_outcome: Optional[PlayOutcome] = None
|
||||||
) -> PlayResult:
|
) -> PlayResult:
|
||||||
"""
|
"""
|
||||||
Resolve a complete play
|
Resolve a complete play
|
||||||
@ -144,22 +146,41 @@ class PlayResolver:
|
|||||||
state: Current game state
|
state: Current game state
|
||||||
defensive_decision: Defensive team's choices
|
defensive_decision: Defensive team's choices
|
||||||
offensive_decision: Offensive team's choices
|
offensive_decision: Offensive team's choices
|
||||||
|
forced_outcome: If provided, use this outcome instead of rolling dice (for testing)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PlayResult with complete outcome
|
PlayResult with complete outcome
|
||||||
"""
|
"""
|
||||||
logger.info(f"Resolving play - Inning {state.inning} {state.half}, {state.outs} outs")
|
logger.info(f"Resolving play - Inning {state.inning} {state.half}, {state.outs} outs")
|
||||||
|
|
||||||
# Roll dice using our advanced AbRoll system
|
if forced_outcome:
|
||||||
ab_roll = dice_system.roll_ab(
|
# Use forced outcome for testing (no dice roll)
|
||||||
league_id=state.league_id,
|
logger.info(f"Using forced outcome: {forced_outcome.value}")
|
||||||
game_id=state.game_id
|
outcome = forced_outcome
|
||||||
)
|
# Create a dummy AbRoll for the forced outcome
|
||||||
logger.info(f"AB Roll: {ab_roll}")
|
ab_roll = AbRoll(
|
||||||
|
roll_id=f"forced_{state.game_id}_{state.play_count}",
|
||||||
|
roll_type=RollType.AB,
|
||||||
|
league_id=state.league_id,
|
||||||
|
timestamp=pendulum.now('UTC'),
|
||||||
|
game_id=state.game_id,
|
||||||
|
d6_one=1, # Dummy values - not used for forced outcomes
|
||||||
|
d6_two_a=3,
|
||||||
|
d6_two_b=4,
|
||||||
|
chaos_d20=10,
|
||||||
|
resolution_d20=10
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Roll dice using our advanced AbRoll system
|
||||||
|
ab_roll = dice_system.roll_ab(
|
||||||
|
league_id=state.league_id,
|
||||||
|
game_id=state.game_id
|
||||||
|
)
|
||||||
|
logger.info(f"AB Roll: {ab_roll}")
|
||||||
|
|
||||||
# Get base outcome from chart
|
# Get base outcome from chart
|
||||||
outcome = self.result_chart.get_outcome(ab_roll)
|
outcome = self.result_chart.get_outcome(ab_roll)
|
||||||
logger.info(f"Base outcome: {outcome}")
|
logger.info(f"Base outcome: {outcome}")
|
||||||
|
|
||||||
# Apply decisions (simplified for Phase 2)
|
# Apply decisions (simplified for Phase 2)
|
||||||
# TODO: Implement full decision logic in Phase 3
|
# TODO: Implement full decision logic in Phase 3
|
||||||
|
|||||||
@ -211,11 +211,8 @@ class GameCommands:
|
|||||||
try:
|
try:
|
||||||
if forced_outcome:
|
if forced_outcome:
|
||||||
display.print_info(f"🎯 Forcing outcome: {forced_outcome.value}")
|
display.print_info(f"🎯 Forcing outcome: {forced_outcome.value}")
|
||||||
# TODO: Integrate with game_engine to properly apply forced outcomes
|
|
||||||
display.print_warning("⚠️ Manual outcome selection is experimental")
|
|
||||||
display.print_warning(" Using regular resolution for now (forced outcome noted)")
|
|
||||||
|
|
||||||
result = await game_engine.resolve_play(game_id)
|
result = await game_engine.resolve_play(game_id, forced_outcome)
|
||||||
state = await game_engine.get_game_state(game_id)
|
state = await game_engine.get_game_state(game_id)
|
||||||
|
|
||||||
if state:
|
if state:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user