Removed all references to the defensive alignment field across frontend codebase after backend removal in Session 1. The alignment field was determined to be unused and was removed from DefensiveDecision model. Changes: - types/websocket.ts: Removed alignment from DefensiveDecisionRequest interface - composables/useGameActions.ts: Removed alignment from submit handler - pages/demo-decisions.vue: Updated demo state and summary text (alignment → depths) - pages/games/[id].vue: Updated decision history text for both defensive and offensive * Defensive: Now shows "infield depth, outfield depth" instead of "alignment, infield" * Offensive: Updated to use new action field with proper labels (swing_away, hit_and_run, etc.) - Test files (3): Updated all test cases to remove alignment references * tests/unit/composables/useGameActions.spec.ts * tests/unit/store/game-decisions.spec.ts * tests/unit/components/Decisions/DefensiveSetup.spec.ts Also updated offensive decision handling to match Session 2 changes (approach/hit_and_run/bunt_attempt → action field). Total: 7 files modified, all alignment references removed Verified: Zero remaining alignment references in .ts/.vue/.js files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
312 lines
7.7 KiB
TypeScript
312 lines
7.7 KiB
TypeScript
/**
|
|
* Game Actions Composable
|
|
*
|
|
* Provides type-safe methods for emitting game actions to the server.
|
|
* Wraps Socket.io emit calls with proper error handling and validation.
|
|
*
|
|
* This composable is league-agnostic and will be shared between SBA and PD.
|
|
*/
|
|
|
|
import { computed } from 'vue'
|
|
import type {
|
|
DefensiveDecision,
|
|
OffensiveDecision,
|
|
ManualOutcomeSubmission,
|
|
PlayOutcome,
|
|
} from '~/types'
|
|
import { useWebSocket } from './useWebSocket'
|
|
import { useGameStore } from '~/store/game'
|
|
import { useUiStore } from '~/store/ui'
|
|
|
|
export function useGameActions(gameId?: string) {
|
|
const { socket, isConnected } = useWebSocket()
|
|
const gameStore = useGameStore()
|
|
const uiStore = useUiStore()
|
|
|
|
// Use provided gameId or get from store
|
|
const currentGameId = computed(() => gameId || gameStore.gameId)
|
|
|
|
/**
|
|
* Validate socket connection before emitting
|
|
*/
|
|
function validateConnection(): boolean {
|
|
if (!isConnected.value) {
|
|
uiStore.showError('Not connected to game server')
|
|
return false
|
|
}
|
|
|
|
if (!socket.value) {
|
|
uiStore.showError('WebSocket not initialized')
|
|
return false
|
|
}
|
|
|
|
if (!currentGameId.value) {
|
|
uiStore.showError('No active game')
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// ============================================================================
|
|
// Connection Actions
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Join a game room
|
|
*/
|
|
function joinGame(role: 'player' | 'spectator' = 'player') {
|
|
if (!validateConnection()) return
|
|
|
|
console.log(`[GameActions] Joining game ${currentGameId.value} as ${role}`)
|
|
|
|
socket.value!.emit('join_game', {
|
|
game_id: currentGameId.value!,
|
|
role,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Leave current game room
|
|
*/
|
|
function leaveGame() {
|
|
if (!socket.value || !currentGameId.value) return
|
|
|
|
console.log(`[GameActions] Leaving game ${currentGameId.value}`)
|
|
|
|
socket.value.emit('leave_game', {
|
|
game_id: currentGameId.value,
|
|
})
|
|
|
|
// Clear game state
|
|
gameStore.resetGame()
|
|
}
|
|
|
|
// ============================================================================
|
|
// Strategic Decision Actions
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Submit defensive decision
|
|
*/
|
|
function submitDefensiveDecision(decision: DefensiveDecision) {
|
|
if (!validateConnection()) return
|
|
|
|
console.log('[GameActions] Submitting defensive decision:', decision)
|
|
|
|
socket.value!.emit('submit_defensive_decision', {
|
|
game_id: currentGameId.value!,
|
|
infield_depth: decision.infield_depth,
|
|
outfield_depth: decision.outfield_depth,
|
|
hold_runners: decision.hold_runners,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Submit offensive decision
|
|
*/
|
|
function submitOffensiveDecision(decision: OffensiveDecision) {
|
|
if (!validateConnection()) return
|
|
|
|
console.log('[GameActions] Submitting offensive decision:', decision)
|
|
|
|
socket.value!.emit('submit_offensive_decision', {
|
|
game_id: currentGameId.value!,
|
|
approach: decision.approach,
|
|
steal_attempts: decision.steal_attempts,
|
|
hit_and_run: decision.hit_and_run,
|
|
bunt_attempt: decision.bunt_attempt,
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Manual Outcome Workflow
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Roll dice for manual outcome selection
|
|
*/
|
|
function rollDice() {
|
|
if (!validateConnection()) return
|
|
|
|
if (!gameStore.canRollDice) {
|
|
uiStore.showWarning('Cannot roll dice at this time')
|
|
return
|
|
}
|
|
|
|
console.log('[GameActions] Rolling dice')
|
|
|
|
socket.value!.emit('roll_dice', {
|
|
game_id: currentGameId.value!,
|
|
})
|
|
|
|
uiStore.showInfo('Rolling dice...', 2000)
|
|
}
|
|
|
|
/**
|
|
* Submit manual outcome after reading card
|
|
*/
|
|
function submitManualOutcome(outcome: PlayOutcome, hitLocation?: string) {
|
|
if (!validateConnection()) return
|
|
|
|
if (!gameStore.canSubmitOutcome) {
|
|
uiStore.showWarning('Must roll dice first')
|
|
return
|
|
}
|
|
|
|
console.log('[GameActions] Submitting outcome:', outcome, hitLocation)
|
|
|
|
socket.value!.emit('submit_manual_outcome', {
|
|
game_id: currentGameId.value!,
|
|
outcome: outcome,
|
|
hit_location: hitLocation,
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Substitution Actions
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Request pinch hitter substitution
|
|
*/
|
|
function requestPinchHitter(
|
|
playerOutLineupId: number,
|
|
playerInCardId: number,
|
|
teamId: number
|
|
) {
|
|
if (!validateConnection()) return
|
|
|
|
console.log('[GameActions] Requesting pinch hitter')
|
|
|
|
socket.value!.emit('request_pinch_hitter', {
|
|
game_id: currentGameId.value!,
|
|
player_out_lineup_id: playerOutLineupId,
|
|
player_in_card_id: playerInCardId,
|
|
team_id: teamId,
|
|
})
|
|
|
|
uiStore.showInfo('Requesting pinch hitter...', 3000)
|
|
}
|
|
|
|
/**
|
|
* Request defensive replacement
|
|
*/
|
|
function requestDefensiveReplacement(
|
|
playerOutLineupId: number,
|
|
playerInCardId: number,
|
|
newPosition: string,
|
|
teamId: number
|
|
) {
|
|
if (!validateConnection()) return
|
|
|
|
console.log('[GameActions] Requesting defensive replacement')
|
|
|
|
socket.value!.emit('request_defensive_replacement', {
|
|
game_id: currentGameId.value!,
|
|
player_out_lineup_id: playerOutLineupId,
|
|
player_in_card_id: playerInCardId,
|
|
new_position: newPosition,
|
|
team_id: teamId,
|
|
})
|
|
|
|
uiStore.showInfo('Requesting defensive replacement...', 3000)
|
|
}
|
|
|
|
/**
|
|
* Request pitching change
|
|
*/
|
|
function requestPitchingChange(
|
|
playerOutLineupId: number,
|
|
playerInCardId: number,
|
|
teamId: number
|
|
) {
|
|
if (!validateConnection()) return
|
|
|
|
console.log('[GameActions] Requesting pitching change')
|
|
|
|
socket.value!.emit('request_pitching_change', {
|
|
game_id: currentGameId.value!,
|
|
player_out_lineup_id: playerOutLineupId,
|
|
player_in_card_id: playerInCardId,
|
|
team_id: teamId,
|
|
})
|
|
|
|
uiStore.showInfo('Requesting pitching change...', 3000)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Data Request Actions
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Get lineup for a team
|
|
*/
|
|
function getLineup(teamId: number) {
|
|
if (!validateConnection()) return
|
|
|
|
console.log('[GameActions] Requesting lineup for team:', teamId)
|
|
|
|
socket.value!.emit('get_lineup', {
|
|
game_id: currentGameId.value!,
|
|
team_id: teamId,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Get box score
|
|
*/
|
|
function getBoxScore() {
|
|
if (!validateConnection()) return
|
|
|
|
console.log('[GameActions] Requesting box score')
|
|
|
|
socket.value!.emit('get_box_score', {
|
|
game_id: currentGameId.value!,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Request full game state (for reconnection)
|
|
*/
|
|
function requestGameState() {
|
|
if (!validateConnection()) return
|
|
|
|
console.log('[GameActions] Requesting full game state')
|
|
|
|
socket.value!.emit('request_game_state', {
|
|
game_id: currentGameId.value!,
|
|
})
|
|
|
|
uiStore.showInfo('Syncing game state...', 3000)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Return API
|
|
// ============================================================================
|
|
|
|
return {
|
|
// Connection
|
|
joinGame,
|
|
leaveGame,
|
|
|
|
// Strategic decisions
|
|
submitDefensiveDecision,
|
|
submitOffensiveDecision,
|
|
|
|
// Manual workflow
|
|
rollDice,
|
|
submitManualOutcome,
|
|
|
|
// Substitutions
|
|
requestPinchHitter,
|
|
requestDefensiveReplacement,
|
|
requestPitchingChange,
|
|
|
|
// Data requests
|
|
getLineup,
|
|
getBoxScore,
|
|
requestGameState,
|
|
}
|
|
}
|