feat: 重构游戏房间架构
This commit is contained in:
@@ -12,15 +12,12 @@ import type {
|
|||||||
/**
|
/**
|
||||||
* 获取指定游戏的房间列表
|
* 获取指定游戏的房间列表
|
||||||
*
|
*
|
||||||
* 返回某个游戏下所有房间(桌台)的信息,支持按状态筛选。
|
|
||||||
* 该接口无需登录即可调用。
|
|
||||||
*
|
|
||||||
* @param params - 查询参数,包含 gameId 和可选的 status 筛选
|
* @param params - 查询参数,包含 gameId 和可选的 status 筛选
|
||||||
* @returns 房间列表
|
* @returns 房间列表
|
||||||
*/
|
*/
|
||||||
export function getRoomList(params: GetRoomListRequest) {
|
export function getRoomList(params: GetRoomListRequest) {
|
||||||
return requestClient.get<GetRoomListResponse>(
|
return requestClient.get<GetRoomListResponse>(
|
||||||
'/game/room/list',
|
'/room/list',
|
||||||
params as unknown as Record<string, unknown>
|
params as unknown as Record<string, unknown>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -28,39 +25,29 @@ export function getRoomList(params: GetRoomListRequest) {
|
|||||||
/**
|
/**
|
||||||
* 创建房间
|
* 创建房间
|
||||||
*
|
*
|
||||||
* 根据当前游戏和选定的参数创建新房间。
|
|
||||||
* 房间名称留空时由后端自动生成"桌 XX"编号。
|
|
||||||
* 密码留空时房间为公开房间。
|
|
||||||
*
|
|
||||||
* @param data - 创建房间参数
|
* @param data - 创建房间参数
|
||||||
* @returns 新建的房间信息
|
* @returns 新建的房间信息
|
||||||
*/
|
*/
|
||||||
export function createRoom(data: CreateRoomRequest) {
|
export function createRoom(data: CreateRoomRequest) {
|
||||||
return requestClient.post<CreateRoomResponse>('/game/room/create', data)
|
return requestClient.post<CreateRoomResponse>('/room/create', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加入房间
|
* 加入房间
|
||||||
*
|
*
|
||||||
* 玩家通过此接口加入指定房间,成为房间内的玩家。
|
|
||||||
* 公开房间无需传密码。加入成功后需通过 WebSocket 进行后续交互。
|
|
||||||
*
|
|
||||||
* @param data - 加入房间参数(roomId 和可选的 password)
|
* @param data - 加入房间参数(roomId 和可选的 password)
|
||||||
* @returns 更新后的房间信息
|
* @returns 更新后的房间信息
|
||||||
*/
|
*/
|
||||||
export function joinRoom(data: JoinRoomRequest) {
|
export function joinRoom(data: JoinRoomRequest) {
|
||||||
return requestClient.post<RoomItem>('/game/room/join', data)
|
return requestClient.post<RoomItem>('/room/join', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取房间详情
|
* 获取房间详情
|
||||||
*
|
*
|
||||||
* 用于进入房间页时获取初始快照(棋盘状态、玩家信息等)。
|
|
||||||
* 无需登录即可调用(观战者也需访问)。
|
|
||||||
*
|
|
||||||
* @param roomId 房间 ID
|
* @param roomId 房间 ID
|
||||||
* @returns 房间详情
|
* @returns 房间详情
|
||||||
*/
|
*/
|
||||||
export function getRoomDetail(roomId: string) {
|
export function getRoomDetail(roomId: string) {
|
||||||
return requestClient.get<RoomDetailResponse>(`/game/room/${roomId}`)
|
return requestClient.get<RoomDetailResponse>(`/room/${roomId}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ export interface RoomItem {
|
|||||||
mode: string
|
mode: string
|
||||||
/** 房主昵称 */
|
/** 房主昵称 */
|
||||||
creatorName: string
|
creatorName: string
|
||||||
/** 是否允许观战 */
|
|
||||||
allowSpectate: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取游戏列表 — 无请求参数 */
|
/** 获取游戏列表 — 无请求参数 */
|
||||||
@@ -101,8 +99,6 @@ export interface CreateRoomRequest {
|
|||||||
stakes: number
|
stakes: number
|
||||||
/** 最大玩家数 */
|
/** 最大玩家数 */
|
||||||
maxPlayers: number
|
maxPlayers: number
|
||||||
/** 是否允许观战 */
|
|
||||||
allowSpectate?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建房间响应 */
|
/** 创建房间响应 */
|
||||||
@@ -124,7 +120,6 @@ export interface RoomDetailResponse {
|
|||||||
status: RoomStatus
|
status: RoomStatus
|
||||||
creatorId: string
|
creatorId: string
|
||||||
creatorName: string
|
creatorName: string
|
||||||
allowSpectate: boolean
|
|
||||||
players: RoomPlayerDetail[]
|
players: RoomPlayerDetail[]
|
||||||
/** 棋盘状态(对局中才有) */
|
/** 棋盘状态(对局中才有) */
|
||||||
boardState?: number[][]
|
boardState?: number[][]
|
||||||
@@ -185,8 +180,8 @@ export type WsOutboundType =
|
|||||||
| 'game_over'
|
| 'game_over'
|
||||||
| 'player_join'
|
| 'player_join'
|
||||||
| 'player_leave'
|
| 'player_leave'
|
||||||
|
| 'player_disconnected'
|
||||||
|
| 'player_reconnected'
|
||||||
| 'ready_change'
|
| 'ready_change'
|
||||||
| 'spectator_join'
|
|
||||||
| 'spectator_leave'
|
|
||||||
| 'move_history'
|
| 'move_history'
|
||||||
| 'error'
|
| 'error'
|
||||||
|
|||||||
@@ -58,11 +58,6 @@
|
|||||||
<div class="create-room-dialog__hint">设置密码后,其他玩家需输入密码才能加入</div>
|
<div class="create-room-dialog__hint">设置密码后,其他玩家需输入密码才能加入</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!-- 是否允许观战 -->
|
|
||||||
<el-form-item label="观战">
|
|
||||||
<el-switch v-model="form.allowSpectate" />
|
|
||||||
<div class="create-room-dialog__hint">开启后,其他玩家可旁观对局</div>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -87,7 +82,6 @@ interface CreateRoomForm {
|
|||||||
mode: string
|
mode: string
|
||||||
stakes: number | undefined
|
stakes: number | undefined
|
||||||
password: string
|
password: string
|
||||||
allowSpectate: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -123,8 +117,7 @@ const form = reactive<CreateRoomForm>({
|
|||||||
name: '',
|
name: '',
|
||||||
mode: '',
|
mode: '',
|
||||||
stakes: undefined,
|
stakes: undefined,
|
||||||
password: '',
|
password: ''
|
||||||
allowSpectate: true
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 初始化默认值:取当前游戏配置的第一个选项 */
|
/** 初始化默认值:取当前游戏配置的第一个选项 */
|
||||||
@@ -135,7 +128,6 @@ function initDefaults() {
|
|||||||
}
|
}
|
||||||
form.name = ''
|
form.name = ''
|
||||||
form.password = ''
|
form.password = ''
|
||||||
form.allowSpectate = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 弹窗打开时设置默认值 */
|
/** 弹窗打开时设置默认值 */
|
||||||
@@ -171,8 +163,7 @@ async function handleSubmit() {
|
|||||||
password: form.password || undefined,
|
password: form.password || undefined,
|
||||||
mode: form.mode,
|
mode: form.mode,
|
||||||
stakes: form.stakes!,
|
stakes: form.stakes!,
|
||||||
maxPlayers: gameConfig.value?.maxPlayers ?? 0,
|
maxPlayers: gameConfig.value?.maxPlayers ?? 0
|
||||||
allowSpectate: form.allowSpectate
|
|
||||||
})
|
})
|
||||||
visible.value = false
|
visible.value = false
|
||||||
emit('created')
|
emit('created')
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export const useRoomStore = defineStore('room', () => {
|
|||||||
const status = ref<RoomStatus>('waiting')
|
const status = ref<RoomStatus>('waiting')
|
||||||
const creatorId = ref<string>('')
|
const creatorId = ref<string>('')
|
||||||
const creatorName = ref<string>('')
|
const creatorName = ref<string>('')
|
||||||
const allowSpectate = ref<boolean>(true)
|
|
||||||
|
|
||||||
// ===== 玩家信息 =====
|
// ===== 玩家信息 =====
|
||||||
const players = ref<RoomPlayerDetail[]>([])
|
const players = ref<RoomPlayerDetail[]>([])
|
||||||
@@ -43,18 +42,15 @@ export const useRoomStore = defineStore('room', () => {
|
|||||||
|
|
||||||
// ===== 当前用户身份 =====
|
// ===== 当前用户身份 =====
|
||||||
const currentUserId = ref<string>('')
|
const currentUserId = ref<string>('')
|
||||||
/** 当前用户在房间中的身份:player(玩家)| spectator(观战者) */
|
/** 当前用户是否为房间中的玩家 */
|
||||||
const myRole = ref<'player' | 'spectator'>('spectator')
|
const isPlayer = computed(() =>
|
||||||
/** 当前用户是否为玩家 */
|
players.value.some((p) => p.userId === currentUserId.value)
|
||||||
const isPlayer = computed(() => myRole.value === 'player')
|
)
|
||||||
|
|
||||||
// ===== 求和状态 =====
|
// ===== 求和状态 =====
|
||||||
const drawRequested = ref<boolean>(false)
|
const drawRequested = ref<boolean>(false)
|
||||||
const drawRequestFrom = ref<string | null>(null)
|
const drawRequestFrom = ref<string | null>(null)
|
||||||
|
|
||||||
// ===== 观战者 =====
|
|
||||||
const spectatorCount = ref<number>(0)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从房间详情 HTTP 响应初始化 Store
|
* 从房间详情 HTTP 响应初始化 Store
|
||||||
* @param detail - 房间详情响应数据
|
* @param detail - 房间详情响应数据
|
||||||
@@ -72,16 +68,11 @@ export const useRoomStore = defineStore('room', () => {
|
|||||||
status.value = detail.status
|
status.value = detail.status
|
||||||
creatorId.value = detail.creatorId
|
creatorId.value = detail.creatorId
|
||||||
creatorName.value = detail.creatorName
|
creatorName.value = detail.creatorName
|
||||||
allowSpectate.value = detail.allowSpectate
|
|
||||||
players.value = detail.players
|
players.value = detail.players
|
||||||
currentUserId.value = userId
|
currentUserId.value = userId
|
||||||
boardState.value = detail.boardState ?? []
|
boardState.value = detail.boardState ?? []
|
||||||
currentTurn.value = detail.currentTurn ?? null
|
currentTurn.value = detail.currentTurn ?? null
|
||||||
moves.value = detail.moves ?? []
|
moves.value = detail.moves ?? []
|
||||||
|
|
||||||
// 判定当前用户是玩家还是观战者
|
|
||||||
const isPlayerInRoom = detail.players.some((p) => p.userId === userId)
|
|
||||||
myRole.value = isPlayerInRoom ? 'player' : 'spectator'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新棋盘状态(WS move 事件) */
|
/** 更新棋盘状态(WS move 事件) */
|
||||||
@@ -145,11 +136,6 @@ export const useRoomStore = defineStore('room', () => {
|
|||||||
resultType.value = type
|
resultType.value = type
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新观战人数 */
|
|
||||||
function setSpectatorCount(count: number) {
|
|
||||||
spectatorCount.value = count
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 收到求和请求 */
|
/** 收到求和请求 */
|
||||||
function receiveDrawRequest(fromUserId: string) {
|
function receiveDrawRequest(fromUserId: string) {
|
||||||
drawRequested.value = true
|
drawRequested.value = true
|
||||||
@@ -178,14 +164,14 @@ export const useRoomStore = defineStore('room', () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
roomId, roomName, gameId, gameIcon, gameName,
|
roomId, roomName, gameId, gameIcon, gameName,
|
||||||
maxPlayers, mode, stakes, status, creatorId, creatorName, allowSpectate,
|
maxPlayers, mode, stakes, status, creatorId, creatorName,
|
||||||
players, isOwner, currentUserId, myRole, isPlayer,
|
players, isOwner, currentUserId, isPlayer,
|
||||||
boardState, currentTurn, moves, winner, resultType,
|
boardState, currentTurn, moves, winner, resultType,
|
||||||
isPlaying, isFinished,
|
isPlaying, isFinished,
|
||||||
messages,
|
messages,
|
||||||
drawRequested, drawRequestFrom, spectatorCount,
|
drawRequested, drawRequestFrom,
|
||||||
initFromDetail, applyMove, setCurrentTurn, addMessage,
|
initFromDetail, applyMove, setCurrentTurn, addMessage,
|
||||||
updateReady, addPlayer, removePlayer, setGameOver, setSpectatorCount,
|
updateReady, addPlayer, removePlayer, setGameOver,
|
||||||
receiveDrawRequest, clearDrawRequest, reset
|
receiveDrawRequest, clearDrawRequest, reset
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,9 +16,6 @@
|
|||||||
<!-- 右侧面板 -->
|
<!-- 右侧面板 -->
|
||||||
<RoomRightPanel />
|
<RoomRightPanel />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部观战栏 -->
|
|
||||||
<SpectatorBar v-if="roomStore.allowSpectate" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -36,7 +33,6 @@ import RoomTopBar from './components/RoomTopBar.vue'
|
|||||||
import RoomSidePanel from './components/RoomSidePanel.vue'
|
import RoomSidePanel from './components/RoomSidePanel.vue'
|
||||||
import GameCanvas from './components/GameCanvas.vue'
|
import GameCanvas from './components/GameCanvas.vue'
|
||||||
import RoomRightPanel from './components/RoomRightPanel.vue'
|
import RoomRightPanel from './components/RoomRightPanel.vue'
|
||||||
import SpectatorBar from './components/SpectatorBar.vue'
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -154,14 +150,10 @@ function handleWsMessage(msg: WsMessage) {
|
|||||||
roomStore.updateReady(data.userId, data.ready)
|
roomStore.updateReady(data.userId, data.ready)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'spectator_join': {
|
case 'player_disconnected': {
|
||||||
const data = msg.data as { count: number }
|
|
||||||
roomStore.setSpectatorCount(data.count)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'spectator_leave': {
|
case 'player_reconnected': {
|
||||||
const data = msg.data as { count: number }
|
|
||||||
roomStore.setSpectatorCount(data.count)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'draw_requested': {
|
case 'draw_requested': {
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="spectator-bar">
|
|
||||||
<span class="spectator-bar__icon">👀</span>
|
|
||||||
<span class="spectator-bar__text">
|
|
||||||
观战中({{ spectatorCount }}人)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useRoomStore } from '@/stores'
|
|
||||||
import { storeToRefs } from 'pinia'
|
|
||||||
|
|
||||||
const store = useRoomStore()
|
|
||||||
const { spectatorCount } = storeToRefs(store)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.spectator-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 6px;
|
|
||||||
height: 32px;
|
|
||||||
background-color: var(--bg-surface);
|
|
||||||
border-top: 1px solid var(--border-light);
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spectator-bar__icon {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user