- 实现 ChengduGamePage.vue 组件,包含完整的麻将游戏界面 - 实现 HallPage.vue 组件,支持房间列表展示、创建和加入功能 - 添加 mahjong API 接口用于房间管理操作 - 集成 store 状态管理和本地存储功能 - 实现 ChengduBottomActions 等游戏控制组件 - 添加 websocket 连接和游戏会话管理逻辑 - 实现游戏倒计时、结算等功能模块
171 lines
5.1 KiB
TypeScript
171 lines
5.1 KiB
TypeScript
import {
|
|
asRecord,
|
|
normalizeTiles,
|
|
normalizeWsType,
|
|
readBoolean,
|
|
readString,
|
|
} from '../../../../game/chengdu/messageNormalizers'
|
|
import type { RoomPlayerUpdatePayload, RoomTrusteePayload } from '../../../../game/actions'
|
|
import {
|
|
clearDingQuePending,
|
|
clearReadyTogglePending,
|
|
clearStartGamePending,
|
|
clearTurnPending,
|
|
completeDiscard,
|
|
setTrustMode,
|
|
syncCurrentUserId,
|
|
} from '../session/sessionStateAdapter'
|
|
import { setPlayerHandState, setPlayerMissingSuit, setPlayerReadyState, setPlayerTrusteeState } from '../store/gameStoreAdapter'
|
|
import type { PlayerHandlerApi, SocketHandlerContext } from '../types'
|
|
|
|
export function createPlayerHandlers(context: SocketHandlerContext): PlayerHandlerApi {
|
|
function applyPlayerReadyState(playerId: string, ready: boolean): void {
|
|
setPlayerReadyState(context.gameStore, playerId, ready)
|
|
}
|
|
|
|
function syncReadyStatesFromRoomUpdate(payload: RoomPlayerUpdatePayload): void {
|
|
if (!Array.isArray(payload.players)) {
|
|
return
|
|
}
|
|
|
|
for (const item of payload.players) {
|
|
const playerId =
|
|
(typeof item.PlayerID === 'string' && item.PlayerID) ||
|
|
(typeof item.player_id === 'string' && item.player_id) ||
|
|
''
|
|
const ready =
|
|
typeof item.Ready === 'boolean'
|
|
? item.Ready
|
|
: typeof item.ready === 'boolean'
|
|
? item.ready
|
|
: typeof item.is_ready === 'boolean'
|
|
? item.is_ready
|
|
: undefined
|
|
|
|
if (!playerId || typeof ready !== 'boolean') {
|
|
continue
|
|
}
|
|
|
|
applyPlayerReadyState(playerId, ready)
|
|
}
|
|
}
|
|
|
|
function handlePlayerHandResponse(message: unknown): void {
|
|
const source = asRecord(message)
|
|
if (!source || typeof source.type !== 'string' || normalizeWsType(source.type) !== 'PLAYER_HAND') {
|
|
return
|
|
}
|
|
|
|
const payload = asRecord(source.payload)
|
|
if (!payload) {
|
|
return
|
|
}
|
|
|
|
syncCurrentUserId(context.session, readString(source, 'target'))
|
|
const roomId = readString(payload, 'room_id', 'roomId') || readString(source, 'roomId')
|
|
if (roomId && context.gameStore.roomId && roomId !== context.gameStore.roomId) {
|
|
return
|
|
}
|
|
|
|
const handTiles = normalizeTiles(payload.hand)
|
|
if (!context.session.loggedInUserId.value || handTiles.length === 0) {
|
|
return
|
|
}
|
|
|
|
clearTurnPending(context.session)
|
|
setPlayerHandState(context.gameStore, context.session.loggedInUserId.value, handTiles)
|
|
clearDingQuePending(context.session)
|
|
|
|
completeDiscard(context.session)
|
|
if (context.gameStore.phase !== 'waiting') {
|
|
clearStartGamePending(context.session)
|
|
}
|
|
}
|
|
|
|
function syncTrusteeState(payload: RoomTrusteePayload): void {
|
|
const playerId =
|
|
(typeof payload.player_id === 'string' && payload.player_id) ||
|
|
(typeof payload.playerId === 'string' && payload.playerId) ||
|
|
''
|
|
if (!playerId) {
|
|
return
|
|
}
|
|
|
|
const trustee = typeof payload.trustee === 'boolean' ? payload.trustee : true
|
|
setPlayerTrusteeState(context.gameStore, playerId, trustee)
|
|
if (playerId === context.session.loggedInUserId.value) {
|
|
setTrustMode(context.session, trustee)
|
|
}
|
|
|
|
}
|
|
|
|
function handleReadyStateResponse(message: unknown): void {
|
|
const source = asRecord(message)
|
|
if (!source || typeof source.type !== 'string' || normalizeWsType(source.type) !== 'PLAYER_READY') {
|
|
return
|
|
}
|
|
|
|
const payload = asRecord(source.payload)
|
|
if (!payload) {
|
|
return
|
|
}
|
|
|
|
const roomId = typeof payload.room_id === 'string' ? payload.room_id : readString(source, 'roomId')
|
|
const userId =
|
|
(typeof payload.player_id === 'string' && payload.player_id) ||
|
|
(typeof payload.user_id === 'string' && payload.user_id) ||
|
|
readString(source, 'target')
|
|
const ready = readBoolean(payload, 'is_ready', 'ready', 'Ready')
|
|
|
|
if (roomId && roomId !== context.gameStore.roomId) {
|
|
return
|
|
}
|
|
|
|
if (ready !== null && userId) {
|
|
applyPlayerReadyState(userId, ready)
|
|
}
|
|
if (userId && userId === context.session.loggedInUserId.value) {
|
|
clearReadyTogglePending(context.session)
|
|
}
|
|
}
|
|
|
|
function handlePlayerDingQueResponse(message: unknown): void {
|
|
const source = asRecord(message)
|
|
if (!source || typeof source.type !== 'string' || normalizeWsType(source.type) !== 'PLAYER_DING_QUE') {
|
|
return
|
|
}
|
|
|
|
const payload = asRecord(source.payload)
|
|
if (!payload) {
|
|
return
|
|
}
|
|
|
|
const roomId = readString(payload, 'room_id', 'roomId') || readString(source, 'roomId')
|
|
if (roomId && roomId !== context.gameStore.roomId) {
|
|
return
|
|
}
|
|
|
|
const userId =
|
|
readString(payload, 'user_id', 'userId', 'player_id', 'playerId') || readString(source, 'target')
|
|
const suit = readString(payload, 'suit', 'Suit')
|
|
if (!userId || !suit) {
|
|
return
|
|
}
|
|
|
|
setPlayerMissingSuit(context.gameStore, userId, suit)
|
|
|
|
if (userId === context.session.loggedInUserId.value) {
|
|
clearDingQuePending(context.session)
|
|
}
|
|
}
|
|
|
|
return {
|
|
applyPlayerReadyState,
|
|
syncReadyStatesFromRoomUpdate,
|
|
handlePlayerHandResponse,
|
|
handleReadyStateResponse,
|
|
handlePlayerDingQueResponse,
|
|
syncTrusteeState,
|
|
}
|
|
}
|