strat-gameplay-webapp/frontend-sba/composables/useGameActions.ts
Cal Corum 4e7ea9e514 CLAUDE: Remove alignment field from frontend - complete Session 1 cleanup
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>
2025-11-14 15:34:59 -06:00

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,
}
}