import { defineStore } from 'pinia' import { GAME_PHASE, type GameState, type PendingClaimState, } from '../types/state' import type { PlayerTurnPayload, RoomPlayerUpdatePayload, RoomTrusteePayload } from '../game/actions' import { readStoredAuth } from '../utils/auth-storage' import type { Tile } from '../types/tile' function parseBooleanish(value: unknown): boolean | null { if (typeof value === 'boolean') { return value } if (typeof value === 'number') { if (value === 1) { return true } if (value === 0) { return false } } if (typeof value === 'string') { const normalized = value.trim().toLowerCase() if (normalized === 'true' || normalized === '1') { return true } if (normalized === 'false' || normalized === '0') { return false } } return null } export const useGameStore = defineStore('game', { state: (): GameState => ({ roomId: '', phase: GAME_PHASE.WAITING, dealerIndex: 0, currentTurn: 0, currentPlayerId: '', needDraw: false, players: {}, remainingTiles: 0, pendingClaim: undefined, winners: [], scores: {}, currentRound: 0, totalRounds: 0, }), actions: { resetGame() { this.$reset() }, // 初始�? initGame(data: GameState) { Object.assign(this, data) }, // 摸牌 onDrawTile(data: { playerId: string; tile: Tile }) { const player = this.players[data.playerId] if (!player) return // 只更新自己的手牌 if (player.playerId === this.getMyPlayerId()) { player.handTiles.push(data.tile) } player.handCount += 1 // 剩余牌数减少 this.remainingTiles = Math.max(0, this.remainingTiles - 1) // 更新回合(seatIndex�? this.currentTurn = player.seatIndex this.currentPlayerId = player.playerId // 清除操作窗口 this.pendingClaim = undefined this.needDraw = false // 进入出牌阶段 this.phase = GAME_PHASE.PLAYING }, // 出牌 onPlayTile(data: { playerId: string tile: Tile nextSeat: number }) { const player = this.players[data.playerId] if (!player) return // 如果是自己,移除手牌 if (player.playerId === this.getMyPlayerId()) { const index = player.handTiles.findIndex( (t) => t.id === data.tile.id ) if (index !== -1) { player.handTiles.splice(index, 1) } } player.handCount = Math.max(0, player.handCount - 1) // 加入出牌�? player.discardTiles.push(data.tile) // 更新回合 this.currentTurn = data.nextSeat this.needDraw = true // 等待其他玩家响应 this.phase = GAME_PHASE.ACTION }, // 触发操作窗口(碰/�?胡) onPendingClaim(data: PendingClaimState) { this.pendingClaim = data this.needDraw = false this.phase = GAME_PHASE.ACTION }, onRoomPlayerUpdate(payload: RoomPlayerUpdatePayload) { if (typeof payload.room_id === 'string' && payload.room_id) { this.roomId = payload.room_id } if (typeof payload.status === 'string' && payload.status) { const phaseMap: Record = { waiting: GAME_PHASE.WAITING, dealing: GAME_PHASE.DEALING, playing: GAME_PHASE.PLAYING, action: GAME_PHASE.ACTION, settlement: GAME_PHASE.SETTLEMENT, } this.phase = phaseMap[payload.status] ?? this.phase } const hasPlayerList = Array.isArray(payload.players) || Array.isArray(payload.player_ids) if (!hasPlayerList) { return } const nextPlayers: GameState['players'] = {} const players = Array.isArray(payload.players) ? payload.players : [] const playerIds = Array.isArray(payload.player_ids) ? payload.player_ids : [] players.forEach((raw, index) => { const playerId = (typeof raw.PlayerID === 'string' && raw.PlayerID) || (typeof raw.player_id === 'string' && raw.player_id) || playerIds[index] if (!playerId) { return } const previous = this.players[playerId] const seatRaw = raw.Index ?? raw.index ?? index const seatIndex = typeof seatRaw === 'number' && Number.isFinite(seatRaw) ? seatRaw : index const readyRaw = raw.Ready ?? raw.ready ?? raw.is_ready const ready = parseBooleanish(readyRaw) const displayNameRaw = raw.PlayerName ?? raw.player_name const avatarUrlRaw = raw.AvatarUrl ?? raw.avatar_url const missingSuitRaw = raw.MissingSuit ?? raw.missing_suit nextPlayers[playerId] = { playerId, seatIndex, displayName: typeof displayNameRaw === 'string' && displayNameRaw ? displayNameRaw : previous?.displayName, avatarURL: typeof avatarUrlRaw === 'string' ? avatarUrlRaw : previous?.avatarURL, isTrustee: previous?.isTrustee ?? false, missingSuit: typeof missingSuitRaw === 'string' || missingSuitRaw === null ? missingSuitRaw : previous?.missingSuit, handTiles: previous?.handTiles ?? [], handCount: previous?.handCount ?? 0, melds: previous?.melds ?? [], discardTiles: previous?.discardTiles ?? [], hasHu: previous?.hasHu ?? false, score: previous?.score ?? 0, isReady: ready !== null ? ready : (previous?.isReady ?? false), } }) if (players.length === 0) { playerIds.forEach((playerId, index) => { if (typeof playerId !== 'string' || !playerId) { return } const previous = this.players[playerId] nextPlayers[playerId] = { playerId, seatIndex: previous?.seatIndex ?? index, displayName: previous?.displayName ?? playerId, avatarURL: previous?.avatarURL, isTrustee: previous?.isTrustee ?? false, missingSuit: previous?.missingSuit, handTiles: previous?.handTiles ?? [], handCount: previous?.handCount ?? 0, melds: previous?.melds ?? [], discardTiles: previous?.discardTiles ?? [], hasHu: previous?.hasHu ?? false, score: previous?.score ?? 0, isReady: previous?.isReady ?? false, } }) } this.players = nextPlayers }, onRoomTrustee(payload: RoomTrusteePayload) { const playerId = (typeof payload.player_id === 'string' && payload.player_id) || (typeof payload.playerId === 'string' && payload.playerId) || '' if (!playerId) { return } const player = this.players[playerId] if (!player) { return } player.isTrustee = typeof payload.trustee === 'boolean' ? payload.trustee : true }, // 清理操作窗口 onPlayerTurn(payload: PlayerTurnPayload) { const playerId = (typeof payload.player_id === 'string' && payload.player_id) || (typeof payload.playerId === 'string' && payload.playerId) || (typeof payload.PlayerID === 'string' && payload.PlayerID) || '' if (!playerId) { return } const player = this.players[playerId] if (player) { this.currentTurn = player.seatIndex } this.currentPlayerId = playerId this.needDraw = false this.pendingClaim = undefined this.phase = GAME_PHASE.PLAYING }, clearPendingClaim() { this.pendingClaim = undefined this.phase = GAME_PHASE.PLAYING }, // 获取当前玩家ID(后续建议放�?userStore�? getMyPlayerId(): string { const auth = readStoredAuth() const source = auth?.user as Record | undefined const rawId = source?.id ?? source?.userID ?? source?.user_id if (typeof rawId === 'string' && rawId.trim()) { return rawId } if (typeof rawId === 'number') { return String(rawId) } return '' }, }, })