feat(game): 添加成都麻将游戏页面和大厅功能
- 实现 ChengduGamePage.vue 组件,包含完整的麻将游戏界面 - 实现 HallPage.vue 组件,支持房间列表展示、创建和加入功能 - 添加 mahjong API 接口用于房间管理操作 - 集成 store 状态管理和本地存储功能 - 实现 ChengduBottomActions 等游戏控制组件 - 添加 websocket 连接和游戏会话管理逻辑 - 实现游戏倒计时、结算等功能模块
This commit is contained in:
109
src/views/chengdu/socket/parsers/roomStateSnapshot.ts
Normal file
109
src/views/chengdu/socket/parsers/roomStateSnapshot.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
asRecord,
|
||||
normalizeMelds,
|
||||
normalizePendingClaim,
|
||||
normalizeTiles,
|
||||
readBoolean,
|
||||
readMissingSuitWithPresence,
|
||||
readNumber,
|
||||
readString,
|
||||
readStringArray,
|
||||
} from '../../../../game/chengdu/messageNormalizers'
|
||||
import type { PendingClaimState, PlayerState } from '../../../../types/state'
|
||||
|
||||
export interface ParsedRoomStateSnapshot {
|
||||
roomId: string
|
||||
nextPlayers: Record<string, PlayerState>
|
||||
phase: string
|
||||
wallCount: number | null
|
||||
currentTurnPlayerId: string
|
||||
currentTurn: number | null
|
||||
needDraw: boolean
|
||||
pendingClaim?: PendingClaimState
|
||||
scores?: Record<string, number>
|
||||
winners: string[]
|
||||
currentRound: number | null
|
||||
totalRounds: number | null
|
||||
settlementDeadlineMs: number | null
|
||||
}
|
||||
|
||||
interface ParseRoomStateSnapshotOptions {
|
||||
payload: Record<string, unknown>
|
||||
roomId: string
|
||||
loggedInUserId: string
|
||||
previousPlayers: Record<string, PlayerState>
|
||||
}
|
||||
|
||||
export function parseRoomStateSnapshot(
|
||||
options: ParseRoomStateSnapshotOptions,
|
||||
): ParsedRoomStateSnapshot {
|
||||
const { payload, previousPlayers } = options
|
||||
const nextPlayers: Record<string, PlayerState> = {}
|
||||
const gamePlayers = Array.isArray(payload.players) ? payload.players : []
|
||||
|
||||
gamePlayers.forEach((item, fallbackIndex) => {
|
||||
const player = asRecord(item)
|
||||
if (!player) {
|
||||
return
|
||||
}
|
||||
|
||||
const playerId = readString(player, 'player_id', 'PlayerID')
|
||||
if (!playerId) {
|
||||
return
|
||||
}
|
||||
|
||||
const previous = previousPlayers[playerId]
|
||||
const seatIndex = previous?.seatIndex ?? fallbackIndex
|
||||
const handCount = readNumber(player, 'hand_count', 'handCount') ?? previous?.handCount ?? 0
|
||||
const outTiles = normalizeTiles(player.out_tiles ?? player.outTiles)
|
||||
const melds = normalizeMelds(player.melds ?? player.exposed_melds ?? player.exposedMelds ?? player.claims)
|
||||
const hasHu = Boolean(player.has_hu ?? player.hasHu)
|
||||
const dingQue = readMissingSuitWithPresence(player)
|
||||
const scores = asRecord(payload.scores)
|
||||
const score = scores?.[playerId]
|
||||
|
||||
nextPlayers[playerId] = {
|
||||
playerId,
|
||||
seatIndex,
|
||||
displayName: previous?.displayName ?? playerId,
|
||||
avatarURL: previous?.avatarURL,
|
||||
isTrustee: previous?.isTrustee ?? false,
|
||||
missingSuit: dingQue.present ? dingQue.value : (previous?.missingSuit ?? null),
|
||||
handTiles: previous?.handTiles ?? [],
|
||||
handCount,
|
||||
melds,
|
||||
discardTiles: outTiles,
|
||||
hasHu,
|
||||
score: typeof score === 'number' ? score : previous?.score ?? 0,
|
||||
isReady: previous?.isReady ?? false,
|
||||
}
|
||||
})
|
||||
|
||||
const phase = readString(payload, 'phase') || readString(payload, 'status') || 'waiting'
|
||||
const wallCount = readNumber(payload, 'wall_count', 'wallCount')
|
||||
const currentTurnSeat = readNumber(payload, 'current_turn', 'currentTurn')
|
||||
const currentTurnPlayerId = readString(payload, 'current_turn_player', 'currentTurnPlayer') || ''
|
||||
const currentTurn =
|
||||
currentTurnSeat ??
|
||||
(currentTurnPlayerId && nextPlayers[currentTurnPlayerId] ? nextPlayers[currentTurnPlayerId].seatIndex : null)
|
||||
|
||||
return {
|
||||
roomId: options.roomId,
|
||||
nextPlayers,
|
||||
phase,
|
||||
wallCount,
|
||||
currentTurnPlayerId,
|
||||
currentTurn,
|
||||
needDraw: readBoolean(payload, 'need_draw', 'needDraw') ?? false,
|
||||
pendingClaim: normalizePendingClaim(payload, options.loggedInUserId),
|
||||
scores: asRecord(payload.scores)
|
||||
? (Object.fromEntries(
|
||||
Object.entries(asRecord(payload.scores) ?? {}).filter(([, value]) => typeof value === 'number'),
|
||||
) as Record<string, number>)
|
||||
: undefined,
|
||||
winners: readStringArray(payload, 'winners'),
|
||||
currentRound: readNumber(payload, 'current_round', 'currentRound'),
|
||||
totalRounds: readNumber(payload, 'total_rounds', 'totalRounds'),
|
||||
settlementDeadlineMs: readNumber(payload, 'settlement_deadline_ms', 'settlementDeadlineMs'),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user