implemented game server sync
This commit is contained in:
@@ -25,11 +25,17 @@ interface Room {
|
||||
status: 'waiting' | 'drafting' | 'deck_building' | 'playing' | 'finished';
|
||||
messages: ChatMessage[];
|
||||
maxPlayers: number;
|
||||
lastActive: number; // For persistence cleanup
|
||||
}
|
||||
|
||||
export class RoomManager {
|
||||
private rooms: Map<string, Room> = new Map();
|
||||
|
||||
constructor() {
|
||||
// Cleanup job: Check every 5 minutes
|
||||
setInterval(() => this.cleanupRooms(), 5 * 60 * 1000);
|
||||
}
|
||||
|
||||
createRoom(hostId: string, hostName: string, packs: any[], basicLands: any[] = [], socketId?: string): Room {
|
||||
const roomId = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
const room: Room = {
|
||||
@@ -40,7 +46,8 @@ export class RoomManager {
|
||||
basicLands,
|
||||
status: 'waiting',
|
||||
messages: [],
|
||||
maxPlayers: 8
|
||||
maxPlayers: hostId.startsWith('SOLO_') ? 1 : 8, // Little hack for solo testing, though 8 is fine
|
||||
lastActive: Date.now()
|
||||
};
|
||||
this.rooms.set(roomId, room);
|
||||
return room;
|
||||
@@ -50,6 +57,7 @@ export class RoomManager {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) return null;
|
||||
|
||||
room.lastActive = Date.now();
|
||||
const player = room.players.find(p => p.id === playerId);
|
||||
if (player) {
|
||||
player.ready = true;
|
||||
@@ -62,6 +70,8 @@ export class RoomManager {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) return null;
|
||||
|
||||
room.lastActive = Date.now();
|
||||
|
||||
// Rejoin if already exists
|
||||
const existingPlayer = room.players.find(p => p.id === playerId);
|
||||
if (existingPlayer) {
|
||||
@@ -83,6 +93,9 @@ export class RoomManager {
|
||||
updatePlayerSocket(roomId: string, playerId: string, socketId: string): Room | null {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) return null;
|
||||
|
||||
room.lastActive = Date.now();
|
||||
|
||||
const player = room.players.find(p => p.id === playerId);
|
||||
if (player) {
|
||||
player.socketId = socketId;
|
||||
@@ -97,6 +110,12 @@ export class RoomManager {
|
||||
const player = room.players.find(p => p.socketId === socketId);
|
||||
if (player) {
|
||||
player.isOffline = true;
|
||||
// Do NOT update lastActive on disconnect, or maybe we should?
|
||||
// No, lastActive is for "when was the room last used?". Disconnect is an event, but inactivity starts from here.
|
||||
// So keeping lastActive as previous interaction time is safer?
|
||||
// Actually, if everyone disconnects now, room should be kept for 8 hours from NOW.
|
||||
// So update lastActive.
|
||||
room.lastActive = Date.now();
|
||||
return { room, playerId: player.id };
|
||||
}
|
||||
}
|
||||
@@ -107,29 +126,30 @@ export class RoomManager {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) return null;
|
||||
|
||||
room.lastActive = Date.now();
|
||||
|
||||
// Logic change: Explicit leave only removes player from list if waiting.
|
||||
// If playing, mark offline (abandon).
|
||||
// NEVER DELETE ROOM HERE. Rely on cleanup.
|
||||
|
||||
if (room.status === 'waiting') {
|
||||
// Normal logic: Remove player completely
|
||||
room.players = room.players.filter(p => p.id !== playerId);
|
||||
|
||||
// If host leaves, assign new host from remaining players
|
||||
if (room.players.length === 0) {
|
||||
this.rooms.delete(roomId);
|
||||
return null;
|
||||
} else if (room.hostId === playerId) {
|
||||
if (room.players.length > 0 && room.hostId === playerId) {
|
||||
const nextPlayer = room.players.find(p => p.role === 'player') || room.players[0];
|
||||
if (nextPlayer) {
|
||||
room.hostId = nextPlayer.id;
|
||||
nextPlayer.isHost = true;
|
||||
}
|
||||
}
|
||||
// If 0 players, room remains in Map until cleanup
|
||||
} else {
|
||||
// Game in progress (Drafting/Playing)
|
||||
// DO NOT REMOVE PLAYER. Just mark offline.
|
||||
// This allows them to rejoin and reclaim their seat (and deck).
|
||||
const player = room.players.find(p => p.id === playerId);
|
||||
if (player) {
|
||||
player.isOffline = true;
|
||||
// Note: socketId is already handled by disconnect event usually, but if explicit leave, we should clear it?
|
||||
player.socketId = undefined;
|
||||
}
|
||||
console.log(`Player ${playerId} left active game in room ${roomId}. Marked as offline.`);
|
||||
@@ -141,26 +161,30 @@ export class RoomManager {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) return null;
|
||||
room.status = 'drafting';
|
||||
room.lastActive = Date.now();
|
||||
return room;
|
||||
}
|
||||
|
||||
getRoom(roomId: string): Room | undefined {
|
||||
// Refresh activity if accessed? Not necessarily, only write actions.
|
||||
// But rejoining calls getRoom implicitly in join logic or index logic?
|
||||
// Let's assume write actions update lastActive.
|
||||
return this.rooms.get(roomId);
|
||||
}
|
||||
|
||||
kickPlayer(roomId: string, playerId: string): Room | null {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) return null;
|
||||
room.lastActive = Date.now();
|
||||
|
||||
room.players = room.players.filter(p => p.id !== playerId);
|
||||
|
||||
// If game was running, we might need more cleanup, but for now just removal.
|
||||
return room;
|
||||
}
|
||||
|
||||
addMessage(roomId: string, sender: string, text: string): ChatMessage | null {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) return null;
|
||||
room.lastActive = Date.now();
|
||||
|
||||
const message: ChatMessage = {
|
||||
id: Math.random().toString(36).substring(7),
|
||||
@@ -173,7 +197,6 @@ export class RoomManager {
|
||||
}
|
||||
|
||||
getPlayerBySocket(socketId: string): { player: Player, room: Room } | null {
|
||||
// Inefficient linear search, but robust for now. Maps would be better for high scale.
|
||||
for (const room of this.rooms.values()) {
|
||||
const player = room.players.find(p => p.socketId === socketId);
|
||||
if (player) {
|
||||
@@ -182,4 +205,26 @@ export class RoomManager {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private cleanupRooms() {
|
||||
const now = Date.now();
|
||||
const EXPIRATION_MS = 8 * 60 * 60 * 1000; // 8 Hours
|
||||
|
||||
for (const [roomId, room] of this.rooms.entries()) {
|
||||
// Logic:
|
||||
// 1. If players are online, room is active. -> Don't delete.
|
||||
// 2. If NO players are online (all offline or empty), check lastActive.
|
||||
|
||||
const anyOnline = room.players.some(p => !p.isOffline);
|
||||
if (anyOnline) {
|
||||
continue; // Active
|
||||
}
|
||||
|
||||
// No one online. Check expiration.
|
||||
if (now - room.lastActive > EXPIRATION_MS) {
|
||||
console.log(`Cleaning up expired room ${roomId}. Inactive for > 8 hours.`);
|
||||
this.rooms.delete(roomId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user