fertig-classic-games/server/multiplayer/lobby.js

75 lines
2.1 KiB
JavaScript

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();