Skip to content

RoomService ​

房间业务入口,负责 Room 的创建、查询、加入、离开、关闭、开始。

概述 ​

RoomService 是 Server Application 层的服务,承担"大厅"概念的全部业务:

  • 创建房间(带 gameType / maxPlayers / minPlayers / meta)
  • 加入 / 离开房间(含房主离开自动继承)
  • 查询 / 列表 / 关闭房间
  • 开始游戏(委托 GameService.startGame)

不负责:

  • 游戏规则(Plugin 已封装)
  • 直接操作 GameEngine

RoomService 通过 RoomRepository 持久化 Room,通过 GameService 触发对局创建。

CreateRoomOptions ​

ts
interface CreateRoomOptions {
  gameType: string
  maxPlayers: number
  ownerId?: string
  minPlayers?: number
  meta?: Record<string, unknown>
}
字段类型说明
gameTypestring游戏类型,需在 PluginRegistry 中已注册
maxPlayersnumber最大玩家数(必须 >= 2)
ownerId?string房主 ID(实际由 createRoom(options, playerId) 第二参数传入)
minPlayers?number最少开始人数;缺省取 2;必须 >= 2
meta?Record<string, unknown>房间元数据(如房间名、是否允许观战)

ListRoomsFilter ​

ts
interface ListRoomsFilter {
  gameType?: string
  status?: RoomStatus
}
字段类型说明
gameType?string按游戏类型过滤
status?RoomStatus按房间状态过滤

类 ​

RoomService ​

ts
class RoomService {
  constructor(
    roomRepository: RoomRepository,
    gameService: GameService
  )
}

方法 ​

createRoom() ​

ts
async createRoom(
  options: CreateRoomOptions,
  playerId: string
): Promise<Room>

创建房间。

参数 ​

名称类型说明
optionsCreateRoomOptions房间配置
playerIdstring创建者玩家 ID(自动成为房主并加入房间)

返回值 ​

Promise<Room>:创建好的房间实例。

行为 ​

  1. 校验 maxPlayers >= 2,否则抛 ServerError('BAD_REQUEST', 'maxPlayers must be >= 2', 400)。
  2. 校验 minPlayers >= 2(若提供),否则抛 ServerError('BAD_REQUEST', 'minPlayers must be >= 2', 400)。
  3. 构造 Room(id 由 newRoomId() 生成,ownerId = playerId,status = 'WAITING')。
  4. room.addPlayer(playerId) 将创建者加入房间。
  5. roomRepository.save(room)。
  6. 日志记录并返回 room。

getRoom() ​

ts
async getRoom(roomId: string): Promise<Room>

查询房间。

参数 ​

名称类型说明
roomIdstring房间 ID

返回值 ​

Promise<Room>:房间实例。

行为 ​

  • 未找到抛 ServerError('ROOM_NOT_FOUND', 'Room not found: ${roomId}', 404)。

listRooms() ​

ts
async listRooms(filter?: ListRoomsFilter): Promise<Room[]>

列出房间。

参数 ​

名称类型说明
filter?ListRoomsFilter可选过滤条件

返回值 ​

Promise<Room[]>:房间数组(未传 filter 时返回全部)。

行为 ​

  • 取出 roomRepository.findAll(),按 filter.gameType / filter.status 过滤。

joinRoom() ​

ts
async joinRoom(roomId: string, playerId: string): Promise<Room>

加入房间。

参数 ​

名称类型说明
roomIdstring房间 ID
playerIdstring玩家 ID

返回值 ​

Promise<Room>:加入后的房间实例。

行为 ​

  1. 取出房间;若 status 不是 WAITING 或 READY 抛 PLAYER_NOT_ALLOWED(403)。
  2. 若 room.isFull 抛 ServerError('ROOM_FULL', 'Room ${roomId} is full', 409)。
  3. 若 room.hasPlayer(playerId) 直接返回(重复加入幂等)。
  4. room.addPlayer(playerId) + roomRepository.save(room)。

leaveRoom() ​

ts
async leaveRoom(roomId: string, playerId: string): Promise<Room>

