From 2521833afb5f024d20520a8ef64c4ea20b9d65dd Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 19 Nov 2025 20:06:57 -0600 Subject: [PATCH] CLAUDE: Add configurable regulation_innings and outs_per_inning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- backend/.claude/CODE_REVIEW_GAME_ENGINE.md | 9 +++++--- backend/app/core/game_engine.py | 4 ++-- backend/app/core/validators.py | 24 ++++++++++++---------- backend/app/models/game_models.py | 23 +++++++++++++-------- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/backend/.claude/CODE_REVIEW_GAME_ENGINE.md b/backend/.claude/CODE_REVIEW_GAME_ENGINE.md index 426ecb7..e517fd5 100644 --- a/backend/.claude/CODE_REVIEW_GAME_ENGINE.md +++ b/backend/.claude/CODE_REVIEW_GAME_ENGINE.md @@ -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) diff --git a/backend/app/core/game_engine.py b/backend/app/core/game_engine.py index ca4f658..5b6f22d 100644 --- a/backend/app/core/game_engine.py +++ b/backend/app/core/game_engine.py @@ -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 diff --git a/backend/app/core/validators.py b/backend/app/core/validators.py index f9c72f5..795292c 100644 --- a/backend/app/core/validators.py +++ b/backend/app/core/validators.py @@ -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 diff --git a/backend/app/models/game_models.py b/backend/app/models/game_models.py index a210cbf..47cab89 100644 --- a/backend/app/models/game_models.py +++ b/backend/app/models/game_models.py @@ -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