CLAUDE: Remove defensive alignment field completely
Removed the unused alignment field from DefensiveDecision model and all related code across backend and frontend. Backend changes: - models/game_models.py: Removed alignment field and validator - terminal_client/display.py: Removed alignment from display - core/ai_opponent.py: Updated log message - tests/unit/models/test_game_models.py: Removed alignment tests - tests/unit/core/test_validators.py: Removed alignment validation test Frontend changes: - types/game.ts: Removed alignment from DefensiveDecision interface - components/Decisions/DefensiveSetup.vue: * Removed alignment section from template * Removed alignment from localSetup initialization * Removed alignmentOptions array * Removed alignmentDisplay computed property * Removed alignment from hasChanges comparison * Removed alignment from visual preview (reorganized to col-span-2) Rationale: Defensive alignment is not active in the game and will not be used. Per Cal's decision, remove completely rather than keep as dead code. Tests: All 728 backend unit tests passing (100%) Session 1 Part 3 - Change #6 complete Part of cleanup work from demo review 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2f0f35f951
commit
197d91edfb
227
.claude/HANDOFF_PROMPT_CLEANUP.md
Normal file
227
.claude/HANDOFF_PROMPT_CLEANUP.md
Normal file
@ -0,0 +1,227 @@
|
||||
# Context Window Handoff - Cleanup Work in Progress
|
||||
|
||||
**Date**: 2025-01-14
|
||||
**Current Session**: Cleanup of proposed changes from demo review
|
||||
**Status**: Session 1 Part 1 Complete (2/11 changes done)
|
||||
|
||||
---
|
||||
|
||||
## Copy This Prompt to New Context Window
|
||||
|
||||
```
|
||||
I'm continuing cleanup work on the Paper Dynasty / SBA web app. We're making changes based on a review of the frontend demo pages.
|
||||
|
||||
Current status: Session 1 Part 1 complete (removed 4 unused config fields). Now need to continue with Session 1 Part 2 and beyond.
|
||||
|
||||
Please read these files for full context:
|
||||
- @.claude/PROPOSED_CHANGES_2025-01-14.md (master list of all 11 changes)
|
||||
- @.claude/TODO_CLEANUP_COMPLETE.md (what we just finished)
|
||||
- @backend/app/config/base_config.py (we just modified this)
|
||||
- @backend/tests/unit/config/test_league_configs.py (we just modified this)
|
||||
|
||||
Key decisions from Cal:
|
||||
1. Remove supports_manual_result_selection() - DONE ✅
|
||||
2. Pitching change player ID - Will use league-independent polymorphic Player ID
|
||||
3. Remove defensive alignment & offensive approach - DONE, remove completely
|
||||
4. Offensive workflow refactor - YES, do before Phase F6 (last thing in cleanup)
|
||||
5. Hit location fix - Only needed for: GROUNDOUT, FLYOUT, LINEOUT, SINGLE_UNCAPPED, DOUBLE_UNCAPPED, ERROR
|
||||
|
||||
Current TODO list (in TodoWrite):
|
||||
- [completed] Remove strikes_for_out and balls_for_walk fields
|
||||
- [completed] Remove supports_manual_result_selection() method
|
||||
- [pending] Fix hit location requirements
|
||||
- [pending] Remove defensive alignment from DefensiveDecision model
|
||||
- [pending] Remove offensive approach from OffensiveDecision model
|
||||
- [pending] Add infield depth validation (infield_in, corners_in, normal)
|
||||
- [pending] Add outfield depth validation (normal, shallow with walk-off rules)
|
||||
- [pending] Refactor offensive decision workflow (swing_away, check_jump, hit_and_run, sac_bunt, squeeze_bunt)
|
||||
- [pending] Add check jump validation (lead runner only)
|
||||
|
||||
Workflow:
|
||||
- Session 1: Quick wins (Changes #1-5) - IN PROGRESS
|
||||
- Session 2: Standard changes (Changes #6-7) - NOT STARTED
|
||||
- Session 3: Major refactor (Changes #8-11) - NOT STARTED
|
||||
|
||||
Dev server is running at http://localhost:3005 with demo pages.
|
||||
|
||||
Please continue from Session 1 Part 2: Fix hit location requirements.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files Modified So Far (Session 1 Part 1)
|
||||
|
||||
### backend/app/config/base_config.py
|
||||
**Changes**: Removed 3 items
|
||||
- Line 27: `strikes_for_out` field
|
||||
- Line 28: `balls_for_walk` field
|
||||
- Lines 41-48: `supports_manual_result_selection()` method
|
||||
|
||||
**Current state**: Clean, tests passing
|
||||
|
||||
### backend/tests/unit/config/test_league_configs.py
|
||||
**Changes**: Updated 2 test methods
|
||||
- `test_sba_basic_rules`: Removed assertions for strikes/balls
|
||||
- `test_sba_supports_auto_mode`: Renamed from manual_selection, tests auto mode
|
||||
- `test_pd_basic_rules`: Removed assertions for strikes/balls
|
||||
- `test_pd_supports_auto_mode`: Renamed from manual_selection, tests auto mode
|
||||
|
||||
**Current state**: 28/28 tests passing
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (Session 1 Part 2)
|
||||
|
||||
### Change #5: Fix Hit Location Requirements
|
||||
|
||||
**Current Problem**:
|
||||
`frontend-sba/components/Gameplay/ManualOutcomeEntry.vue:152` has:
|
||||
```typescript
|
||||
const outcomesNeedingHitLocation = [
|
||||
'GROUNDOUT',
|
||||
'FLYOUT',
|
||||
'LINEOUT',
|
||||
'SINGLE_1', // ❌ Too broad
|
||||
'SINGLE_2', // ❌ Too broad
|
||||
'SINGLE_UNCAPPED', // ✅ Correct
|
||||
'DOUBLE_2', // ❌ Too broad
|
||||
'DOUBLE_3', // ❌ Too broad
|
||||
'DOUBLE_UNCAPPED', // ✅ Correct
|
||||
'TRIPLE', // ❌ Too broad
|
||||
'ERROR', // ✅ Correct
|
||||
]
|
||||
```
|
||||
|
||||
**Correct List** (per Cal):
|
||||
- GROUNDOUT (all groundouts)
|
||||
- FLYOUT (all flyouts)
|
||||
- LINEOUT (all lineouts)
|
||||
- SINGLE_UNCAPPED
|
||||
- DOUBLE_UNCAPPED
|
||||
- ERROR
|
||||
|
||||
**Files to Update**:
|
||||
1. `frontend-sba/components/Gameplay/ManualOutcomeEntry.vue:152` - Update array
|
||||
2. Verify `PlayOutcome.requires_hit_location()` matches in backend (if it exists)
|
||||
|
||||
**Expected outcome**: Only outcomes that affect defensive plays require hit location
|
||||
|
||||
---
|
||||
|
||||
## Remaining Work After Session 1
|
||||
|
||||
### Session 2: Standard Changes (2-4 hours)
|
||||
|
||||
**Change #6**: Remove defensive alignment
|
||||
- File: `backend/app/models/game_models.py` - DefensiveDecision model
|
||||
- File: `frontend-sba/components/Decisions/DefensiveSetup.vue`
|
||||
- Action: Remove `alignment` field completely
|
||||
|
||||
**Change #7**: Remove offensive approach
|
||||
- File: `backend/app/models/game_models.py` - OffensiveDecision model
|
||||
- File: `frontend-sba/components/Decisions/OffensiveApproach.vue`
|
||||
- Action: Remove `approach` field completely (will be replaced in Change #10)
|
||||
|
||||
**Change #8**: Add infield depth validation
|
||||
- Valid values: "infield_in", "corners_in", "normal"
|
||||
- Rule: infield_in and corners_in only legal with R3
|
||||
- Files: validators.py, DefensiveSetup.vue
|
||||
|
||||
**Change #9**: Add outfield depth validation
|
||||
- Valid values: "normal", "shallow"
|
||||
- Rule: shallow only legal when walk-off possible (home team, bottom 9+, trailing/tied, runner on base)
|
||||
- Files: validators.py, DefensiveSetup.vue
|
||||
|
||||
### Session 3: Major Refactor (4+ hours)
|
||||
|
||||
**Change #10**: Refactor offensive decision model
|
||||
- Replace `approach` field with specific actions
|
||||
- New actions: swing_away, check_jump, hit_and_run, sac_bunt, squeeze_bunt
|
||||
- Validation rules:
|
||||
- squeeze_bunt only with R3 and bases not loaded
|
||||
- check_jump only with runner on base
|
||||
- Files: OffensiveDecision model, OffensiveApproach.vue, tests
|
||||
- **Impact**: Will break 213 Phase F3 tests - need to update
|
||||
|
||||
**Change #11**: Check jump validation
|
||||
- Rule: Only lead runner or both if 1st and 3rd
|
||||
- No trail runners
|
||||
- Bundle with Change #10
|
||||
|
||||
---
|
||||
|
||||
## Git Status
|
||||
|
||||
**Branch**: `implement-phase-3`
|
||||
**Last Commit**: `eab61ad` - "Phases 3.5, F1-F5 Complete"
|
||||
**Working Tree**: Clean (all previous work committed)
|
||||
|
||||
**Commits This Session**:
|
||||
- Will need to commit cleanup work when done
|
||||
|
||||
---
|
||||
|
||||
## Test Status Baseline
|
||||
|
||||
**Before cleanup started**:
|
||||
- Backend: 731/731 passing (100%)
|
||||
- Frontend: 446/446 passing (100%)
|
||||
|
||||
**After Session 1 Part 1**:
|
||||
- Backend config tests: 28/28 passing (100%)
|
||||
- Need to verify full suite still passing
|
||||
|
||||
**Expected after Session 3**:
|
||||
- Backend tests: Will need updates for offensive decision refactor
|
||||
- Frontend tests: 213 F3 tests will need updates
|
||||
|
||||
---
|
||||
|
||||
## Key Context Files
|
||||
|
||||
**Master tracking**:
|
||||
- `.claude/PROPOSED_CHANGES_2025-01-14.md` - All 11 changes with analysis
|
||||
- `.claude/TODO_VERIFICATION_RESULTS.md` - What TODOs were already resolved
|
||||
- `.claude/TODO_SUMMARY.md` - Quick reference
|
||||
|
||||
**Implementation docs**:
|
||||
- `backend/app/config/CLAUDE.md` - Config system docs
|
||||
- `backend/app/models/CLAUDE.md` - Game models docs (will need for Changes #6-11)
|
||||
- `frontend-sba/CLAUDE.md` - Frontend docs
|
||||
|
||||
**Modified files so far**:
|
||||
- `backend/app/config/base_config.py`
|
||||
- `backend/tests/unit/config/test_league_configs.py`
|
||||
|
||||
---
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Dev server running**: http://localhost:3005 for testing frontend changes
|
||||
2. **Always run tests** after each change
|
||||
3. **Commit frequently** - don't batch all changes into one commit
|
||||
4. **Session 3 is complex** - offensive decision refactor affects many files
|
||||
5. **Follow workflow** - Don't skip to Session 3, do Session 1 & 2 first
|
||||
|
||||
---
|
||||
|
||||
## Questions Already Answered
|
||||
|
||||
Q: Remove or keep supports_manual_result_selection()?
|
||||
A: Remove completely ✅
|
||||
|
||||
Q: What identifier for pitching change?
|
||||
A: League-independent polymorphic Player ID
|
||||
|
||||
Q: Remove alignment and approach?
|
||||
A: Yes, remove completely
|
||||
|
||||
Q: When to do offensive refactor?
|
||||
A: Before Phase F6, as last part of cleanup
|
||||
|
||||
Q: Hit location requirements?
|
||||
A: Only GROUNDOUT, FLYOUT, LINEOUT, SINGLE_UNCAPPED, DOUBLE_UNCAPPED, ERROR
|
||||
|
||||
---
|
||||
|
||||
**Ready to continue!** Start with Session 1 Part 2: Fix hit location requirements.
|
||||
280
.claude/PROPOSED_CHANGES_2025-01-14.md
Normal file
280
.claude/PROPOSED_CHANGES_2025-01-14.md
Normal file
@ -0,0 +1,280 @@
|
||||
# Proposed Changes Analysis - 2025-01-14
|
||||
|
||||
**Date**: 2025-01-14
|
||||
**Source**: Cal's review of demo pages
|
||||
**Total Changes**: 11 items
|
||||
|
||||
---
|
||||
|
||||
## Quick Wins (Simple, Low Risk) - 3 items
|
||||
|
||||
### ✅ Change #1: Remove `strikes_for_out` field
|
||||
**Location**: `backend/app/config/base_config.py:27`
|
||||
**Change**: Delete line 27
|
||||
**Impact**: 1 file
|
||||
**Effort**: < 5 minutes
|
||||
**Risk**: None (already marked as TODO to remove)
|
||||
**Recommendation**: **PROCEED IMMEDIATELY**
|
||||
|
||||
---
|
||||
|
||||
### ✅ Change #2: Remove `balls_for_walk` field
|
||||
**Location**: `backend/app/config/base_config.py:28`
|
||||
**Change**: Delete line 28
|
||||
**Impact**: 1 file
|
||||
**Effort**: < 5 minutes
|
||||
**Risk**: None (already marked as TODO to remove)
|
||||
**Recommendation**: **PROCEED IMMEDIATELY**
|
||||
|
||||
---
|
||||
|
||||
### 🤔 Change #3: Remove/refactor `supports_manual_result_selection()`
|
||||
**Location**: `backend/app/config/base_config.py:41`
|
||||
**Current**: Method on BaseGameConfig
|
||||
**Question**: When would we ever use this function?
|
||||
|
||||
**Current Usage**:
|
||||
```python
|
||||
def supports_manual_result_selection(self) -> bool:
|
||||
# TODO: consider refactor: manually selecting results is default behavior
|
||||
# with PD allowing auto-results as an option
|
||||
return True
|
||||
```
|
||||
|
||||
**Analysis**:
|
||||
- SbaConfig: Always manual (no auto mode)
|
||||
- PdConfig: Supports both manual and auto
|
||||
|
||||
**Options**:
|
||||
1. **Remove entirely** - If we never check this
|
||||
2. **Rename to `supports_auto_mode()`** - Invert logic (clearer)
|
||||
3. **Keep as-is** - If we do use it
|
||||
|
||||
**Question for Cal**: Do we ever check if manual mode is supported? Or should this be `supports_auto_mode()` instead?
|
||||
|
||||
**Recommendation**: **NEEDS DECISION**
|
||||
|
||||
---
|
||||
|
||||
## Standard Changes (Need Review) - 4 items
|
||||
|
||||
### 🔍 Change #4: Pitching change player identification
|
||||
**Location**: `backend/app/websocket/handlers.py` (pitching_change event)
|
||||
**Current**: Uses `player_in_card_id`
|
||||
**Question**: Is card ID the best way to identify the new player from active roster?
|
||||
|
||||
**Current Implementation**:
|
||||
```python
|
||||
@sio.event
|
||||
async def request_pitching_change(sid, data):
|
||||
player_in_card_id = data.get("player_in_card_id") # Card ID
|
||||
```
|
||||
|
||||
**Analysis**:
|
||||
- **PD League**: `card_id` makes sense (cards are the entity)
|
||||
- **SBA League**: Uses `player_id` (different from card_id)
|
||||
- **Roster**: RosterLink table has both card_id and player_id (polymorphic)
|
||||
|
||||
**Options**:
|
||||
1. **Keep card_id** - Works for PD, need to translate for SBA
|
||||
2. **Use roster_link_id** - Unified identifier
|
||||
3. **League-specific fields** - `card_id` for PD, `player_id` for SBA
|
||||
|
||||
**Question for Cal**: What identifier does the frontend UI have access to when selecting a relief pitcher?
|
||||
|
||||
**Recommendation**: **NEEDS DECISION** (depends on frontend data model)
|
||||
|
||||
---
|
||||
|
||||
### 🔍 Change #5: Hit location enforcement
|
||||
**Current**: Enforcing hit location on too many results
|
||||
**Correct Behavior**:
|
||||
- Use `PlayOutcome.requires_hit_location()` (already exists)
|
||||
- Only required if runner on base who doesn't automatically score
|
||||
|
||||
**Files to Check**:
|
||||
- `frontend-sba/components/Gameplay/ManualOutcomeEntry.vue:152` (outcomesNeedingHitLocation array)
|
||||
- `backend/app/core/play_resolver.py` (validation logic)
|
||||
|
||||
**Analysis Needed**:
|
||||
1. What outcomes currently require hit location?
|
||||
2. What SHOULD require hit location per game rules?
|
||||
3. Does `PlayOutcome.requires_hit_location()` match game rules?
|
||||
|
||||
**Recommendation**: **NEEDS CODE REVIEW** - Let me check current implementation
|
||||
|
||||
---
|
||||
|
||||
### 🔍 Change #6: Remove Defensive Alignment
|
||||
**Current**: DefensiveDecision has `alignment` field
|
||||
**Proposed**: Remove or mark unused (not active in game)
|
||||
|
||||
**Impact**:
|
||||
- `backend/app/models/game_models.py` - DefensiveDecision model
|
||||
- `frontend-sba/components/Decisions/DefensiveSetup.vue` - UI component
|
||||
- `backend/app/websocket/handlers.py` - Event handler
|
||||
|
||||
**Options**:
|
||||
1. **Remove completely** - Clean up unused code
|
||||
2. **Keep but mark unused** - Future feature
|
||||
3. **Hide from UI** - Keep model field, remove UI
|
||||
|
||||
**Question for Cal**: Is this NEVER going to be used, or is it a future feature?
|
||||
|
||||
**Recommendation**: **NEEDS DECISION**
|
||||
|
||||
---
|
||||
|
||||
### 🔍 Change #7: Remove Offensive Approach
|
||||
**Current**: OffensiveDecision has `approach` field
|
||||
**Proposed**: Remove or mark unused (not active in game)
|
||||
|
||||
**Similar to Change #6** - same questions apply
|
||||
|
||||
**Recommendation**: **NEEDS DECISION**
|
||||
|
||||
---
|
||||
|
||||
## Complex Changes (Workflow Changes) - 4 items
|
||||
|
||||
### 💬 Change #8: Infield Depths - Simplify to 3 valid options
|
||||
**Current**: May have more options
|
||||
**Proposed Valid Values**:
|
||||
- `"infield_in"` (only legal with runner on 3rd)
|
||||
- `"corners_in"` (only legal with runner on 3rd)
|
||||
- `"normal"` (default)
|
||||
|
||||
**Impact**:
|
||||
- `backend/app/models/game_models.py` - DefensiveDecision validation
|
||||
- `backend/app/core/validators.py` - Add rule validation
|
||||
- `frontend-sba/components/Decisions/DefensiveSetup.vue` - UI options
|
||||
- Tests for validation
|
||||
|
||||
**Effort**: 1-2 hours
|
||||
**Risk**: Medium (validation logic)
|
||||
|
||||
**Recommendation**: **NEEDS APPROVAL** - Good cleanup, requires validation updates
|
||||
|
||||
---
|
||||
|
||||
### 💬 Change #9: Outfield Depths - Add legal constraints
|
||||
**Current**: May allow any value
|
||||
**Proposed Valid Values**:
|
||||
- `"normal"` (default, 99.99% of time)
|
||||
- `"shallow"` (only legal when batting team could walk-off win with runner on base)
|
||||
|
||||
**Impact**: Same as Change #8
|
||||
|
||||
**Question**: How do we determine "could walk-off win"?
|
||||
- Batter's team is home team
|
||||
- Bottom of 9th or later
|
||||
- Trailing or tied
|
||||
- Runner on base
|
||||
|
||||
**Effort**: 1-2 hours
|
||||
**Risk**: Medium (walk-off logic)
|
||||
|
||||
**Recommendation**: **NEEDS APPROVAL** - Adds game-accurate validation
|
||||
|
||||
---
|
||||
|
||||
### 💬 Change #10: Replace offensive approach with specific actions
|
||||
**Current**: OffensiveDecision has `approach` field
|
||||
**Proposed**: Replace with specific action choices:
|
||||
- `"swing_away"` (default)
|
||||
- `"check_jump"` (for lead runner only)
|
||||
- `"hit_and_run"`
|
||||
- `"sac_bunt"`
|
||||
- `"squeeze_bunt"` (only legal with R3 and bases not loaded)
|
||||
|
||||
**Impact**:
|
||||
- `backend/app/models/game_models.py` - Refactor OffensiveDecision
|
||||
- `frontend-sba/components/Decisions/OffensiveApproach.vue` - Complete UI redesign
|
||||
- `backend/app/websocket/handlers.py` - Update event handling
|
||||
- Tests (213 tests in Phase F3 may need updates)
|
||||
|
||||
**Effort**: 3-4 hours
|
||||
**Risk**: High (affects decision workflow, breaks existing tests)
|
||||
|
||||
**Recommendation**: **NEEDS DISCUSSION** - Major workflow change
|
||||
|
||||
---
|
||||
|
||||
### 💬 Change #11: Check jump only with lead runner
|
||||
**Current**: May allow checking jump with any runner
|
||||
**Proposed Rule**: Players may only check jump with:
|
||||
- Lead runner only
|
||||
- OR both runners if 1st and 3rd
|
||||
- NO checking jump with trail runners
|
||||
|
||||
**Impact**:
|
||||
- Validation logic for "check_jump" action (from Change #10)
|
||||
- Frontend UI to disable invalid options
|
||||
|
||||
**Effort**: 30 minutes (if Change #10 implemented)
|
||||
**Risk**: Low (validation rule)
|
||||
|
||||
**Recommendation**: **BUNDLE WITH CHANGE #10**
|
||||
|
||||
---
|
||||
|
||||
## Summary by Category
|
||||
|
||||
### Quick Wins (5 min each)
|
||||
1. ✅ Remove strikes_for_out
|
||||
2. ✅ Remove balls_for_walk
|
||||
3. 🤔 Remove/refactor supports_manual_result_selection() - **NEEDS DECISION**
|
||||
|
||||
### Standard Changes (1-2 hours each)
|
||||
4. 🔍 Pitching change identifier - **NEEDS DECISION**
|
||||
5. 🔍 Hit location enforcement - **NEEDS CODE REVIEW**
|
||||
6. 🔍 Remove defensive alignment - **NEEDS DECISION**
|
||||
7. 🔍 Remove offensive approach - **NEEDS DECISION** (conflicts with #10)
|
||||
|
||||
### Complex Changes (3-4 hours each)
|
||||
8. 💬 Infield depths validation - **NEEDS APPROVAL**
|
||||
9. 💬 Outfield depths validation - **NEEDS APPROVAL**
|
||||
10. 💬 Replace approach with actions - **NEEDS DISCUSSION** (breaks tests)
|
||||
11. 💬 Check jump lead runner only - **BUNDLE WITH #10**
|
||||
|
||||
---
|
||||
|
||||
## Recommended Approach
|
||||
|
||||
### Phase 1: Quick Wins (15 min)
|
||||
- Execute #1, #2 immediately
|
||||
- Decide on #3 (remove vs refactor vs keep)
|
||||
|
||||
### Phase 2: Decisions Needed (discussion)
|
||||
- #4: What identifier does frontend use?
|
||||
- #6, #7: Remove unused fields or keep for future?
|
||||
- These inform #10 (if we're removing approach anyway, don't refactor it)
|
||||
|
||||
### Phase 3: Code Review (30 min)
|
||||
- #5: Review hit location logic, fix if needed
|
||||
|
||||
### Phase 4: Validation Updates (2-4 hours)
|
||||
- #8, #9: Add depth validation rules
|
||||
- Only if approved
|
||||
|
||||
### Phase 5: Workflow Refactor (4+ hours)
|
||||
- #10, #11: Offensive action redesign
|
||||
- Major change, do last after everything else settled
|
||||
- Will break existing tests, need full re-test
|
||||
|
||||
---
|
||||
|
||||
## Questions for Cal
|
||||
|
||||
1. **Change #3**: Do we ever check `supports_manual_result_selection()`? Should we rename to `supports_auto_mode()` instead?
|
||||
|
||||
2. **Change #4**: What identifier does the frontend have when selecting a relief pitcher from the bench?
|
||||
|
||||
3. **Changes #6, #7**: Are alignment and approach NEVER going to be used, or future features we should keep?
|
||||
|
||||
4. **Change #10**: This is a major workflow change - are you sure we want to do this now (before Phase F6 integration)?
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Status**: Awaiting decisions on questions above
|
||||
@ -24,8 +24,6 @@ class BaseGameConfig(BaseModel, ABC):
|
||||
# Basic baseball rules (same across leagues)
|
||||
innings: int = Field(default=9, description="Standard innings per game")
|
||||
outs_per_inning: int = Field(default=3, description="Outs required per half-inning")
|
||||
strikes_for_out: int = Field(default=3, description="Strikes for strikeout") # TODO: remove - unneeded
|
||||
balls_for_walk: int = Field(default=4, description="Balls for walk") # TODO: remove - unneeded
|
||||
|
||||
@abstractmethod
|
||||
def get_result_chart_name(self) -> str:
|
||||
@ -37,16 +35,6 @@ class BaseGameConfig(BaseModel, ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def supports_manual_result_selection(self) -> bool: # TODO: consider refactor: manually selecting results is default behavior with PD allowing auto-results as an option
|
||||
"""
|
||||
Whether players manually select results after dice roll.
|
||||
|
||||
Returns:
|
||||
True if players pick from chart, False if auto-resolved
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def supports_auto_mode(self) -> bool:
|
||||
"""
|
||||
|
||||
@ -79,7 +79,7 @@ class AIOpponent:
|
||||
# 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}")
|
||||
logger.info(f"AI defensive decision: IF: {decision.infield_depth}, OF: {decision.outfield_depth}")
|
||||
return decision
|
||||
|
||||
async def generate_offensive_decision(
|
||||
|
||||
@ -151,20 +151,10 @@ class DefensiveDecision(BaseModel):
|
||||
|
||||
These decisions affect play outcomes (e.g., infield depth affects double play chances).
|
||||
"""
|
||||
alignment: str = "normal" # normal, shifted_left, shifted_right, extreme_shift
|
||||
infield_depth: str = "normal" # infield_in, normal, corners_in
|
||||
outfield_depth: str = "normal" # in, normal
|
||||
hold_runners: List[int] = Field(default_factory=list) # [1, 3] = hold 1st and 3rd
|
||||
|
||||
@field_validator('alignment')
|
||||
@classmethod
|
||||
def validate_alignment(cls, v: str) -> str:
|
||||
"""Validate alignment"""
|
||||
valid = ['normal', 'shifted_left', 'shifted_right', 'extreme_shift']
|
||||
if v not in valid:
|
||||
raise ValueError(f"alignment must be one of {valid}")
|
||||
return v
|
||||
|
||||
@field_validator('infield_depth')
|
||||
@classmethod
|
||||
def validate_infield_depth(cls, v: str) -> str:
|
||||
|
||||
@ -188,7 +188,6 @@ def display_decision(decision_type: str, decision: Optional[DefensiveDecision |
|
||||
decision_text = Text()
|
||||
|
||||
if isinstance(decision, DefensiveDecision):
|
||||
decision_text.append(f"Alignment: {decision.alignment}\n")
|
||||
decision_text.append(f"Infield Depth: {decision.infield_depth}\n")
|
||||
decision_text.append(f"Outfield Depth: {decision.outfield_depth}\n")
|
||||
if decision.hold_runners:
|
||||
|
||||
@ -42,18 +42,16 @@ class TestSbaConfig:
|
||||
config = SbaConfig()
|
||||
assert config.innings == 9
|
||||
assert config.outs_per_inning == 3
|
||||
assert config.strikes_for_out == 3
|
||||
assert config.balls_for_walk == 4
|
||||
|
||||
def test_sba_result_chart_name(self):
|
||||
"""SBA uses sba_standard_v1 chart."""
|
||||
config = SbaConfig()
|
||||
assert config.get_result_chart_name() == "sba_standard_v1"
|
||||
|
||||
def test_sba_supports_manual_selection(self):
|
||||
"""SBA supports manual result selection."""
|
||||
def test_sba_supports_auto_mode(self):
|
||||
"""SBA does not support auto mode (cards not digitized)."""
|
||||
config = SbaConfig()
|
||||
assert config.supports_manual_result_selection() is True
|
||||
assert config.supports_auto_mode() is False
|
||||
|
||||
def test_sba_api_url(self):
|
||||
"""SBA API URL is correct."""
|
||||
@ -86,18 +84,16 @@ class TestPdConfig:
|
||||
config = PdConfig()
|
||||
assert config.innings == 9
|
||||
assert config.outs_per_inning == 3
|
||||
assert config.strikes_for_out == 3
|
||||
assert config.balls_for_walk == 4
|
||||
|
||||
def test_pd_result_chart_name(self):
|
||||
"""PD uses pd_standard_v1 chart."""
|
||||
config = PdConfig()
|
||||
assert config.get_result_chart_name() == "pd_standard_v1"
|
||||
|
||||
def test_pd_supports_manual_selection(self):
|
||||
"""PD supports manual result selection (though auto is also available)."""
|
||||
def test_pd_supports_auto_mode(self):
|
||||
"""PD supports auto mode (digitized card data)."""
|
||||
config = PdConfig()
|
||||
assert config.supports_manual_result_selection() is True
|
||||
assert config.supports_auto_mode() is True
|
||||
|
||||
def test_pd_api_url(self):
|
||||
"""PD API URL is correct."""
|
||||
|
||||
@ -177,19 +177,6 @@ class TestDefensiveDecisionValidation:
|
||||
# Should not raise
|
||||
validator.validate_defensive_decision(decision, state)
|
||||
|
||||
def test_validate_defensive_decision_invalid_alignment(self):
|
||||
"""Test invalid alignment fails at Pydantic validation"""
|
||||
from pydantic_core import ValidationError as PydanticValidationError
|
||||
|
||||
# Pydantic catches invalid alignment at model creation
|
||||
with pytest.raises(PydanticValidationError) as exc_info:
|
||||
decision = DefensiveDecision(
|
||||
alignment="invalid_alignment",
|
||||
infield_depth="normal"
|
||||
)
|
||||
|
||||
assert "alignment" in str(exc_info.value).lower()
|
||||
|
||||
def test_validate_defensive_decision_invalid_depth(self):
|
||||
"""Test invalid infield depth fails at Pydantic validation"""
|
||||
from pydantic_core import ValidationError as PydanticValidationError
|
||||
|
||||
@ -227,23 +227,10 @@ class TestDefensiveDecision:
|
||||
def test_create_defensive_decision_defaults(self):
|
||||
"""Test creating defensive decision with defaults"""
|
||||
decision = DefensiveDecision()
|
||||
assert decision.alignment == "normal"
|
||||
assert decision.infield_depth == "normal"
|
||||
assert decision.outfield_depth == "normal"
|
||||
assert decision.hold_runners == []
|
||||
|
||||
def test_defensive_decision_valid_alignments(self):
|
||||
"""Test all valid alignments"""
|
||||
valid = ['normal', 'shifted_left', 'shifted_right', 'extreme_shift']
|
||||
for alignment in valid:
|
||||
decision = DefensiveDecision(alignment=alignment)
|
||||
assert decision.alignment == alignment
|
||||
|
||||
def test_defensive_decision_invalid_alignment(self):
|
||||
"""Test that invalid alignment raises error"""
|
||||
with pytest.raises(ValidationError):
|
||||
DefensiveDecision(alignment="invalid")
|
||||
|
||||
def test_defensive_decision_valid_infield_depths(self):
|
||||
"""Test all valid infield depths"""
|
||||
valid = ['infield_in', 'normal', 'corners_in']
|
||||
|
||||
@ -16,20 +16,6 @@
|
||||
|
||||
<!-- Form -->
|
||||
<form @submit.prevent="handleSubmit" class="space-y-6">
|
||||
<!-- Defensive Alignment -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">
|
||||
Defensive Alignment
|
||||
</label>
|
||||
<ButtonGroup
|
||||
v-model="localSetup.alignment"
|
||||
:options="alignmentOptions"
|
||||
:disabled="!isActive"
|
||||
size="md"
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Infield Depth -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">
|
||||
@ -92,10 +78,6 @@
|
||||
Current Setup
|
||||
</h4>
|
||||
<div class="grid grid-cols-2 gap-2 text-xs">
|
||||
<div>
|
||||
<span class="font-medium text-gray-600 dark:text-gray-400">Alignment:</span>
|
||||
<span class="ml-1 text-gray-900 dark:text-white">{{ alignmentDisplay }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-medium text-gray-600 dark:text-gray-400">Infield:</span>
|
||||
<span class="ml-1 text-gray-900 dark:text-white">{{ infieldDisplay }}</span>
|
||||
@ -104,7 +86,7 @@
|
||||
<span class="font-medium text-gray-600 dark:text-gray-400">Outfield:</span>
|
||||
<span class="ml-1 text-gray-900 dark:text-white">{{ outfieldDisplay }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="col-span-2">
|
||||
<span class="font-medium text-gray-600 dark:text-gray-400">Holding:</span>
|
||||
<span class="ml-1 text-gray-900 dark:text-white">{{ holdingDisplay }}</span>
|
||||
</div>
|
||||
@ -151,7 +133,6 @@ const emit = defineEmits<{
|
||||
// Local state
|
||||
const submitting = ref(false)
|
||||
const localSetup = ref<DefensiveDecision>({
|
||||
alignment: props.currentSetup?.alignment || 'normal',
|
||||
infield_depth: props.currentSetup?.infield_depth || 'normal',
|
||||
outfield_depth: props.currentSetup?.outfield_depth || 'normal',
|
||||
hold_runners: props.currentSetup?.hold_runners || [],
|
||||
@ -172,13 +153,6 @@ watch([holdFirst, holdSecond, holdThird], () => {
|
||||
})
|
||||
|
||||
// Options for ButtonGroup components
|
||||
const alignmentOptions: ButtonGroupOption[] = [
|
||||
{ value: 'normal', label: 'Normal', icon: '⚾' },
|
||||
{ value: 'shifted_left', label: 'Shift Left', icon: '←' },
|
||||
{ value: 'shifted_right', label: 'Shift Right', icon: '→' },
|
||||
{ value: 'extreme_shift', label: 'Extreme', icon: '↔️' },
|
||||
]
|
||||
|
||||
const infieldDepthOptions: ButtonGroupOption[] = [
|
||||
{ value: 'in', label: 'Infield In', icon: '⬆️' },
|
||||
{ value: 'normal', label: 'Normal', icon: '•' },
|
||||
@ -194,11 +168,6 @@ const outfieldDepthOptions: ButtonGroupOption[] = [
|
||||
]
|
||||
|
||||
// Display helpers
|
||||
const alignmentDisplay = computed(() => {
|
||||
const option = alignmentOptions.find(opt => opt.value === localSetup.value.alignment)
|
||||
return option?.label || 'Normal'
|
||||
})
|
||||
|
||||
const infieldDisplay = computed(() => {
|
||||
const option = infieldDepthOptions.find(opt => opt.value === localSetup.value.infield_depth)
|
||||
return option?.label || 'Normal'
|
||||
@ -223,7 +192,6 @@ const holdingDisplay = computed(() => {
|
||||
const hasChanges = computed(() => {
|
||||
if (!props.currentSetup) return true
|
||||
return (
|
||||
localSetup.value.alignment !== props.currentSetup.alignment ||
|
||||
localSetup.value.infield_depth !== props.currentSetup.infield_depth ||
|
||||
localSetup.value.outfield_depth !== props.currentSetup.outfield_depth ||
|
||||
JSON.stringify(localSetup.value.hold_runners) !== JSON.stringify(props.currentSetup.hold_runners)
|
||||
|
||||
@ -122,7 +122,6 @@ export interface GameState {
|
||||
* Backend: DefensiveDecision
|
||||
*/
|
||||
export interface DefensiveDecision {
|
||||
alignment: 'normal' | 'shifted_left' | 'shifted_right' | 'extreme_shift'
|
||||
infield_depth: 'in' | 'normal' | 'back' | 'double_play' | 'corners_in'
|
||||
outfield_depth: 'in' | 'normal' | 'back'
|
||||
hold_runners: number[] // Bases to hold (e.g., [1, 3])
|
||||
|
||||
Loading…
Reference in New Issue
Block a user