离开房间。

参数 ​

名称类型说明
roomIdstring房间 ID
playerIdstring玩家 ID

返回值 ​

Promise<Room>:离开后的房间实例。

行为 ​

  1. 取出房间;若玩家不在房间内抛 ServerError('PLAYER_NOT_IN_ROOM', '...', 403)。
  2. room.removePlayer(playerId)。
  3. 房主离开处理:
    • 若 room.playerIds.length === 0:room.status = 'CLOSED'。
    • 否则若 room.ownerId === playerId:room.setOwner(room.playerIds[0]) 自动转给剩余首位玩家。
  4. roomRepository.save(room)。

closeRoom() ​

ts
async closeRoom(roomId: string): Promise<void>

关闭房间。

参数 ​

名称类型说明
roomIdstring房间 ID

返回值 ​

Promise<void>

行为 ​

  • 取出房间,room.status = 'CLOSED' + roomRepository.save(room)。

startRoom() ​

ts
async startRoom(roomId: string): Promise<{ room: Room; gameId: string }>

开始游戏:委托 GameService.startGame。

参数 ​

名称类型说明
roomIdstring房间 ID

返回值 ​

Promise<{ room: Room; gameId: string }>:房间与新建的会话 ID。

行为 ​

  • 取出房间;若 !room.canStart 抛 ServerError('GAME_NOT_STARTED', '...', 400)。
  • 调用 gameService.startGame(room) 创建并启动 GameSession。
  • 返回 { room, gameId: session.id }。

权限校验

startRoom 本身不校验调用者是否为房主——这一权限检查由 Controller / Gateway 层完成。例如 RoomGateway.handleStart 会显式检查 room.ownerId === playerId。

示例 ​

创建并加入房间 ​

ts
import { createServer } from 'decklet/server'

const runtime = createServer()
const { roomService } = runtime

// 创建房间
const room = await roomService.createRoom(
  { gameType: 'uno', maxPlayers: 4, minPlayers: 2 },
  'p1'           // 创建者 = 房主
)
console.log(room.id, room.gameType, room.ownerId, room.status) // '...', 'uno', 'p1', 'WAITING'

// 其他玩家加入
await roomService.joinRoom(room.id, 'p2')
await roomService.joinRoom(room.id, 'p3')
console.log(room.currentPlayers) // 3

// 查询
const found = await roomService.getRoom(room.id)
const waiting = await roomService.listRooms({ status: 'WAITING' })

// 离开(p2 离开)
await roomService.leaveRoom(room.id, 'p2')

开始游戏 ​

ts
// 满足 minPlayers 后即可开始(RoomService 不校验房主身份)
const { room: started, gameId } = await roomService.startRoom(room.id)
console.log(started.status)         // 'PLAYING'
console.log(started.gameSessionId)  // 与 gameId 一致
console.log(gameId)                 // 'game_xxx'

房主自动继承 ​

ts
const room = await roomService.createRoom({ gameType: 'uno', maxPlayers: 4 }, 'p1')
await roomService.joinRoom(room.id, 'p2')

// p1 离开 → 房主自动转给 p2
const after = await roomService.leaveRoom(room.id, 'p1')
console.log(after.ownerId) // 'p2'

// 最后一人离开 → 房间关闭
const empty = await roomService.leaveRoom(after.id, 'p2')
console.log(empty.status) // 'CLOSED'

注意事项 ​

  • Room 不等于 GameSession:Room 是大厅概念,GameSession 是对局概念;Room 通过 gameSessionId 与 GameSession 弱关联。
  • RoomService 不直接持有 GameEngine / Plugin / Socket 引用,仅通过 GameService 间接创建对局。
  • 房主离开的继承策略在 leaveRoom 中实现:剩余首位玩家继承;空房间自动 CLOSED。
  • createRoom 的 ownerId 字段在 CreateRoomOptions 中存在但实际由 createRoom(options, playerId) 第二参数传入并覆盖。
  • 房间状态枚举见 enums.ts(详见 session.md)。