Skip to main content

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:
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

{
  "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

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

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

StatusDescriptionActions Available
waitingRoom created, waiting for playersPlayer connections
activeTournament in progressGameplay, player management
pausedTemporarily pausedResume, player reconnection
completedTournament finishedResults, cleanup
cancelledTournament cancelledPlayer cleanup, refunds

Room Cleanup Process

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

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

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

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

// 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
  });
}

Next Steps