Features: - PlayerCardModal: Tap any player to view full playing card image - OutcomeWizard: Progressive 3-step outcome selection (On Base/Out/X-Check) - GameBoard: Expandable view showing all 9 fielder positions - Post-roll card display: Shows batter/pitcher card based on d6 roll - CurrentSituation: Tappable player cards with modal integration Bug fixes: - Fix batter not advancing after play (state_manager recovery logic) - Add dark mode support for buttons and panels (partial - iOS issue noted) New files: - PlayerCardModal.vue, OutcomeWizard.vue, BottomSheet.vue - outcomeFlow.ts constants for outcome category mapping - TEST_PLAN_UI_OVERHAUL.md with 23/24 tests passing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
221 lines
5.0 KiB
TypeScript
221 lines
5.0 KiB
TypeScript
/**
|
|
* Outcome Flow Constants
|
|
*
|
|
* Defines the hierarchical structure for the progressive disclosure
|
|
* outcome selection wizard. Three top-level categories branch into
|
|
* specific outcome types.
|
|
*
|
|
* Categories:
|
|
* - ON_BASE: Hits and walks that result in batter reaching base
|
|
* - OUT: Various types of outs
|
|
* - X_CHECK: Defensive X-Check resolution (errors result from this)
|
|
*/
|
|
|
|
import type { PlayOutcome } from '~/types/game'
|
|
|
|
/**
|
|
* Top-level outcome categories for step 1 selection
|
|
*/
|
|
export type OutcomeCategory = 'ON_BASE' | 'OUT' | 'X_CHECK'
|
|
|
|
/**
|
|
* Sub-categories for ON_BASE outcomes
|
|
*/
|
|
export type OnBaseSubCategory = 'SINGLE' | 'DOUBLE' | 'TRIPLE' | 'HOME_RUN' | 'WALK' | 'HBP'
|
|
|
|
/**
|
|
* Sub-categories for OUT outcomes
|
|
*/
|
|
export type OutSubCategory = 'STRIKEOUT' | 'GROUNDOUT' | 'FLYOUT' | 'LINEOUT' | 'POPOUT'
|
|
|
|
/**
|
|
* Category configuration with display info
|
|
*/
|
|
export interface CategoryConfig {
|
|
label: string
|
|
description: string
|
|
color: string
|
|
bgColor: string
|
|
borderColor: string
|
|
}
|
|
|
|
/**
|
|
* Sub-category configuration with outcomes
|
|
*/
|
|
export interface SubCategoryConfig {
|
|
label: string
|
|
outcomes: PlayOutcome[]
|
|
}
|
|
|
|
/**
|
|
* Category display configuration
|
|
*/
|
|
export const CATEGORY_CONFIG: Record<OutcomeCategory, CategoryConfig> = {
|
|
ON_BASE: {
|
|
label: 'On Base',
|
|
description: 'Hit, walk, or HBP',
|
|
color: 'text-green-700',
|
|
bgColor: 'bg-green-50 hover:bg-green-100',
|
|
borderColor: 'border-green-300',
|
|
},
|
|
OUT: {
|
|
label: 'Out',
|
|
description: 'Strikeout, groundout, flyout',
|
|
color: 'text-red-700',
|
|
bgColor: 'bg-red-50 hover:bg-red-100',
|
|
borderColor: 'border-red-300',
|
|
},
|
|
X_CHECK: {
|
|
label: 'X-Check',
|
|
description: 'Defensive play check',
|
|
color: 'text-orange-700',
|
|
bgColor: 'bg-orange-50 hover:bg-orange-100',
|
|
borderColor: 'border-orange-300',
|
|
},
|
|
}
|
|
|
|
/**
|
|
* ON_BASE sub-categories and their specific outcomes
|
|
*/
|
|
export const ON_BASE_OUTCOMES: Record<OnBaseSubCategory, SubCategoryConfig> = {
|
|
SINGLE: {
|
|
label: 'Single',
|
|
outcomes: ['single_1', 'single_2', 'single_uncapped'],
|
|
},
|
|
DOUBLE: {
|
|
label: 'Double',
|
|
outcomes: ['double_2', 'double_3', 'double_uncapped'],
|
|
},
|
|
TRIPLE: {
|
|
label: 'Triple',
|
|
outcomes: ['triple'],
|
|
},
|
|
HOME_RUN: {
|
|
label: 'Home Run',
|
|
outcomes: ['homerun'],
|
|
},
|
|
WALK: {
|
|
label: 'Walk',
|
|
outcomes: ['walk', 'intentional_walk'],
|
|
},
|
|
HBP: {
|
|
label: 'Hit By Pitch',
|
|
outcomes: ['hbp'],
|
|
},
|
|
}
|
|
|
|
/**
|
|
* OUT sub-categories and their specific outcomes
|
|
*/
|
|
export const OUT_OUTCOMES: Record<OutSubCategory, SubCategoryConfig> = {
|
|
STRIKEOUT: {
|
|
label: 'Strikeout',
|
|
outcomes: ['strikeout'],
|
|
},
|
|
GROUNDOUT: {
|
|
label: 'Groundout',
|
|
outcomes: ['groundball_a', 'groundball_b', 'groundball_c'],
|
|
},
|
|
FLYOUT: {
|
|
label: 'Flyout',
|
|
outcomes: ['flyout_a', 'flyout_b', 'flyout_bq', 'flyout_c'],
|
|
},
|
|
LINEOUT: {
|
|
label: 'Lineout',
|
|
outcomes: ['lineout'],
|
|
},
|
|
POPOUT: {
|
|
label: 'Popout',
|
|
outcomes: ['popout'],
|
|
},
|
|
}
|
|
|
|
/**
|
|
* X-CHECK outcome (singular - error is a result, not input)
|
|
*/
|
|
export const X_CHECK_OUTCOMES: PlayOutcome[] = ['x_check']
|
|
|
|
/**
|
|
* Outcome display labels for final selection
|
|
*/
|
|
export const OUTCOME_LABELS: Partial<Record<PlayOutcome, string>> = {
|
|
// Singles
|
|
single_1: 'Single (1 base)',
|
|
single_2: 'Single (2 bases)',
|
|
single_uncapped: 'Single (uncapped)',
|
|
|
|
// Doubles
|
|
double_2: 'Double (2 bases)',
|
|
double_3: 'Double (3 bases)',
|
|
double_uncapped: 'Double (uncapped)',
|
|
|
|
// Other hits
|
|
triple: 'Triple',
|
|
homerun: 'Home Run',
|
|
|
|
// Walks
|
|
walk: 'Walk',
|
|
intentional_walk: 'Intentional Walk',
|
|
hbp: 'Hit By Pitch',
|
|
|
|
// Outs
|
|
strikeout: 'Strikeout',
|
|
groundball_a: 'Groundball A',
|
|
groundball_b: 'Groundball B',
|
|
groundball_c: 'Groundball C',
|
|
flyout_a: 'Flyout A',
|
|
flyout_b: 'Flyout B',
|
|
flyout_bq: 'Flyout B*',
|
|
flyout_c: 'Flyout C',
|
|
lineout: 'Lineout',
|
|
popout: 'Popout',
|
|
|
|
// X-Check
|
|
x_check: 'X-Check',
|
|
}
|
|
|
|
/**
|
|
* Hit location positions for location selection step
|
|
*/
|
|
export const HIT_LOCATIONS = [
|
|
{ id: 'P', label: 'P', position: 'Pitcher' },
|
|
{ id: 'C', label: 'C', position: 'Catcher' },
|
|
{ id: '1B', label: '1B', position: 'First Base' },
|
|
{ id: '2B', label: '2B', position: 'Second Base' },
|
|
{ id: 'SS', label: 'SS', position: 'Shortstop' },
|
|
{ id: '3B', label: '3B', position: 'Third Base' },
|
|
{ id: 'LF', label: 'LF', position: 'Left Field' },
|
|
{ id: 'CF', label: 'CF', position: 'Center Field' },
|
|
{ id: 'RF', label: 'RF', position: 'Right Field' },
|
|
] as const
|
|
|
|
/**
|
|
* Outcomes that require hit location selection
|
|
*/
|
|
export const OUTCOMES_REQUIRING_LOCATION: PlayOutcome[] = [
|
|
'groundball_a',
|
|
'groundball_b',
|
|
'groundball_c',
|
|
'flyout_a',
|
|
'flyout_b',
|
|
'flyout_bq',
|
|
'flyout_c',
|
|
'lineout',
|
|
'popout',
|
|
'x_check',
|
|
]
|
|
|
|
/**
|
|
* Check if an outcome requires a hit location
|
|
*/
|
|
export function requiresHitLocation(outcome: PlayOutcome): boolean {
|
|
return OUTCOMES_REQUIRING_LOCATION.includes(outcome)
|
|
}
|
|
|
|
/**
|
|
* Get the display label for an outcome
|
|
*/
|
|
export function getOutcomeLabel(outcome: PlayOutcome): string {
|
|
return OUTCOME_LABELS[outcome] || outcome
|
|
}
|