implemented game server sync

This commit is contained in:
2025-12-18 17:24:07 +01:00
parent e31323859f
commit a2a45a995c
15 changed files with 708 additions and 146 deletions

View File

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