Compare commits
2 Commits
0bf68d4e49
...
3c876c4c3d
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c876c4c3d | |||
| cfc65070ea |
@@ -30,6 +30,22 @@ export interface RoomTrusteePayload {
|
|||||||
reason?: string
|
reason?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DiscardActionPayload {
|
||||||
|
player_id?: string
|
||||||
|
playerId?: string
|
||||||
|
PlayerID?: string
|
||||||
|
tile?: Tile
|
||||||
|
next_seat?: number
|
||||||
|
nextSeat?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DrawActionPayload {
|
||||||
|
player_id?: string
|
||||||
|
playerId?: string
|
||||||
|
PlayerID?: string
|
||||||
|
tile?: Tile
|
||||||
|
}
|
||||||
|
|
||||||
export interface PlayerTurnPayload {
|
export interface PlayerTurnPayload {
|
||||||
player_id?: string
|
player_id?: string
|
||||||
playerId?: string
|
playerId?: string
|
||||||
@@ -69,20 +85,13 @@ export type GameAction =
|
|||||||
// 摸牌
|
// 摸牌
|
||||||
| {
|
| {
|
||||||
type: 'DRAW_TILE'
|
type: 'DRAW_TILE'
|
||||||
payload: {
|
payload: DrawActionPayload
|
||||||
playerId: string
|
|
||||||
tile: Tile
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 出牌
|
// 出牌
|
||||||
| {
|
| {
|
||||||
type: 'PLAY_TILE'
|
type: 'PLAY_TILE'
|
||||||
payload: {
|
payload: DiscardActionPayload
|
||||||
playerId: string
|
|
||||||
tile: Tile
|
|
||||||
nextSeat: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 进入操作窗口(碰/杠/胡)
|
// 进入操作窗口(碰/杠/胡)
|
||||||
|
|||||||
@@ -70,13 +70,21 @@ export const useGameStore = defineStore('game', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 摸牌
|
// 摸牌
|
||||||
onDrawTile(data: { playerId: string; tile: Tile }) {
|
onDrawTile(data: { playerId?: string; player_id?: string; PlayerID?: string; tile?: Tile }) {
|
||||||
const player = this.players[data.playerId]
|
const playerId =
|
||||||
|
(typeof data.playerId === 'string' && data.playerId) ||
|
||||||
|
(typeof data.player_id === 'string' && data.player_id) ||
|
||||||
|
(typeof data.PlayerID === 'string' && data.PlayerID) ||
|
||||||
|
''
|
||||||
|
const tile = data.tile
|
||||||
|
if (!playerId || !tile) return
|
||||||
|
|
||||||
|
const player = this.players[playerId]
|
||||||
if (!player) return
|
if (!player) return
|
||||||
|
|
||||||
// 只更新自己的手牌
|
// 只更新自己的手牌
|
||||||
if (player.playerId === this.getMyPlayerId()) {
|
if (player.playerId === this.getMyPlayerId()) {
|
||||||
player.handTiles.push(data.tile)
|
player.handTiles.push(tile)
|
||||||
}
|
}
|
||||||
player.handCount += 1
|
player.handCount += 1
|
||||||
|
|
||||||
@@ -97,17 +105,28 @@ export const useGameStore = defineStore('game', {
|
|||||||
|
|
||||||
// 出牌
|
// 出牌
|
||||||
onPlayTile(data: {
|
onPlayTile(data: {
|
||||||
playerId: string
|
playerId?: string
|
||||||
tile: Tile
|
player_id?: string
|
||||||
nextSeat: number
|
PlayerID?: string
|
||||||
|
tile?: Tile
|
||||||
|
nextSeat?: number
|
||||||
|
next_seat?: number
|
||||||
}) {
|
}) {
|
||||||
const player = this.players[data.playerId]
|
const playerId =
|
||||||
|
(typeof data.playerId === 'string' && data.playerId) ||
|
||||||
|
(typeof data.player_id === 'string' && data.player_id) ||
|
||||||
|
(typeof data.PlayerID === 'string' && data.PlayerID) ||
|
||||||
|
''
|
||||||
|
const tile = data.tile
|
||||||
|
if (!playerId || !tile) return
|
||||||
|
|
||||||
|
const player = this.players[playerId]
|
||||||
if (!player) return
|
if (!player) return
|
||||||
|
|
||||||
// 如果是自己,移除手牌
|
// 如果是自己,移除手牌
|
||||||
if (player.playerId === this.getMyPlayerId()) {
|
if (player.playerId === this.getMyPlayerId()) {
|
||||||
const index = player.handTiles.findIndex(
|
const index = player.handTiles.findIndex(
|
||||||
(t) => t.id === data.tile.id
|
(t) => t.id === tile.id
|
||||||
)
|
)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
player.handTiles.splice(index, 1)
|
player.handTiles.splice(index, 1)
|
||||||
@@ -116,10 +135,16 @@ export const useGameStore = defineStore('game', {
|
|||||||
player.handCount = Math.max(0, player.handCount - 1)
|
player.handCount = Math.max(0, player.handCount - 1)
|
||||||
|
|
||||||
// 加入出牌<E587BA>?
|
// 加入出牌<E587BA>?
|
||||||
player.discardTiles.push(data.tile)
|
player.discardTiles.push(tile)
|
||||||
|
|
||||||
// 更新回合
|
// 更新回合
|
||||||
this.currentTurn = data.nextSeat
|
const nextSeat =
|
||||||
|
typeof data.nextSeat === 'number'
|
||||||
|
? data.nextSeat
|
||||||
|
: typeof data.next_seat === 'number'
|
||||||
|
? data.next_seat
|
||||||
|
: this.currentTurn
|
||||||
|
this.currentTurn = nextSeat
|
||||||
this.needDraw = true
|
this.needDraw = true
|
||||||
|
|
||||||
// 等待其他玩家响应
|
// 等待其他玩家响应
|
||||||
@@ -297,4 +322,3 @@ export const useGameStore = defineStore('game', {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,20 +34,55 @@ const session = useChengduGameSession({
|
|||||||
gameStore,
|
gameStore,
|
||||||
roomMeta,
|
roomMeta,
|
||||||
})
|
})
|
||||||
|
const {
|
||||||
|
now,
|
||||||
|
wsStatus,
|
||||||
|
wsError,
|
||||||
|
roomCountdown,
|
||||||
|
leaveRoomPending,
|
||||||
|
readyTogglePending,
|
||||||
|
dingQuePending,
|
||||||
|
discardPending,
|
||||||
|
claimActionPending,
|
||||||
|
turnActionPending,
|
||||||
|
nextRoundPending,
|
||||||
|
selectedDiscardTileId,
|
||||||
|
menuOpen,
|
||||||
|
isTrustMode,
|
||||||
|
menuTriggerActive,
|
||||||
|
loggedInUserId,
|
||||||
|
networkLabel,
|
||||||
|
formattedClock,
|
||||||
|
toggleMenu,
|
||||||
|
toggleTrustMode,
|
||||||
|
backHall,
|
||||||
|
} = session
|
||||||
|
|
||||||
const routeRoomName = computed(() => (typeof route.query.roomName === 'string' ? route.query.roomName : ''))
|
const routeRoomName = computed(() => (typeof route.query.roomName === 'string' ? route.query.roomName : ''))
|
||||||
const myPlayer = computed(() => gameStore.players[session.loggedInUserId.value] as DisplayPlayer | undefined)
|
const myPlayer = computed(() => gameStore.players[loggedInUserId.value] as DisplayPlayer | undefined)
|
||||||
const myHandTiles = computed(() => myPlayer.value?.handTiles ?? [])
|
const myHandTiles = computed(() => myPlayer.value?.handTiles ?? [])
|
||||||
const gamePlayers = computed<DisplayPlayer[]>(() =>
|
const gamePlayers = computed<DisplayPlayer[]>(() =>
|
||||||
Object.values(gameStore.players).sort((a, b) => a.seatIndex - b.seatIndex) as DisplayPlayer[],
|
Object.values(gameStore.players).sort((a, b) => a.seatIndex - b.seatIndex) as DisplayPlayer[],
|
||||||
)
|
)
|
||||||
|
|
||||||
const tableView = useChengduTableView({
|
const {
|
||||||
|
roomName,
|
||||||
|
roomState,
|
||||||
|
seatWinds,
|
||||||
|
currentTurnSeat,
|
||||||
|
currentPhaseText,
|
||||||
|
roomStatusText,
|
||||||
|
roundText,
|
||||||
|
wallSeats,
|
||||||
|
deskSeats,
|
||||||
|
seatDecor,
|
||||||
|
settlementPlayers,
|
||||||
|
} = useChengduTableView({
|
||||||
roomMeta,
|
roomMeta,
|
||||||
gamePlayers,
|
gamePlayers,
|
||||||
gameStore,
|
gameStore,
|
||||||
localCachedAvatarUrl: session.localCachedAvatarUrl,
|
localCachedAvatarUrl: session.localCachedAvatarUrl,
|
||||||
loggedInUserId: session.loggedInUserId,
|
loggedInUserId,
|
||||||
loggedInUserName: session.loggedInUserName,
|
loggedInUserName: session.loggedInUserName,
|
||||||
myHandTiles,
|
myHandTiles,
|
||||||
myPlayer,
|
myPlayer,
|
||||||
@@ -59,13 +94,43 @@ const socket = useChengduGameSocket({
|
|||||||
router,
|
router,
|
||||||
gameStore,
|
gameStore,
|
||||||
roomMeta,
|
roomMeta,
|
||||||
roomName: tableView.roomName,
|
roomName,
|
||||||
myHandTiles,
|
myHandTiles,
|
||||||
myPlayer,
|
myPlayer,
|
||||||
session,
|
session,
|
||||||
})
|
})
|
||||||
|
const { showSettlementOverlay, settlementCountdown } = socket
|
||||||
|
|
||||||
const actions = useChengduGameActions({
|
const {
|
||||||
|
isLastRound,
|
||||||
|
myReadyState,
|
||||||
|
isRoomOwner,
|
||||||
|
showStartGameButton,
|
||||||
|
showWaitingOwnerTip,
|
||||||
|
canStartGame,
|
||||||
|
showReadyToggle,
|
||||||
|
showDingQueChooser,
|
||||||
|
selectedDiscardTile,
|
||||||
|
discardBlockedReason,
|
||||||
|
discardTileBlockedReason,
|
||||||
|
canConfirmDiscard,
|
||||||
|
confirmDiscardLabel,
|
||||||
|
canDrawTile,
|
||||||
|
visibleClaimOptions,
|
||||||
|
showClaimActions,
|
||||||
|
canSelfHu,
|
||||||
|
canSelfGang,
|
||||||
|
toggleReadyState,
|
||||||
|
startGame,
|
||||||
|
nextRound,
|
||||||
|
chooseDingQue,
|
||||||
|
selectDiscardTile,
|
||||||
|
confirmDiscard,
|
||||||
|
drawTile,
|
||||||
|
submitSelfGang,
|
||||||
|
submitSelfHu,
|
||||||
|
submitClaim,
|
||||||
|
} = useChengduGameActions({
|
||||||
gameStore,
|
gameStore,
|
||||||
roomMeta,
|
roomMeta,
|
||||||
gamePlayers,
|
gamePlayers,
|
||||||
@@ -74,7 +139,7 @@ const actions = useChengduGameActions({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const actionCountdown = computed<ActionCountdownView | null>(() => {
|
const actionCountdown = computed<ActionCountdownView | null>(() => {
|
||||||
const countdown = session.roomCountdown.value
|
const countdown = roomCountdown.value
|
||||||
if (!countdown) {
|
if (!countdown) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -82,7 +147,7 @@ const actionCountdown = computed<ActionCountdownView | null>(() => {
|
|||||||
const deadlineAt = countdown.actionDeadlineAt ? Date.parse(countdown.actionDeadlineAt) : Number.NaN
|
const deadlineAt = countdown.actionDeadlineAt ? Date.parse(countdown.actionDeadlineAt) : Number.NaN
|
||||||
const fallbackRemaining = countdown.remaining > 0 ? countdown.remaining : countdown.countdownSeconds
|
const fallbackRemaining = countdown.remaining > 0 ? countdown.remaining : countdown.countdownSeconds
|
||||||
const derivedRemaining = Number.isFinite(deadlineAt)
|
const derivedRemaining = Number.isFinite(deadlineAt)
|
||||||
? Math.ceil((deadlineAt - session.now.value) / 1000)
|
? Math.ceil((deadlineAt - now.value) / 1000)
|
||||||
: fallbackRemaining
|
: fallbackRemaining
|
||||||
const remaining = Math.max(0, derivedRemaining)
|
const remaining = Math.max(0, derivedRemaining)
|
||||||
|
|
||||||
@@ -97,7 +162,7 @@ const actionCountdown = computed<ActionCountdownView | null>(() => {
|
|||||||
|
|
||||||
const playerLabel = targetPlayerIds
|
const playerLabel = targetPlayerIds
|
||||||
.map((playerId) => {
|
.map((playerId) => {
|
||||||
if (playerId === session.loggedInUserId.value) {
|
if (playerId === loggedInUserId.value) {
|
||||||
return '你'
|
return '你'
|
||||||
}
|
}
|
||||||
const targetPlayer = gameStore.players[playerId]
|
const targetPlayer = gameStore.players[playerId]
|
||||||
@@ -111,7 +176,7 @@ const actionCountdown = computed<ActionCountdownView | null>(() => {
|
|||||||
})
|
})
|
||||||
.join('、')
|
.join('、')
|
||||||
const duration = countdown.duration > 0 ? countdown.duration : Math.max(remaining, fallbackRemaining, 1)
|
const duration = countdown.duration > 0 ? countdown.duration : Math.max(remaining, fallbackRemaining, 1)
|
||||||
const includesSelf = targetPlayerIds.includes(session.loggedInUserId.value)
|
const includesSelf = targetPlayerIds.includes(loggedInUserId.value)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
playerLabel,
|
playerLabel,
|
||||||
@@ -123,8 +188,8 @@ const actionCountdown = computed<ActionCountdownView | null>(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function handleLeaveRoom(): void {
|
function handleLeaveRoom(): void {
|
||||||
session.menuOpen.value = false
|
menuOpen.value = false
|
||||||
session.backHall()
|
backHall()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -140,24 +205,24 @@ function handleLeaveRoom(): void {
|
|||||||
<div class="inner-outline mid"></div>
|
<div class="inner-outline mid"></div>
|
||||||
|
|
||||||
<ChengduTableHeader
|
<ChengduTableHeader
|
||||||
:leave-room-pending="session.leaveRoomPending"
|
:leave-room-pending="leaveRoomPending"
|
||||||
:menu-open="session.menuOpen"
|
:menu-open="menuOpen"
|
||||||
:menu-trigger-active="session.menuTriggerActive"
|
:menu-trigger-active="menuTriggerActive"
|
||||||
:is-trust-mode="session.isTrustMode"
|
:is-trust-mode="isTrustMode"
|
||||||
:wall-count="tableView.roomState.game.state.wall.length || 48"
|
:wall-count="roomState.game.state.wall.length || 48"
|
||||||
:network-label="session.networkLabel"
|
:network-label="networkLabel"
|
||||||
:ws-status="session.wsStatus"
|
:ws-status="wsStatus"
|
||||||
:formatted-clock="session.formattedClock"
|
:formatted-clock="formattedClock"
|
||||||
:room-name="tableView.roomState.name || tableView.roomName"
|
:room-name="roomState.name || roomName"
|
||||||
:current-phase-text="tableView.currentPhaseText"
|
:current-phase-text="currentPhaseText"
|
||||||
:player-count="tableView.roomState.playerCount"
|
:player-count="roomState.playerCount"
|
||||||
:max-players="tableView.roomState.maxPlayers"
|
:max-players="roomState.maxPlayers"
|
||||||
:round-text="tableView.roundText"
|
:round-text="roundText"
|
||||||
:room-status-text="tableView.roomStatusText"
|
:room-status-text="roomStatusText"
|
||||||
:ws-error="session.wsError"
|
:ws-error="wsError"
|
||||||
:action-countdown="actionCountdown"
|
:action-countdown="actionCountdown"
|
||||||
@toggle-menu="session.toggleMenu"
|
@toggle-menu="toggleMenu"
|
||||||
@toggle-trust-mode="session.toggleTrustMode"
|
@toggle-trust-mode="toggleTrustMode"
|
||||||
@leave-room="handleLeaveRoom"
|
@leave-room="handleLeaveRoom"
|
||||||
>
|
>
|
||||||
<template #robot-icon>
|
<template #robot-icon>
|
||||||
@@ -172,67 +237,67 @@ function handleLeaveRoom(): void {
|
|||||||
</template>
|
</template>
|
||||||
</ChengduTableHeader>
|
</ChengduTableHeader>
|
||||||
|
|
||||||
<TopPlayerCard :player="tableView.seatDecor.top"/>
|
<TopPlayerCard :player="seatDecor.top"/>
|
||||||
<RightPlayerCard :player="tableView.seatDecor.right"/>
|
<RightPlayerCard :player="seatDecor.right"/>
|
||||||
<BottomPlayerCard :player="tableView.seatDecor.bottom"/>
|
<BottomPlayerCard :player="seatDecor.bottom"/>
|
||||||
<LeftPlayerCard :player="tableView.seatDecor.left"/>
|
<LeftPlayerCard :player="seatDecor.left"/>
|
||||||
|
|
||||||
<ChengduDeskZones :desk-seats="tableView.deskSeats"/>
|
<ChengduDeskZones :desk-seats="deskSeats"/>
|
||||||
|
|
||||||
<ChengduWallSeats
|
<ChengduWallSeats
|
||||||
:wall-seats="tableView.wallSeats"
|
:wall-seats="wallSeats"
|
||||||
:selected-discard-tile-id="session.selectedDiscardTileId"
|
:selected-discard-tile-id="selectedDiscardTileId"
|
||||||
:discard-blocked-reason="discardBlockedReason"
|
:discard-blocked-reason="discardBlockedReason"
|
||||||
:discard-tile-blocked-reason="discardTileBlockedReason"
|
:discard-tile-blocked-reason="discardTileBlockedReason"
|
||||||
:format-tile="formatTile"
|
:format-tile="formatTile"
|
||||||
@select-discard-tile="selectDiscardTile"
|
@select-discard-tile="selectDiscardTile"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WindSquare class="center-wind-square" :seat-winds="tableView.seatWinds"
|
<WindSquare class="center-wind-square" :seat-winds="seatWinds"
|
||||||
:active-position="tableView.currentTurnSeat"/>
|
:active-position="currentTurnSeat"/>
|
||||||
|
|
||||||
<ChengduSettlementOverlay
|
<ChengduSettlementOverlay
|
||||||
:show="socket.showSettlementOverlay"
|
:show="showSettlementOverlay"
|
||||||
:is-last-round="actions.isLastRound"
|
:is-last-round="isLastRound"
|
||||||
:current-round="gameStore.currentRound"
|
:current-round="gameStore.currentRound"
|
||||||
:total-rounds="gameStore.totalRounds"
|
:total-rounds="gameStore.totalRounds"
|
||||||
:settlement-players="tableView.settlementPlayers"
|
:settlement-players="settlementPlayers"
|
||||||
:logged-in-user-id="session.loggedInUserId"
|
:logged-in-user-id="loggedInUserId"
|
||||||
:next-round-pending="session.nextRoundPending"
|
:next-round-pending="nextRoundPending"
|
||||||
:settlement-countdown="socket.settlementCountdown"
|
:settlement-countdown="settlementCountdown"
|
||||||
@next-round="actions.nextRound"
|
@next-round="nextRound"
|
||||||
@back-hall="session.backHall"
|
@back-hall="backHall"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ChengduBottomActions
|
<ChengduBottomActions
|
||||||
:show-ding-que-chooser="actions.showDingQueChooser"
|
:show-ding-que-chooser="showDingQueChooser"
|
||||||
:show-ready-toggle="actions.showReadyToggle"
|
:show-ready-toggle="showReadyToggle"
|
||||||
:show-start-game-button="actions.showStartGameButton"
|
:show-start-game-button="showStartGameButton"
|
||||||
:selected-discard-tile="actions.selectedDiscardTile"
|
:selected-discard-tile="selectedDiscardTile"
|
||||||
:ding-que-pending="session.dingQuePending"
|
:ding-que-pending="dingQuePending"
|
||||||
:can-confirm-discard="actions.canConfirmDiscard"
|
:can-confirm-discard="canConfirmDiscard"
|
||||||
:discard-pending="session.discardPending"
|
:discard-pending="discardPending"
|
||||||
:confirm-discard-label="actions.confirmDiscardLabel"
|
:confirm-discard-label="confirmDiscardLabel"
|
||||||
:ready-toggle-pending="session.readyTogglePending"
|
:ready-toggle-pending="readyTogglePending"
|
||||||
:my-ready-state="actions.myReadyState"
|
:my-ready-state="myReadyState"
|
||||||
:can-draw-tile="actions.canDrawTile"
|
:can-draw-tile="canDrawTile"
|
||||||
:can-start-game="actions.canStartGame"
|
:can-start-game="canStartGame"
|
||||||
:is-room-owner="actions.isRoomOwner"
|
:is-room-owner="isRoomOwner"
|
||||||
:can-self-gang="actions.canSelfGang"
|
:can-self-gang="canSelfGang"
|
||||||
:can-self-hu="actions.canSelfHu"
|
:can-self-hu="canSelfHu"
|
||||||
:show-claim-actions="actions.showClaimActions"
|
:show-claim-actions="showClaimActions"
|
||||||
:turn-action-pending="session.turnActionPending"
|
:turn-action-pending="turnActionPending"
|
||||||
:visible-claim-options="actions.visibleClaimOptions"
|
:visible-claim-options="visibleClaimOptions"
|
||||||
:claim-action-pending="session.claimActionPending"
|
:claim-action-pending="claimActionPending"
|
||||||
:show-waiting-owner-tip="actions.showWaitingOwnerTip"
|
:show-waiting-owner-tip="showWaitingOwnerTip"
|
||||||
@choose-ding-que="actions.chooseDingQue"
|
@choose-ding-que="chooseDingQue"
|
||||||
@confirm-discard="actions.confirmDiscard"
|
@confirm-discard="confirmDiscard"
|
||||||
@toggle-ready-state="actions.toggleReadyState"
|
@toggle-ready-state="toggleReadyState"
|
||||||
@draw-tile="actions.drawTile"
|
@draw-tile="drawTile"
|
||||||
@start-game="actions.startGame"
|
@start-game="startGame"
|
||||||
@submit-self-gang="actions.submitSelfGang"
|
@submit-self-gang="submitSelfGang"
|
||||||
@submit-self-hu="actions.submitSelfHu"
|
@submit-self-hu="submitSelfHu"
|
||||||
@submit-claim="actions.submitClaim"
|
@submit-claim="submitClaim"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import { parseRoomInfoSnapshot } from '../parsers/roomInfoSnapshot'
|
|||||||
import { clearRoomAndRedirect, syncActiveRoomFromRoomInfo } from '../room/roomSnapshotSync'
|
import { clearRoomAndRedirect, syncActiveRoomFromRoomInfo } from '../room/roomSnapshotSync'
|
||||||
import {
|
import {
|
||||||
clearClaimAndTurnPending,
|
clearClaimAndTurnPending,
|
||||||
|
clearRoomCountdown,
|
||||||
clearDingQuePending,
|
clearDingQuePending,
|
||||||
clearSelfTurnAllowActions,
|
clearSelfTurnAllowActions,
|
||||||
clearTurnPending,
|
clearTurnPending,
|
||||||
|
setRoomCountdown,
|
||||||
setSettlementDeadline,
|
setSettlementDeadline,
|
||||||
syncCurrentUserId,
|
syncCurrentUserId,
|
||||||
} from '../session/sessionStateAdapter'
|
} from '../session/sessionStateAdapter'
|
||||||
@@ -69,6 +71,11 @@ export function createRoomInfoHandlers(context: SocketHandlerContext) {
|
|||||||
} else {
|
} else {
|
||||||
clearTurnPending(context.session)
|
clearTurnPending(context.session)
|
||||||
}
|
}
|
||||||
|
if (snapshot.actionTimer) {
|
||||||
|
setRoomCountdown(context.session, snapshot.actionTimer)
|
||||||
|
} else {
|
||||||
|
clearRoomCountdown(context.session)
|
||||||
|
}
|
||||||
if (typeof snapshot.settlementDeadlineMs === 'number' && snapshot.settlementDeadlineMs > 0) {
|
if (typeof snapshot.settlementDeadlineMs === 'number' && snapshot.settlementDeadlineMs > 0) {
|
||||||
setSettlementDeadline(context.session, snapshot.settlementDeadlineMs)
|
setSettlementDeadline(context.session, snapshot.settlementDeadlineMs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import { parseRoomStateSnapshot } from '../parsers/roomStateSnapshot'
|
|||||||
import { syncActiveRoomFromRoomState } from '../room/roomSnapshotSync'
|
import { syncActiveRoomFromRoomState } from '../room/roomSnapshotSync'
|
||||||
import {
|
import {
|
||||||
clearClaimAndTurnPending,
|
clearClaimAndTurnPending,
|
||||||
|
clearRoomCountdown,
|
||||||
clearSelfTurnAllowActions,
|
clearSelfTurnAllowActions,
|
||||||
clearStartGamePending,
|
clearStartGamePending,
|
||||||
clearTurnPending,
|
clearTurnPending,
|
||||||
completeDiscard,
|
completeDiscard,
|
||||||
resetSettlementOverlayState,
|
resetSettlementOverlayState,
|
||||||
|
setRoomCountdown,
|
||||||
setSettlementDeadline,
|
setSettlementDeadline,
|
||||||
} from '../session/sessionStateAdapter'
|
} from '../session/sessionStateAdapter'
|
||||||
import { applyRoomSnapshot } from '../store/gameStoreAdapter'
|
import { applyRoomSnapshot } from '../store/gameStoreAdapter'
|
||||||
@@ -61,6 +63,11 @@ export function createRoomStateHandlers(context: SocketHandlerContext) {
|
|||||||
} else if (snapshot.phase !== 'settlement') {
|
} else if (snapshot.phase !== 'settlement') {
|
||||||
setSettlementDeadline(context.session, null)
|
setSettlementDeadline(context.session, null)
|
||||||
}
|
}
|
||||||
|
if (snapshot.actionTimer) {
|
||||||
|
setRoomCountdown(context.session, snapshot.actionTimer)
|
||||||
|
} else {
|
||||||
|
clearRoomCountdown(context.session)
|
||||||
|
}
|
||||||
|
|
||||||
if (!snapshot.pendingClaim) {
|
if (!snapshot.pendingClaim) {
|
||||||
clearClaimAndTurnPending(context.session)
|
clearClaimAndTurnPending(context.session)
|
||||||
|
|||||||
@@ -3,10 +3,36 @@ import {
|
|||||||
normalizeWsType,
|
normalizeWsType,
|
||||||
readString,
|
readString,
|
||||||
} from '../../../../game/chengdu/messageNormalizers'
|
} from '../../../../game/chengdu/messageNormalizers'
|
||||||
import { clearClaimAndTurnPending, pushWsMessage, setWsError } from '../session/sessionStateAdapter'
|
import { clearClaimAndTurnPending, clearRoomCountdown, pushWsMessage, setWsError } from '../session/sessionStateAdapter'
|
||||||
import type { SocketHandlerContext, StatusHandlerApi } from '../types'
|
import type { SocketHandlerContext, StatusHandlerApi } from '../types'
|
||||||
|
|
||||||
export function createStatusHandlers(context: SocketHandlerContext): StatusHandlerApi {
|
export function createStatusHandlers(context: SocketHandlerContext): StatusHandlerApi {
|
||||||
|
function handleActionAck(message: unknown): void {
|
||||||
|
const source = asRecord(message)
|
||||||
|
if (!source || typeof source.type !== 'string' || normalizeWsType(source.type) !== 'ACTION_ACK') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = asRecord(source.payload)
|
||||||
|
const roomId = readString(payload ?? {}, 'room_id', 'roomId') || readString(source, 'roomId')
|
||||||
|
if (roomId && context.gameStore.roomId && roomId !== context.gameStore.roomId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = normalizeWsType(readString(payload ?? {}, 'action') || readString(source, 'action'))
|
||||||
|
if (
|
||||||
|
action === 'DISCARD' ||
|
||||||
|
action === 'DRAW' ||
|
||||||
|
action === 'PENG' ||
|
||||||
|
action === 'GANG' ||
|
||||||
|
action === 'HU' ||
|
||||||
|
action === 'PASS' ||
|
||||||
|
action === 'DING_QUE'
|
||||||
|
) {
|
||||||
|
clearRoomCountdown(context.session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleActionError(message: unknown): void {
|
function handleActionError(message: unknown): void {
|
||||||
const source = asRecord(message)
|
const source = asRecord(message)
|
||||||
if (!source || typeof source.type !== 'string' || normalizeWsType(source.type) !== 'ACTION_ERROR') {
|
if (!source || typeof source.type !== 'string' || normalizeWsType(source.type) !== 'ACTION_ERROR') {
|
||||||
@@ -27,6 +53,7 @@ export function createStatusHandlers(context: SocketHandlerContext): StatusHandl
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
handleActionAck,
|
||||||
handleActionError,
|
handleActionError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,46 @@ import { resetRoundResolutionState } from '../store/gameStoreAdapter'
|
|||||||
import type { SocketHandlerContext, TurnHandlerApi, TurnPayloadRecord } from '../types'
|
import type { SocketHandlerContext, TurnHandlerApi, TurnPayloadRecord } from '../types'
|
||||||
|
|
||||||
export function createTurnHandlers(context: SocketHandlerContext): TurnHandlerApi {
|
export function createTurnHandlers(context: SocketHandlerContext): TurnHandlerApi {
|
||||||
|
function handlePlayerAllowAction(message: unknown): void {
|
||||||
|
const source = asRecord(message)
|
||||||
|
if (!source || typeof source.type !== 'string' || normalizeWsType(source.type) !== 'PLAYER_ALLOW_ACTION') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = asRecord(source.payload) ?? source
|
||||||
|
const roomId = readString(payload, 'room_id', 'roomId') || readString(source, 'roomId', 'room_id')
|
||||||
|
if (roomId && context.gameStore.roomId && roomId !== context.gameStore.roomId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerId = readPlayerTurnPlayerId(payload)
|
||||||
|
const timeout =
|
||||||
|
readNumber(payload, 'timeout', 'Timeout') ??
|
||||||
|
readNumber(source, 'timeout', 'Timeout') ??
|
||||||
|
0
|
||||||
|
const startAtRaw =
|
||||||
|
readNumber(payload, 'start_at', 'startAt', 'StartAt') ??
|
||||||
|
readNumber(source, 'start_at', 'startAt', 'StartAt')
|
||||||
|
|
||||||
|
if (!playerId || timeout <= 0) {
|
||||||
|
clearRoomCountdown(context.session)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const startAtMs = normalizeTimestampMs(startAtRaw)
|
||||||
|
const deadlineAtMs = startAtMs !== null ? startAtMs + timeout * 1000 : null
|
||||||
|
const remaining =
|
||||||
|
deadlineAtMs !== null ? Math.max(0, Math.ceil((deadlineAtMs - context.session.now.value) / 1000)) : timeout
|
||||||
|
|
||||||
|
setRoomCountdown(context.session, {
|
||||||
|
playerIds: [playerId],
|
||||||
|
actionDeadlineAt: deadlineAtMs !== null ? new Date(deadlineAtMs).toISOString() : null,
|
||||||
|
countdownSeconds: timeout,
|
||||||
|
duration: timeout,
|
||||||
|
remaining,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function handleDingQueCountdown(message: unknown): void {
|
function handleDingQueCountdown(message: unknown): void {
|
||||||
const source = asRecord(message)
|
const source = asRecord(message)
|
||||||
if (!source || typeof source.type !== 'string' || normalizeWsType(source.type) !== 'DING_QUE_COUNTDOWN') {
|
if (!source || typeof source.type !== 'string' || normalizeWsType(source.type) !== 'DING_QUE_COUNTDOWN') {
|
||||||
@@ -151,6 +191,7 @@ export function createTurnHandlers(context: SocketHandlerContext): TurnHandlerAp
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
handleDingQueCountdown,
|
handleDingQueCountdown,
|
||||||
|
handlePlayerAllowAction,
|
||||||
handlePlayerTurn,
|
handlePlayerTurn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/views/chengdu/socket/parsers/actionTimerSnapshot.ts
Normal file
27
src/views/chengdu/socket/parsers/actionTimerSnapshot.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { asRecord, readNumber, readString, readStringArray } from '../../../../game/chengdu/messageNormalizers'
|
||||||
|
import type { PlayerActionTimer } from '../../types'
|
||||||
|
|
||||||
|
export function parseActionTimerSnapshot(source: unknown): PlayerActionTimer | null {
|
||||||
|
const timer = asRecord(source)
|
||||||
|
if (!timer) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerIds = readStringArray(timer, 'player_ids', 'playerIds', 'PlayerIDs')
|
||||||
|
const countdownSeconds = readNumber(timer, 'countdown_seconds', 'countdownSeconds', 'CountdownSeconds') ?? 0
|
||||||
|
const duration = readNumber(timer, 'duration', 'Duration') ?? countdownSeconds
|
||||||
|
const remaining = readNumber(timer, 'remaining', 'Remaining') ?? countdownSeconds
|
||||||
|
const actionDeadlineAt = readString(timer, 'action_deadline_at', 'actionDeadlineAt', 'ActionDeadlineAt') || null
|
||||||
|
|
||||||
|
if (playerIds.length === 0 && countdownSeconds <= 0 && remaining <= 0 && !actionDeadlineAt) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
playerIds,
|
||||||
|
actionDeadlineAt,
|
||||||
|
countdownSeconds,
|
||||||
|
duration,
|
||||||
|
remaining,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { GameAction, PlayerTurnPayload, RoomTrusteePayload } from '../../../../game/actions'
|
import type { DiscardActionPayload, DrawActionPayload, GameAction, PlayerTurnPayload, RoomTrusteePayload } from '../../../../game/actions'
|
||||||
import type { ClaimOptionState } from '../../../../types/state'
|
import type { ClaimOptionState } from '../../../../types/state'
|
||||||
import { asRecord, normalizeWsType, readString } from '../../../../game/chengdu/messageNormalizers'
|
import { asRecord, normalizeTile, normalizeWsType, readNumber, readString } from '../../../../game/chengdu/messageNormalizers'
|
||||||
|
|
||||||
export function parseGameActionMessage(message: unknown): GameAction | null {
|
export function parseGameActionMessage(message: unknown): GameAction | null {
|
||||||
if (!message || typeof message !== 'object') {
|
if (!message || typeof message !== 'object') {
|
||||||
@@ -29,9 +29,46 @@ export function parseGameActionMessage(message: unknown): GameAction | null {
|
|||||||
? ({ type: 'ROOM_PLAYER_UPDATE', payload } as GameAction)
|
? ({ type: 'ROOM_PLAYER_UPDATE', payload } as GameAction)
|
||||||
: null
|
: null
|
||||||
case 'ROOM_TRUSTEE':
|
case 'ROOM_TRUSTEE':
|
||||||
|
case 'PLAYER_TRUSTEE':
|
||||||
return payload && typeof payload === 'object'
|
return payload && typeof payload === 'object'
|
||||||
? ({ type: 'ROOM_TRUSTEE', payload } as GameAction)
|
? ({ type: 'ROOM_TRUSTEE', payload } as GameAction)
|
||||||
: ({ type: 'ROOM_TRUSTEE', payload: source as unknown as RoomTrusteePayload } as GameAction)
|
: ({ type: 'ROOM_TRUSTEE', payload: source as unknown as RoomTrusteePayload } as GameAction)
|
||||||
|
case 'DRAW': {
|
||||||
|
const resolvedPayload = asRecord(payload)
|
||||||
|
const playerId =
|
||||||
|
readString(resolvedPayload ?? {}, 'player_id', 'playerId', 'PlayerID') ||
|
||||||
|
readString(source, 'target')
|
||||||
|
if (!playerId) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'DRAW_TILE',
|
||||||
|
payload: {
|
||||||
|
...(resolvedPayload as DrawActionPayload | null),
|
||||||
|
player_id: playerId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'DISCARD': {
|
||||||
|
const resolvedPayload = asRecord(payload)
|
||||||
|
const playerId =
|
||||||
|
readString(resolvedPayload ?? {}, 'player_id', 'playerId', 'PlayerID') ||
|
||||||
|
readString(source, 'target')
|
||||||
|
const tile = normalizeTile(resolvedPayload?.tile)
|
||||||
|
if (!playerId || !tile) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const nextSeat = readNumber(resolvedPayload ?? {}, 'next_seat', 'nextSeat')
|
||||||
|
return {
|
||||||
|
type: 'PLAY_TILE',
|
||||||
|
payload: {
|
||||||
|
...(resolvedPayload as DiscardActionPayload | null),
|
||||||
|
player_id: playerId,
|
||||||
|
tile,
|
||||||
|
...(typeof nextSeat === 'number' ? { next_seat: nextSeat } : {}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
case 'PLAYER_TURN':
|
case 'PLAYER_TURN':
|
||||||
case 'NEXT_TURN':
|
case 'NEXT_TURN':
|
||||||
return payload && typeof payload === 'object'
|
return payload && typeof payload === 'object'
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import {
|
|||||||
} from '../../../../game/chengdu/messageNormalizers'
|
} from '../../../../game/chengdu/messageNormalizers'
|
||||||
import type { RoomMetaSnapshotState } from '../../../../store/state'
|
import type { RoomMetaSnapshotState } from '../../../../store/state'
|
||||||
import type { PendingClaimState, PlayerState } from '../../../../types/state'
|
import type { PendingClaimState, PlayerState } from '../../../../types/state'
|
||||||
|
import type { PlayerActionTimer } from '../../types'
|
||||||
|
import { parseActionTimerSnapshot } from './actionTimerSnapshot'
|
||||||
|
|
||||||
interface RoomInfoSnapshotPlayerPair {
|
interface RoomInfoSnapshotPlayerPair {
|
||||||
roomPlayer: RoomMetaSnapshotState['players'][number]
|
roomPlayer: RoomMetaSnapshotState['players'][number]
|
||||||
@@ -39,6 +41,7 @@ export interface ParsedRoomInfoSnapshot {
|
|||||||
currentRound: number | null
|
currentRound: number | null
|
||||||
totalRounds: number | null
|
totalRounds: number | null
|
||||||
settlementDeadlineMs: number | null
|
settlementDeadlineMs: number | null
|
||||||
|
actionTimer: PlayerActionTimer | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ParseRoomInfoSnapshotOptions {
|
interface ParseRoomInfoSnapshotOptions {
|
||||||
@@ -237,7 +240,10 @@ export function parseRoomInfoSnapshot(
|
|||||||
readString(room ?? {}, 'status') ||
|
readString(room ?? {}, 'status') ||
|
||||||
readString(gameState ?? {}, 'phase') ||
|
readString(gameState ?? {}, 'phase') ||
|
||||||
'waiting'
|
'waiting'
|
||||||
const phase = readString(gameState ?? {}, 'phase') || readString(room ?? {}, 'status') || 'waiting'
|
const rawPendingClaim = asRecord(gameState?.pending_claim ?? gameState?.pendingClaim)
|
||||||
|
const hasPendingClaimWindow = Boolean(rawPendingClaim && Object.keys(rawPendingClaim).length > 0)
|
||||||
|
const phase =
|
||||||
|
hasPendingClaimWindow ? 'action' : readString(gameState ?? {}, 'phase') || readString(room ?? {}, 'status') || 'waiting'
|
||||||
const wallCount = readNumber(gameState ?? {}, 'wall_count', 'wallCount')
|
const wallCount = readNumber(gameState ?? {}, 'wall_count', 'wallCount')
|
||||||
const dealerIndex = readNumber(gameState ?? {}, 'dealer_index', 'dealerIndex')
|
const dealerIndex = readNumber(gameState ?? {}, 'dealer_index', 'dealerIndex')
|
||||||
const currentTurnSeat = readNumber(gameState ?? {}, 'current_turn', 'currentTurn')
|
const currentTurnSeat = readNumber(gameState ?? {}, 'current_turn', 'currentTurn')
|
||||||
@@ -270,5 +276,6 @@ export function parseRoomInfoSnapshot(
|
|||||||
currentRound: readNumber(gameState ?? {}, 'current_round', 'currentRound'),
|
currentRound: readNumber(gameState ?? {}, 'current_round', 'currentRound'),
|
||||||
totalRounds: readNumber(gameState ?? {}, 'total_rounds', 'totalRounds'),
|
totalRounds: readNumber(gameState ?? {}, 'total_rounds', 'totalRounds'),
|
||||||
settlementDeadlineMs: readNumber(gameState ?? {}, 'settlement_deadline_ms', 'settlementDeadlineMs'),
|
settlementDeadlineMs: readNumber(gameState ?? {}, 'settlement_deadline_ms', 'settlementDeadlineMs'),
|
||||||
|
actionTimer: parseActionTimerSnapshot(gameState?.action_timer ?? gameState?.actionTimer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
readStringArray,
|
readStringArray,
|
||||||
} from '../../../../game/chengdu/messageNormalizers'
|
} from '../../../../game/chengdu/messageNormalizers'
|
||||||
import type { PendingClaimState, PlayerState } from '../../../../types/state'
|
import type { PendingClaimState, PlayerState } from '../../../../types/state'
|
||||||
|
import type { PlayerActionTimer } from '../../types'
|
||||||
|
import { parseActionTimerSnapshot } from './actionTimerSnapshot'
|
||||||
|
|
||||||
export interface ParsedRoomStateSnapshot {
|
export interface ParsedRoomStateSnapshot {
|
||||||
roomId: string
|
roomId: string
|
||||||
@@ -25,6 +27,7 @@ export interface ParsedRoomStateSnapshot {
|
|||||||
currentRound: number | null
|
currentRound: number | null
|
||||||
totalRounds: number | null
|
totalRounds: number | null
|
||||||
settlementDeadlineMs: number | null
|
settlementDeadlineMs: number | null
|
||||||
|
actionTimer: PlayerActionTimer | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ParseRoomStateSnapshotOptions {
|
interface ParseRoomStateSnapshotOptions {
|
||||||
@@ -79,7 +82,10 @@ export function parseRoomStateSnapshot(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const phase = readString(payload, 'phase') || readString(payload, 'status') || 'waiting'
|
const rawPendingClaim = asRecord(payload.pending_claim ?? payload.pendingClaim)
|
||||||
|
const hasPendingClaimWindow = Boolean(rawPendingClaim && Object.keys(rawPendingClaim).length > 0)
|
||||||
|
const phase =
|
||||||
|
hasPendingClaimWindow ? 'action' : readString(payload, 'phase') || readString(payload, 'status') || 'waiting'
|
||||||
const wallCount = readNumber(payload, 'wall_count', 'wallCount')
|
const wallCount = readNumber(payload, 'wall_count', 'wallCount')
|
||||||
const currentTurnSeat = readNumber(payload, 'current_turn', 'currentTurn')
|
const currentTurnSeat = readNumber(payload, 'current_turn', 'currentTurn')
|
||||||
const currentTurnPlayerId = readString(payload, 'current_turn_player', 'currentTurnPlayer') || ''
|
const currentTurnPlayerId = readString(payload, 'current_turn_player', 'currentTurnPlayer') || ''
|
||||||
@@ -105,5 +111,6 @@ export function parseRoomStateSnapshot(
|
|||||||
currentRound: readNumber(payload, 'current_round', 'currentRound'),
|
currentRound: readNumber(payload, 'current_round', 'currentRound'),
|
||||||
totalRounds: readNumber(payload, 'total_rounds', 'totalRounds'),
|
totalRounds: readNumber(payload, 'total_rounds', 'totalRounds'),
|
||||||
settlementDeadlineMs: readNumber(payload, 'settlement_deadline_ms', 'settlementDeadlineMs'),
|
settlementDeadlineMs: readNumber(payload, 'settlement_deadline_ms', 'settlementDeadlineMs'),
|
||||||
|
actionTimer: parseActionTimerSnapshot(payload.action_timer ?? payload.actionTimer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ export function createSocketMessageRouter(deps: SocketMessageRouterDeps) {
|
|||||||
['ROOM_INFO', [deps.roomHandlers.handleRoomInfoResponse]],
|
['ROOM_INFO', [deps.roomHandlers.handleRoomInfoResponse]],
|
||||||
['ROOM_STATE', [deps.roomHandlers.handleRoomStateResponse]],
|
['ROOM_STATE', [deps.roomHandlers.handleRoomStateResponse]],
|
||||||
['PLAYER_HAND', [deps.playerHandlers.handlePlayerHandResponse]],
|
['PLAYER_HAND', [deps.playerHandlers.handlePlayerHandResponse]],
|
||||||
|
['PLAYER_ALLOW_ACTION', [deps.turnHandlers.handlePlayerAllowAction]],
|
||||||
['PLAYER_TURN', [deps.turnHandlers.handlePlayerTurn]],
|
['PLAYER_TURN', [deps.turnHandlers.handlePlayerTurn]],
|
||||||
['NEXT_TURN', [deps.turnHandlers.handlePlayerTurn]],
|
['NEXT_TURN', [deps.turnHandlers.handlePlayerTurn]],
|
||||||
|
['ACTION_ACK', [deps.statusHandlers.handleActionAck]],
|
||||||
['ACTION_ERROR', [deps.statusHandlers.handleActionError]],
|
['ACTION_ERROR', [deps.statusHandlers.handleActionError]],
|
||||||
['DING_QUE_COUNTDOWN', [deps.turnHandlers.handleDingQueCountdown]],
|
['DING_QUE_COUNTDOWN', [deps.turnHandlers.handleDingQueCountdown]],
|
||||||
['PLAYER_READY', [deps.playerHandlers.handleReadyStateResponse]],
|
['PLAYER_READY', [deps.playerHandlers.handleReadyStateResponse]],
|
||||||
|
|||||||
@@ -51,10 +51,12 @@ export interface PlayerHandlerApi extends ReadyStateApi {
|
|||||||
|
|
||||||
export interface TurnHandlerApi {
|
export interface TurnHandlerApi {
|
||||||
handleDingQueCountdown: (message: unknown) => void
|
handleDingQueCountdown: (message: unknown) => void
|
||||||
|
handlePlayerAllowAction: (message: unknown) => void
|
||||||
handlePlayerTurn: (message: unknown) => void
|
handlePlayerTurn: (message: unknown) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StatusHandlerApi {
|
export interface StatusHandlerApi {
|
||||||
|
handleActionAck: (message: unknown) => void
|
||||||
handleActionError: (message: unknown) => void
|
handleActionError: (message: unknown) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ class WsClient {
|
|||||||
// 订阅状态变化
|
// 订阅状态变化
|
||||||
onStatusChange(handler: StatusHandler) {
|
onStatusChange(handler: StatusHandler) {
|
||||||
this.statusHandlers.push(handler)
|
this.statusHandlers.push(handler)
|
||||||
|
handler(this.status)
|
||||||
return () => {
|
return () => {
|
||||||
this.statusHandlers = this.statusHandlers.filter(fn => fn !== handler)
|
this.statusHandlers = this.statusHandlers.filter(fn => fn !== handler)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user