Compare commits
5 Commits
5210b8309f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ee797ebb14 | |||
| 1308ca5a2c | |||
| 632a0267a4 | |||
| fba407c1bf | |||
| 0fa14ca407 |
BIN
pictures/微信图片_20260318170012_3_20.png
Normal file
BIN
pictures/微信图片_20260318170012_3_20.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
BIN
pictures/微信图片_20260318170013_4_20.png
Normal file
BIN
pictures/微信图片_20260318170013_4_20.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
BIN
pictures/微信图片_20260318170016_5_20.png
Normal file
BIN
pictures/微信图片_20260318170016_5_20.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
BIN
pictures/微信图片_20260318170019_6_20.png
Normal file
BIN
pictures/微信图片_20260318170019_6_20.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
BIN
pictures/微信图片_20260318170025_7_20.png
Normal file
BIN
pictures/微信图片_20260318170025_7_20.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
@@ -7,6 +7,11 @@ export interface RoomItem {
|
||||
owner_id: string
|
||||
max_players: number
|
||||
player_count: number
|
||||
players?: Array<{
|
||||
index: number
|
||||
player_id: string
|
||||
ready: boolean
|
||||
}>
|
||||
status: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
@@ -58,8 +63,8 @@ export async function joinRoom(
|
||||
auth: AuthSession,
|
||||
input: { roomId: string },
|
||||
onAuthUpdated?: (next: AuthSession) => void,
|
||||
): Promise<void> {
|
||||
await authedRequest<Record<string, never> | RoomItem>({
|
||||
): Promise<RoomItem> {
|
||||
return authedRequest<RoomItem>({
|
||||
method: 'POST',
|
||||
path: ROOM_JOIN_PATH,
|
||||
auth,
|
||||
|
||||
139
src/state/active-room.ts
Normal file
139
src/state/active-room.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const DEFAULT_MAX_PLAYERS = 4
|
||||
export type RoomStatus = 'waiting' | 'playing' | 'finished'
|
||||
|
||||
export interface RoomPlayerState {
|
||||
index: number
|
||||
playerId: string
|
||||
ready: boolean
|
||||
}
|
||||
|
||||
export interface RuleState {
|
||||
name: string
|
||||
isBloodFlow: boolean
|
||||
hasHongZhong: boolean
|
||||
}
|
||||
|
||||
export interface GamePlayerState {
|
||||
playerId: string
|
||||
index: number
|
||||
ready: boolean
|
||||
}
|
||||
|
||||
export interface EngineState {
|
||||
phase: string
|
||||
dealerIndex: number
|
||||
currentTurn: number
|
||||
needDraw: boolean
|
||||
players: GamePlayerState[]
|
||||
wall: string[]
|
||||
lastDiscardTile: string | null
|
||||
lastDiscardBy: string
|
||||
pendingClaim: Record<string, unknown> | null
|
||||
winners: string[]
|
||||
scores: Record<string, number>
|
||||
lastDrawPlayerId: string
|
||||
lastDrawFromGang: boolean
|
||||
lastDrawIsLastTile: boolean
|
||||
huWay: string
|
||||
}
|
||||
|
||||
export interface GameState {
|
||||
rule: RuleState | null
|
||||
state: EngineState | null
|
||||
}
|
||||
|
||||
export interface RoomState {
|
||||
id: string
|
||||
name: string
|
||||
gameType: string
|
||||
ownerId: string
|
||||
maxPlayers: number
|
||||
playerCount: number
|
||||
status: RoomStatus | string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
game: GameState | null
|
||||
players: RoomPlayerState[]
|
||||
currentTurnIndex: number | null
|
||||
}
|
||||
|
||||
function createInitialRoomState(): RoomState {
|
||||
return {
|
||||
id: '',
|
||||
name: '',
|
||||
gameType: 'chengdu',
|
||||
ownerId: '',
|
||||
maxPlayers: DEFAULT_MAX_PLAYERS,
|
||||
playerCount: 0,
|
||||
status: 'waiting',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
game: null,
|
||||
players: [],
|
||||
currentTurnIndex: null,
|
||||
}
|
||||
}
|
||||
|
||||
export const activeRoomState = ref<RoomState>(createInitialRoomState())
|
||||
|
||||
export function destroyActiveRoomState(): void {
|
||||
activeRoomState.value = createInitialRoomState()
|
||||
}
|
||||
|
||||
export function resetActiveRoomState(seed?: Partial<RoomState>): void {
|
||||
destroyActiveRoomState()
|
||||
if (!seed) {
|
||||
return
|
||||
}
|
||||
|
||||
activeRoomState.value = {
|
||||
...activeRoomState.value,
|
||||
...seed,
|
||||
players: seed.players ?? [],
|
||||
}
|
||||
}
|
||||
|
||||
export function mergeActiveRoomState(next: RoomState): void {
|
||||
if (activeRoomState.value.id && next.id && next.id !== activeRoomState.value.id) {
|
||||
return
|
||||
}
|
||||
|
||||
activeRoomState.value = {
|
||||
...activeRoomState.value,
|
||||
...next,
|
||||
game: next.game ?? activeRoomState.value.game,
|
||||
players: next.players.length > 0 ? next.players : activeRoomState.value.players,
|
||||
currentTurnIndex:
|
||||
next.currentTurnIndex !== null ? next.currentTurnIndex : activeRoomState.value.currentTurnIndex,
|
||||
}
|
||||
}
|
||||
|
||||
export function hydrateActiveRoomFromSelection(input: {
|
||||
roomId: string
|
||||
roomName?: string
|
||||
gameType?: string
|
||||
ownerId?: string
|
||||
maxPlayers?: number
|
||||
playerCount?: number
|
||||
status?: string
|
||||
createdAt?: string
|
||||
updatedAt?: string
|
||||
players?: RoomPlayerState[]
|
||||
currentTurnIndex?: number | null
|
||||
}): void {
|
||||
resetActiveRoomState({
|
||||
id: input.roomId,
|
||||
name: input.roomName ?? '',
|
||||
gameType: input.gameType ?? 'chengdu',
|
||||
ownerId: input.ownerId ?? '',
|
||||
maxPlayers: input.maxPlayers ?? DEFAULT_MAX_PLAYERS,
|
||||
playerCount: input.playerCount ?? 0,
|
||||
status: input.status ?? 'waiting',
|
||||
createdAt: input.createdAt ?? '',
|
||||
updatedAt: input.updatedAt ?? '',
|
||||
players: input.players ?? [],
|
||||
currentTurnIndex: input.currentTurnIndex ?? null,
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@ import { useRouter } from 'vue-router'
|
||||
import { AuthExpiredError, type AuthSession } from '../api/authed-request'
|
||||
import { createRoom, joinRoom, listRooms, type RoomItem } from '../api/mahjong'
|
||||
import { getUserInfo, type UserInfo } from '../api/user'
|
||||
import { hydrateActiveRoomFromSelection } from '../state/active-room'
|
||||
import type { RoomPlayerState } from '../state/active-room'
|
||||
import type { StoredAuth } from '../types/session'
|
||||
import { clearAuth, readStoredAuth, writeStoredAuth } from '../utils/auth-storage'
|
||||
|
||||
@@ -121,6 +123,16 @@ function isMyRoom(room: RoomItem): boolean {
|
||||
return Boolean(currentUserId.value) && room.owner_id === currentUserId.value
|
||||
}
|
||||
|
||||
function mapRoomPlayers(room: RoomItem): RoomPlayerState[] {
|
||||
return (room.players ?? [])
|
||||
.map((item, fallbackIndex) => ({
|
||||
index: Number.isFinite(item.index) ? item.index : fallbackIndex,
|
||||
playerId: item.player_id,
|
||||
ready: Boolean(item.ready),
|
||||
}))
|
||||
.filter((item) => Boolean(item.playerId))
|
||||
}
|
||||
|
||||
function toSession(source: StoredAuth): AuthSession {
|
||||
return {
|
||||
token: source.token,
|
||||
@@ -220,6 +232,18 @@ async function submitCreateRoom(): Promise<void> {
|
||||
)
|
||||
|
||||
createdRoom.value = room
|
||||
hydrateActiveRoomFromSelection({
|
||||
roomId: room.room_id,
|
||||
roomName: room.name,
|
||||
gameType: room.game_type,
|
||||
ownerId: room.owner_id,
|
||||
maxPlayers: room.max_players,
|
||||
playerCount: room.player_count,
|
||||
status: room.status,
|
||||
createdAt: room.created_at,
|
||||
updatedAt: room.updated_at,
|
||||
players: mapRoomPlayers(room),
|
||||
})
|
||||
quickJoinRoomId.value = room.room_id
|
||||
createRoomForm.value.name = ''
|
||||
showCreateModal.value = false
|
||||
@@ -251,18 +275,27 @@ async function handleJoinRoom(room?: { roomId?: string; roomName?: string }): Pr
|
||||
return
|
||||
}
|
||||
|
||||
const targetRoomName =
|
||||
room?.roomName ?? rooms.value.find((item) => item.room_id === targetRoomId)?.name ?? ''
|
||||
|
||||
roomSubmitting.value = true
|
||||
try {
|
||||
await joinRoom(session, { roomId: targetRoomId }, syncAuth)
|
||||
quickJoinRoomId.value = targetRoomId
|
||||
successMessage.value = `已加入房间:${targetRoomId}`
|
||||
const joinedRoom = await joinRoom(session, { roomId: targetRoomId }, syncAuth)
|
||||
hydrateActiveRoomFromSelection({
|
||||
roomId: joinedRoom.room_id,
|
||||
roomName: joinedRoom.name,
|
||||
gameType: joinedRoom.game_type,
|
||||
ownerId: joinedRoom.owner_id,
|
||||
maxPlayers: joinedRoom.max_players,
|
||||
playerCount: joinedRoom.player_count,
|
||||
status: joinedRoom.status,
|
||||
createdAt: joinedRoom.created_at,
|
||||
updatedAt: joinedRoom.updated_at,
|
||||
players: mapRoomPlayers(joinedRoom),
|
||||
})
|
||||
quickJoinRoomId.value = joinedRoom.room_id
|
||||
successMessage.value = `已加入房间:${joinedRoom.room_id}`
|
||||
await refreshRooms()
|
||||
await router.push({
|
||||
path: `/game/chengdu/${targetRoomId}`,
|
||||
query: targetRoomName ? { roomName: targetRoomName } : undefined,
|
||||
path: `/game/chengdu/${joinedRoom.room_id}`,
|
||||
query: joinedRoom.name ? { roomName: joinedRoom.name } : undefined,
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof AuthExpiredError) {
|
||||
@@ -298,6 +331,18 @@ async function enterCreatedRoom(): Promise<void> {
|
||||
}
|
||||
|
||||
showCreatedModal.value = false
|
||||
hydrateActiveRoomFromSelection({
|
||||
roomId: createdRoom.value.room_id,
|
||||
roomName: createdRoom.value.name,
|
||||
gameType: createdRoom.value.game_type,
|
||||
ownerId: createdRoom.value.owner_id,
|
||||
maxPlayers: createdRoom.value.max_players,
|
||||
playerCount: createdRoom.value.player_count,
|
||||
status: createdRoom.value.status,
|
||||
createdAt: createdRoom.value.created_at,
|
||||
updatedAt: createdRoom.value.updated_at,
|
||||
players: mapRoomPlayers(createdRoom.value),
|
||||
})
|
||||
await router.push({
|
||||
path: `/game/chengdu/${createdRoom.value.room_id}`,
|
||||
query: { roomName: createdRoom.value.name },
|
||||
|
||||
@@ -4,15 +4,17 @@ import vue from '@vitejs/plugin-vue'
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
host: '127.0.0.1',
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api/v1': {
|
||||
target: 'http://127.0.0.1:8080',
|
||||
target: 'http://127.0.0.1:19000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/api/v1/ws': {
|
||||
target: 'ws://127.0.0.1:8080',
|
||||
target: 'ws://127.0.0.1:19000',
|
||||
ws: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user