CLAUDE: Add configurable regulation_innings and outs_per_inning
Issue #8 from code review - hardcoded inning limit (9) and outs (3) prevented custom game modes like 7-inning doubleheaders. Changes: - Added regulation_innings: int = 9 to GameState (default standard) - Added outs_per_inning: int = 3 to GameState (default standard) - Updated validators.py: is_game_over(), can_continue_inning(), shallow outfield - Updated game_models.py: is_game_over() uses state.regulation_innings - Updated game_engine.py: _finalize_play() uses state.outs_per_inning Now supports custom game modes: - 7-inning doubleheaders - 6-inning youth/minor league games - Alternative out rules (2-out innings, etc.) All 739 unit tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
86f671ba0c
commit
2521833afb
@ -295,9 +295,12 @@ Some methods seem designed for testing but are public API.
|
||||
- Validation exists in validators.py: hold_runners validates against occupied bases (lines 71-77)
|
||||
- Validation exists in validators.py: steal_attempts validates against runner state (lines 156-165)
|
||||
- Validators called in submit_defensive_decision (line 225) and submit_offensive_decision (line 263)
|
||||
- [x] High issue #8 deferred to next sprint (2025-11-19)
|
||||
- Hardcoded inning limit (9) used in validators.py and game_models.py
|
||||
- Requires config system changes - appropriate for technical debt phase
|
||||
- [x] High issue #8 fixed (2025-11-19)
|
||||
- Added `regulation_innings: int = 9` and `outs_per_inning: int = 3` to GameState
|
||||
- Updated validators.py: `is_game_over()`, `can_continue_inning()`, shallow outfield check
|
||||
- Updated game_models.py: `is_game_over()` method
|
||||
- Updated game_engine.py: `_finalize_play()` inning change checks
|
||||
- Now supports 7-inning doubleheaders, 6-inning youth games, etc.
|
||||
- [x] High issue #9 already fixed (part of Issue #3)
|
||||
- `_cleanup_game_resources()` called in `end_game()` at line 1109
|
||||
- [x] High issue #10 acknowledged (2025-11-19)
|
||||
|
||||
@ -474,7 +474,7 @@ class GameEngine:
|
||||
logger.debug("Skipped game state update - no changes to persist")
|
||||
|
||||
# Check for inning change
|
||||
if state.outs >= 3:
|
||||
if state.outs >= state.outs_per_inning:
|
||||
await self._advance_inning(state, game_id)
|
||||
# Update DB again after inning change
|
||||
await self.db_ops.update_game_state(
|
||||
@ -497,7 +497,7 @@ class GameEngine:
|
||||
raise
|
||||
|
||||
# Batch save rolls at half-inning boundary (separate transaction - audit data)
|
||||
if state.outs >= 3:
|
||||
if state.outs >= state.outs_per_inning:
|
||||
await self._batch_save_inning_rolls(game_id)
|
||||
|
||||
# Prepare next play or clean up if game completed
|
||||
|
||||
@ -85,11 +85,11 @@ class GameValidator:
|
||||
if decision.outfield_depth == 'shallow':
|
||||
# Walk-off conditions:
|
||||
# 1. Home team batting (bottom of inning)
|
||||
# 2. Bottom of 9th or later
|
||||
# 2. Bottom of final inning or later
|
||||
# 3. Tied or trailing
|
||||
# 4. Runner on base
|
||||
is_home_batting = (state.half == 'bottom')
|
||||
is_late_inning = (state.inning >= 9)
|
||||
is_late_inning = (state.inning >= state.regulation_innings)
|
||||
|
||||
if is_home_batting:
|
||||
is_close_game = (state.home_score <= state.away_score)
|
||||
@ -100,8 +100,8 @@ class GameValidator:
|
||||
|
||||
if not (is_home_batting and is_late_inning and is_close_game and has_runners):
|
||||
raise ValidationError(
|
||||
"Shallow outfield only allowed in walk-off situations "
|
||||
"(home team batting, bottom 9th+ inning, tied/trailing, runner on base)"
|
||||
f"Shallow outfield only allowed in walk-off situations "
|
||||
f"(home team batting, bottom {state.regulation_innings}th+ inning, tied/trailing, runner on base)"
|
||||
)
|
||||
|
||||
logger.debug("Defensive decision validated")
|
||||
@ -202,21 +202,23 @@ class GameValidator:
|
||||
|
||||
@staticmethod
|
||||
def can_continue_inning(state: GameState) -> bool:
|
||||
"""Check if inning can continue"""
|
||||
return state.outs < 3
|
||||
"""Check if inning can continue using state's outs_per_inning"""
|
||||
return state.outs < state.outs_per_inning
|
||||
|
||||
@staticmethod
|
||||
def is_game_over(state: GameState) -> bool:
|
||||
"""Check if game is complete"""
|
||||
# Game over after 9 innings if score not tied
|
||||
if state.inning >= 9 and state.half == "bottom":
|
||||
"""Check if game is complete using state's regulation_innings"""
|
||||
reg = state.regulation_innings
|
||||
|
||||
# Game over after regulation innings if score not tied
|
||||
if state.inning >= reg and state.half == "bottom":
|
||||
if state.home_score != state.away_score:
|
||||
return True
|
||||
# Home team wins if ahead in bottom of 9th
|
||||
# Home team wins if ahead in bottom of final inning
|
||||
if state.home_score > state.away_score:
|
||||
return True
|
||||
# Also check if we're in extras and bottom team is ahead
|
||||
if state.inning > 9 and state.half == "bottom":
|
||||
if state.inning > reg and state.half == "bottom":
|
||||
if state.home_score > state.away_score:
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -374,6 +374,10 @@ class GameState(BaseModel):
|
||||
# Resolution mode
|
||||
auto_mode: bool = False # True = auto-generate outcomes (PD only), False = manual submissions
|
||||
|
||||
# Game rules (configurable per game)
|
||||
regulation_innings: int = Field(default=9, ge=1) # Standard 9, doubleheader 7, etc.
|
||||
outs_per_inning: int = Field(default=3, ge=1) # Standard 3
|
||||
|
||||
# Game state
|
||||
status: str = "pending" # pending, active, paused, completed
|
||||
inning: int = Field(default=1, ge=1)
|
||||
@ -646,24 +650,27 @@ class GameState(BaseModel):
|
||||
"""
|
||||
Check if game is over.
|
||||
|
||||
Game ends after 9 innings if home team is ahead or tied,
|
||||
or immediately if home team takes lead in bottom of 9th or later.
|
||||
Game ends after regulation innings if home team is ahead or tied,
|
||||
or immediately if home team takes lead in bottom of final inning or later.
|
||||
Uses self.regulation_innings (default 9) for game length.
|
||||
"""
|
||||
if self.inning < 9:
|
||||
reg = self.regulation_innings
|
||||
|
||||
if self.inning < reg:
|
||||
return False
|
||||
|
||||
if self.inning == 9 and self.half == "bottom":
|
||||
# Bottom of 9th - game ends if home team ahead
|
||||
if self.inning == reg and self.half == "bottom":
|
||||
# Bottom of final regulation inning - game ends if home team ahead
|
||||
if self.home_score > self.away_score:
|
||||
return True
|
||||
|
||||
if self.inning > 9 and self.half == "bottom":
|
||||
if self.inning > reg and self.half == "bottom":
|
||||
# Extra innings, bottom half - walk-off possible
|
||||
if self.home_score > self.away_score:
|
||||
return True
|
||||
|
||||
if self.inning >= 9 and self.half == "top" and self.outs >= 3:
|
||||
# Top of 9th or later just ended
|
||||
if self.inning >= reg and self.half == "top" and self.outs >= self.outs_per_inning:
|
||||
# Top of final inning or later just ended
|
||||
if self.home_score != self.away_score:
|
||||
return True
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user