/** * 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!, alignment: decision.alignment, 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, } }