shuffle players implementado correctamente

This commit is contained in:
2025-08-15 20:03:26 -06:00
parent 9b84008f19
commit f214174bab
4 changed files with 234 additions and 40 deletions

View File

@@ -68,6 +68,7 @@ class ColyseusService {
} else {
// No client-side persistence; user must confirm name once per session
}
// Resume request is now triggered by Lobby.vue after listeners are attached
});
room.onMessage("nameUpdated", (data) => {
@@ -186,6 +187,37 @@ class ColyseusService {
}
}
async joinShuffledGameRoom(roomId: string, role: string, playerName: string, playerColor: string): Promise<Room> {
try {
const uuid = this.getUuidFromPath();
console.log(`[Colyseus] Joining shuffled room ${roomId} as ${role} with UUID ${uuid}`);
// Update local values
this.playerName.value = playerName;
this.playerColor.value = playerColor;
const gameRoom = await this.client.joinById(roomId, {
playerName,
playerColor,
uuid,
isShuffleJoin: true,
role
});
try { this.sessionId.value = (gameRoom as any).sessionId || this.sessionId.value; } catch {}
this.gameRoom.value = gameRoom;
this.currentRoom = gameRoom;
// Register reconnection token on server for this UUID
try { (gameRoom as any).send("registerReconnection", (gameRoom as any).reconnectionToken || ""); } catch {}
return gameRoom;
} catch (error) {
console.error("Failed to join shuffled game room:", error);
throw error;
}
}
sendClick(): void {
if (this.gameRoom.value) {
this.gameRoom.value.send("click");

View File

@@ -137,19 +137,72 @@ onMounted(async () => {
// Listen for server-initiated resume to existing game (fallback joinById)
room.onMessage("resumeGame", async (data: any) => {
if (guardResume()) return;
try {
const gameRoom = await colyseusService.joinGameRoom(data.roomId);
// Leave lobby before navigating
if (colyseusService.lobbyRoom.value) {
colyseusService.lobbyRoom.value.leave();
colyseusService.lobbyRoom.value = null;
const tryJoin = async (attempt = 1): Promise<void> => {
try {
const gameRoom = await colyseusService.joinGameRoom(data.roomId);
if (colyseusService.lobbyRoom.value) {
colyseusService.lobbyRoom.value.leave();
colyseusService.lobbyRoom.value = null;
}
await router.push(`/${routeUuid.value}/demo`);
} catch (error: any) {
const msg = String(error?.message || error);
if (attempt < 3 && (msg.includes('locked') || msg.includes('full'))) {
setTimeout(() => tryJoin(attempt + 1), 300);
return;
}
console.error('Auto-join failed:', error);
// allow future resume if this one failed entirely
resumed = false;
}
await router.push(`/${routeUuid.value}/demo`);
} catch (error) {
console.error('Auto-join failed:', error);
}
};
await tryJoin(1);
});
// Listen for shuffle redirect with complete player information
room.onMessage("shuffleRedirect", async (data: any) => {
if (guardResume()) return;
console.log('[Lobby] Received shuffle redirect:', data);
// Update player info before joining
if (data.playerName) {
colyseusService.playerName.value = data.playerName;
}
if (data.playerColor) {
colyseusService.playerColor.value = data.playerColor;
}
const tryJoin = async (attempt = 1): Promise<void> => {
try {
// Join with shuffle flag to bypass normal restrictions
const gameRoom = await colyseusService.joinShuffledGameRoom(
data.roomId,
data.role,
data.playerName,
data.playerColor
);
if (colyseusService.lobbyRoom.value) {
colyseusService.lobbyRoom.value.leave();
colyseusService.lobbyRoom.value = null;
}
await router.push(`/${routeUuid.value}/demo`);
} catch (error: any) {
const msg = String(error?.message || error);
if (attempt < 3) {
setTimeout(() => tryJoin(attempt + 1), 500);
return;
}
console.error('Shuffle join failed:', error);
resumed = false;
}
};
await tryJoin(1);
});
// After listeners are attached, ask server if we should resume (shuffle/currentRoom)
try { room.send("resumeMe"); } catch {}
// Keep color input synced with server-updated color
watch(() => colyseusService.playerColor.value, (c) => {
if (c && c !== colorInput.value) colorInput.value = c;

View File

@@ -6,6 +6,26 @@ import { broadcastDashboardUpdate } from "../adminApi";
export class GameRoom extends Room<GameState> {
maxClients = 2;
getAvailableData() {
// If waiting for shuffled players, report as available regardless of current client count
if (this.isWaitingForShuffledPlayers) {
return {
clients: 0,
maxClients: this.maxClients,
metadata: this.metadata
};
}
return super.getAvailableData();
}
hasReachedMaxClients() {
// If waiting for shuffled players, allow up to 2 new clients regardless of current count
if (this.isWaitingForShuffledPlayers) {
return false;
}
return super.hasReachedMaxClients();
}
private gameInterval?: NodeJS.Timeout;
private recentSystemMessage: { text: string; kind: string; timestamp: number } | null = null;
@@ -242,7 +262,7 @@ export class GameRoom extends Room<GameState> {
}
onJoin(client: Client, options: any) {
console.log(`[GameRoom] ${client.sessionId} joined room ${this.roomId} with name: ${options.playerName}`);
console.log(`[GameRoom] ${client.sessionId} joined room ${this.roomId} with name: ${options.playerName}, isShuffleJoin: ${options.isShuffleJoin}, isWaitingForShuffledPlayers: ${this.isWaitingForShuffledPlayers}, playersCount: ${this.state.players.size}`);
const uuid = options?.uuid as string | undefined;
// UUID-based reconnection: if game already started or room is full, allow join if UUID matches a participant
@@ -283,12 +303,13 @@ export class GameRoom extends Room<GameState> {
}
// Special handling for shuffled players
if (this.isWaitingForShuffledPlayers && options.uuid) {
if ((this.isWaitingForShuffledPlayers || options.isShuffleJoin) && options.uuid) {
return this.handleShuffledPlayerJoin(client, options);
}
// Prevent new joins if game already started or two players are registered
if (this.state.gameStatus !== GameStatus.WAITING || this.state.players.size >= 2) {
// BUT allow if waiting for shuffled players (they should go through shuffle handler above)
if ((this.state.gameStatus !== GameStatus.WAITING || this.state.players.size >= 2) && !this.isWaitingForShuffledPlayers) {
try { client.leave(1000); } catch {}
return;
}
@@ -685,10 +706,18 @@ export class GameRoom extends Room<GameState> {
}
});
// Clear all players from state completely
this.state.players.clear();
this.state.p1Id = "";
this.state.p2Id = "";
this.sessionToUuid.clear();
// Reset room state but keep variant
const currentVariant = this.state.currentVariant;
this.state.restartGame();
this.state.currentVariant = currentVariant;
// Ensure room is accepting new joins after shuffle
try { (this as any).unlock?.(); } catch {}
// Prepare for new players
this.isWaitingForShuffledPlayers = true;
@@ -704,25 +733,54 @@ export class GameRoom extends Room<GameState> {
this.expectedShuffledPlayers = assignments;
this.isWaitingForShuffledPlayers = true;
// Update metadata
// Update metadata to reflect that room is waiting for shuffled players
this.setMetadata({
gameStatus: 'waiting',
currentRound: 1,
currentVariant: this.state.currentVariant
currentVariant: this.state.currentVariant,
playersCount: 0, // Force reset player count
maxClients: 2
});
// Make sure the room is unlocked and can accept new connections
try {
(this as any).unlock?.();
// Force Colyseus to recalculate available slots
(this as any).maxClients = 2;
} catch {}
}
private handleShuffledPlayerJoin(client: Client, options: any) {
const uuid = options.uuid;
console.log(`[GameRoom] Shuffled player ${uuid} trying to join room ${this.roomId}`);
console.log(`[GameRoom] Shuffled player ${uuid} trying to join room ${this.roomId} with options:`, options);
console.log(`[GameRoom] Room state - isWaitingForShuffledPlayers: ${this.isWaitingForShuffledPlayers}, playersCount: ${this.state.players.size}, expectedPlayers:`, this.expectedShuffledPlayers);
// Check if this player is expected in this room
let expectedRole: 'P1' | 'P2' | null = null;
let expectedRole: 'P1' | 'P2' | null = options.role || null;
if (this.expectedShuffledPlayers.p1?.uuid === uuid) {
expectedRole = 'P1';
} else if (this.expectedShuffledPlayers.p2?.uuid === uuid) {
expectedRole = 'P2';
// If role not provided, determine from expected players
if (!expectedRole) {
if (this.expectedShuffledPlayers.p1?.uuid === uuid) {
expectedRole = 'P1';
} else if (this.expectedShuffledPlayers.p2?.uuid === uuid) {
expectedRole = 'P2';
}
}
if (!expectedRole) {
// Fallback: consult NameManager assignment if not set yet in room
try {
const { NameManager } = require("../utils/nameManager");
const assign = NameManager.getInstance().getPlayerRoomAssignment(uuid);
if (assign && assign.roomId === this.roomId) {
expectedRole = assign.role;
if (assign.role === 'P1' && !this.expectedShuffledPlayers.p1) {
this.expectedShuffledPlayers.p1 = { uuid, name: options.playerName || 'player', color: options.playerColor };
} else if (assign.role === 'P2' && !this.expectedShuffledPlayers.p2) {
this.expectedShuffledPlayers.p2 = { uuid, name: options.playerName || 'player', color: options.playerColor };
}
}
} catch {}
}
if (!expectedRole) {
@@ -731,15 +789,19 @@ export class GameRoom extends Room<GameState> {
return;
}
// Get player info from expected data
// Get player info from expected data or use provided options as fallback
const expectedPlayerData = expectedRole === 'P1' ? this.expectedShuffledPlayers.p1 : this.expectedShuffledPlayers.p2;
const playerName = expectedPlayerData?.name || options.playerName || 'player';
const playerColor = expectedPlayerData?.color || options.playerColor || "#667eea";
console.log(`[GameRoom] Adding shuffled player ${uuid} as ${expectedRole} in room ${this.roomId}`);
// Add player with the expected role
const player = this.state.addPlayer(client.sessionId, expectedPlayerData.name);
const player = this.state.addPlayer(client.sessionId, playerName);
player.role = expectedRole;
player.color = expectedPlayerData.color || "#667eea";
player.color = playerColor;
(player as any).uuid = uuid;
this.sessionToUuid.set(client.sessionId, uuid);
// Set role IDs
if (expectedRole === 'P1') {
@@ -750,12 +812,16 @@ export class GameRoom extends Room<GameState> {
client.send("playerInfo", {
sessionId: client.sessionId,
name: expectedPlayerData.name,
name: playerName,
roomId: this.roomId
});
// Remove from NameManager assignment once joined
NameManager.getInstance().removePlayerRoomAssignment(uuid);
// Update mappings in NameManager
try {
const { NameManager } = require("../utils/nameManager");
NameManager.getInstance().setCurrentRoom(uuid, this.roomId);
NameManager.getInstance().removePlayerRoomAssignment(uuid);
} catch {}
// Check if room is complete
if (this.state.players.size === 2) {

View File

@@ -44,6 +44,52 @@ export class LobbyRoom extends Room<LobbyState> {
this.handleJoinRoom(client, roomId);
});
// Client asks server to suggest a resume target (after listeners are ready)
this.onMessage("resumeMe", async (client) => {
const uuid = this.sessionToUuid.get(client.sessionId);
if (!uuid) return;
// If shuffle in progress and an assignment exists, prefer it
if (NameManager.getInstance().isShuffleInProgress()) {
const assignment = NameManager.getInstance().getPlayerRoomAssignment(uuid);
if (assignment) {
try {
const delay = assignment.role === 'P1' ? 150 : 750; // stagger joins to avoid full race
const playerName = NameManager.getInstance().getPlayerName(uuid) || "";
const playerColor = NameManager.getInstance().getPlayerColor(uuid) || "#667eea";
setTimeout(() => {
try {
client.send("shuffleRedirect", {
roomId: assignment.roomId,
role: assignment.role,
playerName,
playerColor,
isShuffleJoin: true
});
} catch {}
}, delay);
} catch {}
return;
}
}
// Otherwise, try current room mapping
try {
const currentRoomId = NameManager.getInstance().getCurrentRoom(uuid);
if (currentRoomId) {
const rooms = await matchMaker.query({ roomId: currentRoomId });
const room = rooms[0];
const status = room?.metadata?.gameStatus || "waiting";
if (room && status !== "finished") {
const token = NameManager.getInstance().getReconnectToken(uuid);
if (token) { client.send("resumeReconnection", { token }); }
else { client.send("resumeGame", { roomId: currentRoomId }); }
}
}
} catch {}
});
this.updateInterval = setInterval(() => {
this.updateAvailableRooms();
}, 2000);
@@ -88,11 +134,7 @@ export class LobbyRoom extends Room<LobbyState> {
name: existingName || "",
color: this.state.players.get(client.sessionId)?.color || "#667eea"
});
// Then immediately redirect to assigned room
setTimeout(() => {
this.handleJoinRoom(client, assignment.roomId);
}, 500);
// Do not push immediate redirect here; client will send "resumeMe" after handlers are ready
return;
}
@@ -237,7 +279,7 @@ export class LobbyRoom extends Room<LobbyState> {
}
}
private async handleJoinRoom(client: Client, roomId: string) {
private async handleJoinRoom(client: Client, roomId: string, opts?: { force?: boolean }) {
const player = this.state.players.get(client.sessionId);
if (!player || player.inGame) return;
if (!player.name || !player.name.trim()) {
@@ -246,12 +288,13 @@ export class LobbyRoom extends Room<LobbyState> {
}
try {
// Verify the room exists and is available
const rooms = await matchMaker.query({ roomId });
const status = rooms[0]?.metadata?.gameStatus || "waiting";
if (rooms.length === 0 || rooms[0].clients >= 2 || status !== "waiting") {
throw new Error("Room not available");
if (!opts?.force) {
// Verify the room exists and is available
const rooms = await matchMaker.query({ roomId });
const status = rooms[0]?.metadata?.gameStatus || "waiting";
if (rooms.length === 0 || rooms[0].clients >= 2 || status !== "waiting") {
throw new Error("Room not available");
}
}
this.state.setPlayerInGame(client.sessionId, true);