- 移除硬编码的花色图标导入,改用动态加载方式 - 添加新的 flowerColorMap 配置文件统一管理缺门图标 - 引入 clearActiveRoom 函数用于清理活动房间状态 - 在游戏数据解析中添加缺失花色的读取函数 - 当房间数据为空时自动清理房间状态并跳转回大厅 - 统一玩家缺门花色数据处理逻辑 - 注释掉浮动状态显示区域以优化界面布局 - 调整CSS样式中缺门标记尺寸和旋转效果 - 在游戏存储模块中添加清除快照功能 - 重构座位玩家卡片组件中的花色图标计算逻辑 - 优化花色标签映射和归一化处理函数
211 lines
7.1 KiB
TypeScript
211 lines
7.1 KiB
TypeScript
import { defineStore } from 'pinia'
|
||
import {
|
||
GAME_PHASE,
|
||
type GameState,
|
||
type PendingClaimState,
|
||
} from '../types/state'
|
||
import type { RoomPlayerUpdatePayload } from '../game/actions'
|
||
|
||
import type { Tile } from '../types/tile'
|
||
|
||
export const useGameStore = defineStore('game', {
|
||
state: (): GameState => ({
|
||
roomId: '',
|
||
|
||
phase: GAME_PHASE.WAITING,
|
||
|
||
dealerIndex: 0,
|
||
currentTurn: 0,
|
||
|
||
players: {},
|
||
|
||
remainingTiles: 0,
|
||
|
||
pendingClaim: undefined,
|
||
|
||
winners: [],
|
||
|
||
scores: {},
|
||
}),
|
||
|
||
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.pendingClaim = undefined
|
||
|
||
// 进入出牌阶段
|
||
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.phase = GAME_PHASE.ACTION
|
||
},
|
||
|
||
// 触发操作窗口(碰/杠/胡)
|
||
onPendingClaim(data: PendingClaimState) {
|
||
this.pendingClaim = data
|
||
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<string, GameState['phase']> = {
|
||
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
|
||
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,
|
||
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:
|
||
typeof readyRaw === 'boolean'
|
||
? readyRaw
|
||
: (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,
|
||
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
|
||
},
|
||
|
||
// 清理操作窗口
|
||
clearPendingClaim() {
|
||
this.pendingClaim = undefined
|
||
this.phase = GAME_PHASE.PLAYING
|
||
},
|
||
|
||
// 获取当前玩家ID(后续建议放到 userStore)
|
||
getMyPlayerId(): string {
|
||
return Object.keys(this.players)[0] || ''
|
||
},
|
||
},
|
||
})
|