Room Management
Room Management handles the creation, configuration, and lifecycle of game rooms for tournament play. This includes player connections, session state synchronization, and room cleanup after tournament completion.Room Creation Process
Tournament Room Initialization
When a tournament becomes active, the Game Platform creates a dedicated game room:Copy
class TournamentRoomManager {
constructor(internalApiKey) {
this.apiKey = internalApiKey;
this.rooms = new Map();
}
async createTournamentRoom(tournamentId) {
try {
// Get tournament configuration
const config = await this.getTournamentConfig(tournamentId);
// Create game room instance
const room = {
roomId: `room_${tournamentId}_${Date.now()}`,
tournamentId: tournamentId,
gameSlug: config.gameSlug,
maxPlayers: config.maxPlayers,
currentPlayers: 0,
status: 'waiting',
gameConfig: config.gameConfig,
players: new Map(),
createdAt: new Date(),
lastActivity: new Date()
};
// Initialize game-specific settings
await this.initializeGameRoom(room, config);
// Store room reference
this.rooms.set(room.roomId, room);
console.log(`Tournament room created: ${room.roomId}`);
return room;
} catch (error) {
console.error(`Room creation failed for tournament ${tournamentId}:`, error);
throw error;
}
}
async initializeGameRoom(room, config) {
// Game-specific initialization
switch (config.gameSlug) {
case 'crash-classic':
room.gameState = {
currentRound: 0,
roundStartTime: null,
crashPoint: null,
bets: new Map()
};
break;
default:
room.gameState = {};
}
// Set up room events
room.events = {
onPlayerJoin: this.handlePlayerJoin.bind(this),
onPlayerLeave: this.handlePlayerLeave.bind(this),
onGameEvent: this.handleGameEvent.bind(this)
};
}
}
Room Configuration
Copy
{
"roomId": "room_12345_1642089600",
"tournamentId": "12345",
"gameSlug": "crash-classic",
"status": "active",
"maxPlayers": 100,
"currentPlayers": 87,
"gameConfig": {
"maxRounds": 10,
"roundDuration": 300,
"eliminationRate": 0.5
},
"roomSettings": {
"allowSpectators": true,
"chatEnabled": true,
"pauseOnDisconnect": false
},
"createdAt": "2024-01-15T20:00:00Z",
"lastActivity": "2024-01-15T20:25:30Z"
}
Player Connection Management
Player Join Process
Copy
async handlePlayerConnection(roomId, jwtToken, socketConnection) {
try {
// Validate JWT token
const validation = await this.validatePlayerToken(jwtToken);
if (!validation.valid) {
throw new Error(`Invalid token: ${validation.error}`);
}
// Get room reference
const room = this.rooms.get(roomId);
if (!room) {
throw new Error(`Room ${roomId} not found`);
}
// Check room capacity
if (room.currentPlayers >= room.maxPlayers) {
throw new Error('Room is full');
}
// Create player session
const playerSession = {
playerId: validation.playerId,
displayName: validation.playerInfo.displayName,
socket: socketConnection,
joinedAt: new Date(),
lastActivity: new Date(),
status: 'connected',
gameData: {
position: null,
score: 0,
isEliminated: false
}
};
// Add player to room
room.players.set(validation.playerId, playerSession);
room.currentPlayers++;
room.lastActivity = new Date();
// Send room state to player
await this.sendRoomState(playerSession.socket, room);
// Notify other players
await this.broadcastPlayerJoin(room, playerSession);
console.log(`Player ${validation.playerId} joined room ${roomId}`);
return playerSession;
} catch (error) {
console.error('Player connection failed:', error);
throw error;
}
}
Session State Synchronization
Copy
async syncPlayerState(roomId, playerId, gameState) {
const room = this.rooms.get(roomId);
const player = room?.players.get(playerId);
if (!player) {
throw new Error('Player not found in room');
}
// Update player game data
player.gameData = {
...player.gameData,
...gameState,
lastUpdate: new Date()
};
player.lastActivity = new Date();
room.lastActivity = new Date();
// Sync with tournament system if significant change
if (gameState.score || gameState.position) {
await this.reportPlayerUpdate(room.tournamentId, playerId, gameState);
}
}
Room Lifecycle Management
Room Status States
| Status | Description | Actions Available |
|---|---|---|
waiting | Room created, waiting for players | Player connections |
active | Tournament in progress | Gameplay, player management |
paused | Temporarily paused | Resume, player reconnection |
completed | Tournament finished | Results, cleanup |
cancelled | Tournament cancelled | Player cleanup, refunds |
Room Cleanup Process
Copy
async cleanupRoom(roomId, reason = 'completed') {
const room = this.rooms.get(roomId);
if (!room) {
console.warn(`Room ${roomId} not found for cleanup`);
return;
}
console.log(`Cleaning up room ${roomId} - reason: ${reason}`);
try {
// Disconnect all players
for (const [playerId, player] of room.players) {
try {
await this.disconnectPlayer(player.socket, reason);
} catch (error) {
console.error(`Failed to disconnect player ${playerId}:`, error);
}
}
// Archive room data
await this.archiveRoomData(room);
// Remove room from active rooms
this.rooms.delete(roomId);
console.log(`Room ${roomId} cleanup completed`);
} catch (error) {
console.error(`Room cleanup failed for ${roomId}:`, error);
}
}
Connection Management
WebSocket Connection Handling
Copy
class RoomConnectionManager {
constructor(roomManager) {
this.roomManager = roomManager;
this.connections = new Map();
}
handleConnection(socket, request) {
const url = new URL(request.url, 'http://localhost');
const token = url.searchParams.get('token');
const roomId = url.searchParams.get('room');
if (!token || !roomId) {
socket.close(1008, 'Missing token or room ID');
return;
}
this.authenticateAndConnect(socket, roomId, token);
}
async authenticateAndConnect(socket, roomId, token) {
try {
// Connect player to room
const playerSession = await this.roomManager.handlePlayerConnection(
roomId, token, socket
);
// Store connection reference
this.connections.set(socket, {
roomId: roomId,
playerId: playerSession.playerId,
connectedAt: new Date()
});
// Set up socket event handlers
this.setupSocketHandlers(socket, roomId, playerSession.playerId);
} catch (error) {
console.error('Connection authentication failed:', error);
socket.close(1008, error.message);
}
}
setupSocketHandlers(socket, roomId, playerId) {
socket.on('message', async (data) => {
try {
const message = JSON.parse(data);
await this.handlePlayerMessage(roomId, playerId, message);
} catch (error) {
console.error('Message handling failed:', error);
}
});
socket.on('close', () => {
this.handleDisconnection(socket, roomId, playerId);
});
socket.on('error', (error) => {
console.error(`Socket error for player ${playerId}:`, error);
});
}
}
Player Reconnection
Copy
async handlePlayerReconnection(roomId, playerId, newSocket) {
const room = this.rooms.get(roomId);
const player = room?.players.get(playerId);
if (!player) {
throw new Error('Player session not found');
}
// Close old connection if exists
if (player.socket) {
player.socket.close(1000, 'Reconnected from new session');
}
// Update player session with new socket
player.socket = newSocket;
player.lastActivity = new Date();
player.status = 'reconnected';
// Send current room state
await this.sendRoomState(newSocket, room);
// Notify other players of reconnection
await this.broadcastPlayerReconnect(room, playerId);
console.log(`Player ${playerId} reconnected to room ${roomId}`);
}
Room Monitoring & Analytics
Room Health Monitoring
Copy
class RoomMonitor {
constructor(roomManager) {
this.roomManager = roomManager;
this.monitoringInterval = setInterval(() => {
this.monitorRooms();
}, 30000); // Monitor every 30 seconds
}
monitorRooms() {
for (const [roomId, room] of this.roomManager.rooms) {
this.checkRoomHealth(room);
}
}
checkRoomHealth(room) {
const now = new Date();
const inactiveThreshold = 5 * 60 * 1000; // 5 minutes
// Check for inactive rooms
if (now - room.lastActivity > inactiveThreshold) {
console.warn(`Room ${room.roomId} inactive for ${now - room.lastActivity}ms`);
if (room.currentPlayers === 0) {
// Clean up empty inactive rooms
this.roomManager.cleanupRoom(room.roomId, 'inactive');
}
}
// Check for disconnected players
for (const [playerId, player] of room.players) {
if (now - player.lastActivity > inactiveThreshold) {
console.warn(`Player ${playerId} inactive in room ${room.roomId}`);
// Handle player timeout
this.handlePlayerTimeout(room, playerId);
}
}
}
generateRoomMetrics(room) {
return {
roomId: room.roomId,
tournamentId: room.tournamentId,
status: room.status,
playerCount: room.currentPlayers,
maxPlayers: room.maxPlayers,
utilization: (room.currentPlayers / room.maxPlayers) * 100,
uptime: new Date() - room.createdAt,
lastActivity: room.lastActivity,
averageLatency: this.calculateAverageLatency(room),
messageCount: this.getMessageCount(room)
};
}
}
Performance Optimization
Copy
// Efficient room state broadcasting
async broadcastToRoom(room, message, excludePlayer = null) {
const promises = [];
for (const [playerId, player] of room.players) {
if (playerId === excludePlayer) continue;
if (player.socket.readyState === WebSocket.OPEN) {
promises.push(
new Promise((resolve, reject) => {
player.socket.send(JSON.stringify(message), (error) => {
if (error) reject(error);
else resolve();
});
})
);
}
}
// Wait for all messages to be sent
try {
await Promise.allSettled(promises);
} catch (error) {
console.error('Broadcast failed:', error);
}
}
// Memory-efficient player state updates
updatePlayerStates(room, updates) {
const batch = [];
for (const [playerId, update] of Object.entries(updates)) {
const player = room.players.get(playerId);
if (player) {
Object.assign(player.gameData, update);
batch.push({ playerId, ...update });
}
}
// Broadcast batched updates
this.broadcastToRoom(room, {
type: 'player_states_updated',
updates: batch
});
}

