strat-gameplay-webapp/frontend-sba/types/game.ts
Cal Corum 38fb76c849 CLAUDE: Fix resolution phase control and add demo mode
Bug fix: During resolution phase (dice rolling), isMyTurn was false
for both players, preventing anyone from seeing the dice roller.
Now the batting team has control during resolution since they read
their card.

Demo mode: myTeamId now returns whichever team needs to act,
allowing single-player testing of both sides.

Changes:
- Add creator_discord_id to GameState (backend + frontend types)
- Add get_current_user_optional dependency for optional auth
- Update quick-create to capture creator's discord_id
- Fix isMyTurn to give batting team control during resolution
- Demo mode: myTeamId returns active team based on phase

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 23:47:21 -06:00

338 lines
7.2 KiB
TypeScript

/**
* Game State and Play Result Types
*
* TypeScript definitions matching backend Pydantic models exactly.
* These types ensure type safety between frontend and backend.
*
* Backend Reference: app/models/game_models.py
*/
/**
* Game status enumeration
*/
export type GameStatus = 'pending' | 'active' | 'paused' | 'completed' | 'abandoned'
/**
* Inning half
*/
export type InningHalf = 'top' | 'bottom'
/**
* Game mode
*/
export type GameMode = 'live' | 'async' | 'vs_ai'
/**
* Game visibility
*/
export type GameVisibility = 'public' | 'private'
/**
* League identifier
*/
export type LeagueId = 'sba' | 'pd'
/**
* Decision phase in the play workflow
*
* Standardized naming (2025-01-21): Uses backend convention 'awaiting_*'
* for clarity about what action is pending.
*/
export type DecisionPhase = 'awaiting_defensive' | 'awaiting_stolen_base' | 'awaiting_offensive' | 'resolution' | 'complete'
/**
* Lineup player state - represents a player in the game
* Backend: LineupPlayerState
*/
export interface LineupPlayerState {
lineup_id: number
card_id: number
position: string
batting_order: number | null
is_active: boolean
position_rating?: {
range: number
error: number
innings: number
} | null
}
/**
* Core game state - complete representation of active game
* Backend: GameState
*/
export interface GameState {
// Identity
game_id: string
league_id: LeagueId
// Teams
home_team_id: number
away_team_id: number
home_team_is_ai: boolean
away_team_is_ai: boolean
// Creator (for demo/testing - creator can control home team)
creator_discord_id: string | null
// Resolution mode
auto_mode: boolean
// Game state
status: GameStatus
inning: number
half: InningHalf
outs: number
balls: number
strikes: number
home_score: number
away_score: number
// Runners (direct references)
on_first: LineupPlayerState | null
on_second: LineupPlayerState | null
on_third: LineupPlayerState | null
// Current players
current_batter: LineupPlayerState
current_pitcher: LineupPlayerState | null
current_catcher: LineupPlayerState | null
current_on_base_code: number
// Batting order tracking
away_team_batter_idx: number // 0-8
home_team_batter_idx: number // 0-8
// Decision tracking
pending_decision: DecisionPhase | null
decision_phase: DecisionPhase
decisions_this_play: Record<string, boolean>
pending_defensive_decision: DefensiveDecision | null
pending_offensive_decision: OffensiveDecision | null
// Manual mode
pending_manual_roll: RollData | null
// Play history
play_count: number
last_play_result: string | null
// Timestamps
created_at: string
started_at: string | null
completed_at: string | null
}
/**
* Defensive strategic decision
* Backend: DefensiveDecision
*/
export interface DefensiveDecision {
infield_depth: 'infield_in' | 'normal' | 'corners_in'
outfield_depth: 'normal' | 'shallow'
hold_runners: number[] // Bases to hold (e.g., [1, 3])
}
/**
* Offensive strategic decision
* Backend: OffensiveDecision
*
* Session 2 Update (2025-01-14): Replaced approach/hit_and_run/bunt_attempt with action field.
*/
export interface OffensiveDecision {
action: 'swing_away' | 'steal' | 'check_jump' | 'hit_and_run' | 'sac_bunt' | 'squeeze_bunt'
steal_attempts: number[] // Bases to steal (2, 3, or 4) - only used when action="steal"
}
/**
* Dice roll data from server
* Backend: AbRoll (simplified for frontend)
*/
export interface RollData {
roll_id: string
d6_one: number
d6_two_a: number
d6_two_b: number
d6_two_total: number
chaos_d20: number
resolution_d20: number
check_wild_pitch: boolean
check_passed_ball: boolean
timestamp: string
}
/**
* Play outcome enumeration (matches backend PlayOutcome enum values)
* Note: These are the STRING VALUES, not enum names
*/
export type PlayOutcome =
// Standard outs
| 'strikeout'
| 'groundball_a'
| 'groundball_b'
| 'groundball_c'
| 'flyout_a'
| 'flyout_b'
| 'flyout_bq'
| 'flyout_c'
| 'lineout'
| 'popout'
// Walks and hits by pitch
| 'walk'
| 'intentional_walk'
| 'hbp'
// Hits (capped - specific bases)
| 'single_1'
| 'single_2'
| 'single_uncapped'
| 'double_2'
| 'double_3'
| 'double_uncapped'
| 'triple'
| 'homerun'
// Interrupt plays (baserunning events)
| 'stolen_base'
| 'caught_stealing'
| 'wild_pitch'
| 'passed_ball'
| 'balk'
| 'pick_off'
// Errors
| 'error'
| 'x_check'
// Ballpark power (PD specific)
| 'bp_homerun'
| 'bp_flyout'
| 'bp_single'
| 'bp_lineout'
/**
* Runner advancement during a play
*/
export interface RunnerAdvancement {
from: number // 0=batter, 1-3=bases
to: number // 1-4=bases (4=home/scored)
lineup_id: number // Player's lineup ID for name lookup
is_out?: boolean // Runner was out during advancement (renamed from 'out')
}
/**
* Play resolution result from server
* Backend: PlayResult
*/
export interface PlayResult {
// Play identification
play_number: number
inning?: number // Inning when play occurred
half?: InningHalf // 'top' or 'bottom'
outcome: PlayOutcome
// Play description
description: string
hit_type?: string
// Results
outs_recorded: number
runs_scored: number
// Player identification for display
batter_lineup_id?: number // Batter's lineup ID for name lookup
// Runner advancement
runners_advanced: RunnerAdvancement[]
batter_result: number | null // Where batter ended up (1-4, null=out)
// Updated state snapshot
new_state: Partial<GameState>
// Categorization helpers
is_hit: boolean
is_out: boolean
is_walk: boolean
is_strikeout: boolean
// Roll reference (if manual mode)
roll_id?: string
// X-Check details (if defensive play)
x_check_details?: XCheckResult
}
/**
* X-Check resolution audit trail
*/
export interface XCheckResult {
check_position: string
defense_range: number
error_rating: number
d20_roll: number
error_3d6: number
base_result: string
error_result: string
final_outcome: PlayOutcome
held_runners: string[]
}
/**
* Decision prompt from server
* Backend: DecisionPrompt (WebSocket event)
*/
export interface DecisionPrompt {
phase: DecisionPhase
role: 'home' | 'away'
timeout_seconds: number
options?: string[]
message?: string
}
/**
* Manual outcome submission to server
* Backend: ManualOutcomeSubmission
*/
export interface ManualOutcomeSubmission {
outcome: PlayOutcome
hit_location?: string // Required for groundballs/flyballs
}
/**
* Game list item for active/completed games
*/
export interface GameListItem {
game_id: string
league_id: LeagueId
home_team_id: number
away_team_id: number
status: GameStatus
current_inning: number
current_half: InningHalf
home_score: number
away_score: number
started_at: string | null
completed_at: string | null
}
/**
* Game creation request
*/
export interface CreateGameRequest {
league_id: LeagueId
home_team_id: number
away_team_id: number
game_mode: GameMode
visibility: GameVisibility
}
/**
* Game creation response
*/
export interface CreateGameResponse {
game_id: string
status: GameStatus
created_at: string
}