import crypto from 'node:crypto'; import { getGame } from './gameRegistry.js'; export class LobbyManager { constructor() { this.rooms = new Map(); } list(gameSlug) { return [...this.rooms.values()] .filter((r) => !gameSlug || r.gameSlug === gameSlug) .map((r) => this.summary(r)); } summary(room) { return { id: room.id, gameSlug: room.gameSlug, name: room.name, hostId: room.hostId, players: room.players.map((p) => ({ id: p.userId, username: p.username, seat: p.seat })), maxPlayers: room.maxPlayers, status: room.status, }; } create({ gameSlug, hostUser, name }) { const game = getGame(gameSlug); if (!game) throw new Error(`Unknown game: ${gameSlug}`); if (!game.supportsMultiplayer) throw new Error(`${game.name} is not multiplayer.`); const id = crypto.randomBytes(6).toString('hex'); const room = { id, gameSlug, name: name ?? `${hostUser.username}'s table`, hostId: hostUser.id, players: [{ userId: hostUser.id, username: hostUser.username, seat: 0 }], maxPlayers: game.maxPlayers, status: 'waiting', }; this.rooms.set(id, room); return room; } join(roomId, user) { const room = this.rooms.get(roomId); if (!room) throw new Error('Room not found.'); if (room.players.some((p) => p.userId === user.id)) return room; if (room.players.length >= room.maxPlayers) throw new Error('Room is full.'); const usedSeats = new Set(room.players.map((p) => p.seat)); let seat = 0; while (usedSeats.has(seat)) seat += 1; room.players.push({ userId: user.id, username: user.username, seat }); return room; } leave(roomId, userId) { const room = this.rooms.get(roomId); if (!room) return null; room.players = room.players.filter((p) => p.userId !== userId); if (!room.players.length) { this.rooms.delete(roomId); return null; } if (room.hostId === userId) room.hostId = room.players[0].userId; return room; } get(roomId) { return this.rooms.get(roomId) ?? null; } } export const lobby = new LobbyManager();