feat(game): 实现出牌选择与计时功能

- 添加 PlayerTurnPayload 接口定义和 PLAYER_TURN 动作类型
- 实现选牌、出牌确认逻辑和相关状态管理
- 添加客户端出牌限制检查和错误提示
- 集成 PLAYER_TURN WebSocket 消息处理
- 添加房间状态面板显示游戏信息
- 优化桌面背景图片和样式布局
- 添加马蹄形动画样式文件
- 配置 Vite 别名和端口设置
This commit is contained in:
2026-03-30 17:23:43 +08:00
parent 43439cb09d
commit 2625baf266
12 changed files with 645 additions and 344 deletions

View File

@@ -4,7 +4,8 @@ import {
type GameState,
type PendingClaimState,
} from '../types/state'
import type { RoomPlayerUpdatePayload, RoomTrusteePayload } from '../game/actions'
import type { PlayerTurnPayload, RoomPlayerUpdatePayload, RoomTrusteePayload } from '../game/actions'
import { readStoredAuth } from '../utils/auth-storage'
import type { Tile } from '../types/tile'
@@ -16,6 +17,7 @@ export const useGameStore = defineStore('game', {
dealerIndex: 0,
currentTurn: 0,
currentPlayerId: '',
needDraw: false,
players: {},
@@ -34,7 +36,7 @@ export const useGameStore = defineStore('game', {
this.$reset()
},
// 初始
// 初始<EFBFBD>?
initGame(data: GameState) {
Object.assign(this, data)
},
@@ -53,8 +55,9 @@ export const useGameStore = defineStore('game', {
// 剩余牌数减少
this.remainingTiles = Math.max(0, this.remainingTiles - 1)
// 更新回合seatIndex
// 更新回合seatIndex<EFBFBD>?
this.currentTurn = player.seatIndex
this.currentPlayerId = player.playerId
// 清除操作窗口
this.pendingClaim = undefined
@@ -84,7 +87,7 @@ export const useGameStore = defineStore('game', {
}
player.handCount = Math.max(0, player.handCount - 1)
// 加入出牌
// 加入出牌<EFBFBD>?
player.discardTiles.push(data.tile)
// 更新回合
@@ -95,7 +98,7 @@ export const useGameStore = defineStore('game', {
this.phase = GAME_PHASE.ACTION
},
// 触发操作窗口(碰/杠/胡)
// 触发操作窗口(碰/<EFBFBD>?胡)
onPendingClaim(data: PendingClaimState) {
this.pendingClaim = data
this.needDraw = false
@@ -220,14 +223,49 @@ export const useGameStore = defineStore('game', {
},
// 清理操作窗口
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
// 获取当前玩家ID后续建议放<EFBFBD>?userStore<EFBFBD>?
getMyPlayerId(): string {
return Object.keys(this.players)[0] || ''
const auth = readStoredAuth()
const source = auth?.user as Record<string, unknown> | 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 ''
},
},
})