From 1392e5a6526bf277228ff9bb3a30bc9f6cd1604d Mon Sep 17 00:00:00 2001 From: josedario87 Date: Wed, 6 Aug 2025 02:58:29 -0600 Subject: [PATCH] fix: resolve room state synchronization and player display issues - Fix room.state.players undefined error on component mount - Wait for initial state sync before accessing room data - Move message handlers from service to Game component - Fix player count display in waiting screen (was showing 0/2) - Prevent lobby component from clearing game room on unmount - Separate leaveLobby() and leaveGame() methods for proper cleanup - Ensure player names persist when moving from lobby to game - Add proper error handling for state initialization --- client/src/services/colyseus.ts | 137 +++++++++++++++++++------------- client/src/views/Game.vue | 59 ++++++++++++-- client/src/views/Lobby.vue | 27 ++++++- server/src/rooms/GameRoom.ts | 14 ++-- server/src/rooms/LobbyRoom.ts | 48 ++++++----- 5 files changed, 192 insertions(+), 93 deletions(-) diff --git a/client/src/services/colyseus.ts b/client/src/services/colyseus.ts index ba78570..f1cf656 100644 --- a/client/src/services/colyseus.ts +++ b/client/src/services/colyseus.ts @@ -61,67 +61,68 @@ class ColyseusService { } } - async quickPlay(): Promise { - if (this.lobbyRoom.value) { - return new Promise((resolve, reject) => { - const room = this.lobbyRoom.value!; - - room.onMessage("roomReservation", async (reservation) => { - try { - await this.joinGameByReservation(reservation); - resolve(); - } catch (error) { - reject(error); - } - }); - - room.onMessage("error", (error) => { - reject(new Error(error.message)); - }); - - room.send("quickPlay"); - }); + async quickPlay(): Promise { + if (!this.lobbyRoom.value) { + throw new Error("Not in lobby"); } + + return new Promise((resolve, reject) => { + const room = this.lobbyRoom.value!; + console.log('Sending quickPlay message to lobby...'); + + const handleGameJoined = async (data: any) => { + console.log('Received gameJoined with roomId:', data.roomId); + try { + // Join the game room directly using the roomId + console.log('Joining game room with name:', this.playerName.value); + const gameRoom = await this.client.joinById(data.roomId, { + playerName: this.playerName.value + }); + + // Ensure the room id is set + if (!gameRoom.id) { + gameRoom.id = data.roomId; + } + + console.log('Successfully joined game room:', gameRoom.id, gameRoom); + console.log('Setting gameRoom.value...'); + this.gameRoom.value = gameRoom; + this.currentRoom = gameRoom; + console.log('gameRoom.value is now:', this.gameRoom.value); + + // Don't register message handlers here - let the Game component handle them + + resolve(gameRoom); + } catch (error) { + console.error('Error joining game room:', error); + reject(error); + } + }; + + const handleError = (error: any) => { + console.error('Received error from lobby:', error); + reject(new Error(error.message)); + }; + + room.onMessage("gameJoined", handleGameJoined); + room.onMessage("error", handleError); + + room.send("quickPlay"); + }); } - async joinGameRoom(roomId: string): Promise { - if (this.lobbyRoom.value) { - return new Promise((resolve, reject) => { - const room = this.lobbyRoom.value!; - - room.onMessage("roomReservation", async (reservation) => { - try { - await this.joinGameByReservation(reservation); - resolve(); - } catch (error) { - reject(error); - } - }); - - room.onMessage("error", (error) => { - reject(new Error(error.message)); - }); - - room.send("joinRoom", roomId); - }); - } - } - - private async joinGameByReservation(reservation: any): Promise { + async joinGameRoom(roomId: string): Promise { try { - const room = await this.client.consumeSeatReservation(reservation); - this.gameRoom.value = room; - this.currentRoom = room; - - room.onMessage("playerInfo", (data) => { - this.sessionId.value = data.sessionId; - this.playerName.value = data.name; + const gameRoom = await this.client.joinById(roomId, { + playerName: this.playerName.value }); - - if (this.lobbyRoom.value) { - this.lobbyRoom.value.leave(); - this.lobbyRoom.value = null; - } + + this.gameRoom.value = gameRoom; + this.currentRoom = gameRoom; + + // Don't register message handlers here - let the Game component handle them + + return gameRoom; } catch (error) { console.error("Failed to join game room:", error); throw error; @@ -134,7 +135,31 @@ class ColyseusService { } } + leaveLobby(): void { + console.log('leaveLobby called'); + if (this.lobbyRoom.value) { + this.lobbyRoom.value.leave(); + this.lobbyRoom.value = null; + if (this.currentRoom === this.lobbyRoom.value) { + this.currentRoom = null; + } + } + } + + leaveGame(): void { + console.log('leaveGame called'); + if (this.gameRoom.value) { + this.gameRoom.value.leave(); + this.gameRoom.value = null; + if (this.currentRoom === this.gameRoom.value) { + this.currentRoom = null; + } + } + } + leaveCurrentRoom(): void { + console.log('leaveCurrentRoom called - THIS SHOULD NOT BE USED'); + // This method is deprecated - use leaveLobby() or leaveGame() instead if (this.currentRoom) { this.currentRoom.leave(); this.currentRoom = null; diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index c0bfebe..ee75eed 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -37,6 +37,7 @@

Waiting for opponent...

+

Players in room: {{ players.length }}/2

Game will start when another player joins

@@ -90,15 +91,45 @@ const sessionId = computed(() => colyseusService.sessionId.value); let clickTimeout: NodeJS.Timeout; onMounted(() => { + console.log('Game component mounted'); + console.log('colyseusService.gameRoom:', colyseusService.gameRoom); + console.log('colyseusService.gameRoom.value:', colyseusService.gameRoom.value); const room = colyseusService.gameRoom.value; + console.log('Current game room:', room); if (!room) { + console.error('No game room found, redirecting to lobby...'); router.push('/'); return; } + + console.log('Setting up game room listeners...'); const $ = getStateCallbacks(room); + // Wait for the initial state sync + room.onStateChange.once((state) => { + console.log('Initial state received:', state); + gameStatus.value = state.gameStatus || 'waiting'; + timeRemaining.value = state.timeRemaining || 600; + winner.value = state.winner || ''; + + // Load existing players if they exist + if (state.players) { + console.log('Players map exists in initial state, loading players...'); + state.players.forEach((player: any, sessionId: string) => { + console.log('Loading initial player:', player); + players.value.push({ + sessionId: player.sessionId, + name: player.name, + clicks: player.clicks, + connected: player.connected + }); + }); + } + }); + + // Listen for future changes $(room.state).listen("gameStatus", (value: string) => { gameStatus.value = value; }); @@ -112,12 +143,16 @@ onMounted(() => { }); $(room.state).players.onAdd((player: any) => { - players.value.push({ - sessionId: player.sessionId, - name: player.name, - clicks: player.clicks, - connected: player.connected - }); + // Check if player already exists before adding + const exists = players.value.find(p => p.sessionId === player.sessionId); + if (!exists) { + players.value.push({ + sessionId: player.sessionId, + name: player.name, + clicks: player.clicks, + connected: player.connected + }); + } $(player).listen("clicks", (value: number) => { const p = players.value.find(p => p.sessionId === player.sessionId); @@ -137,21 +172,31 @@ onMounted(() => { } }); + room.onMessage("playerInfo", (info) => { + console.log('Received playerInfo:', info); + colyseusService.sessionId.value = info.sessionId; + colyseusService.playerName.value = info.name; + }); + room.onMessage("gameStart", () => { console.log("Game started!"); + gameStatus.value = 'playing'; }); room.onMessage("gameEnd", (data) => { console.log("Game ended!", data); + gameStatus.value = 'finished'; }); room.onMessage("gamePaused", () => { console.log("Game paused"); + gameStatus.value = 'paused'; }); room.onMessage("gameRestart", () => { console.log("Game restarting"); players.value.forEach(p => p.clicks = 0); + gameStatus.value = 'waiting'; }); }); @@ -180,7 +225,7 @@ function formatTime(seconds: number): string { } function leaveGame() { - colyseusService.leaveCurrentRoom(); + colyseusService.leaveGame(); router.push('/'); } diff --git a/client/src/views/Lobby.vue b/client/src/views/Lobby.vue index df0e15b..1827b8c 100644 --- a/client/src/views/Lobby.vue +++ b/client/src/views/Lobby.vue @@ -131,7 +131,8 @@ onMounted(async () => { }); onUnmounted(() => { - colyseusService.leaveCurrentRoom(); + // Don't leave the current room - it might be a game room now + console.log('Lobby component unmounting...'); }); async function updateName() { @@ -143,9 +144,21 @@ async function updateName() { async function handleQuickPlay() { isJoining.value = true; + console.log('Starting quickPlay...'); try { - await colyseusService.quickPlay(); - router.push('/game'); + const gameRoom = await colyseusService.quickPlay(); + console.log('Game room joined:', gameRoom?.id, 'Full room object:', gameRoom); + + // Leave the lobby room before navigating + if (colyseusService.lobbyRoom.value) { + console.log('Leaving lobby room...'); + colyseusService.lobbyRoom.value.leave(); + colyseusService.lobbyRoom.value = null; + } + + console.log('Navigating to /game...'); + await router.push('/game'); + console.log('Navigation complete'); } catch (error) { console.error('Failed to join game:', error); isJoining.value = false; @@ -155,7 +168,13 @@ async function handleQuickPlay() { async function joinRoom(roomId: string) { isJoining.value = true; try { - await colyseusService.joinGameRoom(roomId); + // For direct room joining, we can use joinGameRoom directly + const gameRoom = await colyseusService.joinGameRoom(roomId); + // Leave the lobby room before navigating + if (colyseusService.lobbyRoom.value) { + colyseusService.lobbyRoom.value.leave(); + colyseusService.lobbyRoom.value = null; + } router.push('/game'); } catch (error) { console.error('Failed to join room:', error); diff --git a/server/src/rooms/GameRoom.ts b/server/src/rooms/GameRoom.ts index 0080f4d..d58b003 100644 --- a/server/src/rooms/GameRoom.ts +++ b/server/src/rooms/GameRoom.ts @@ -34,16 +34,16 @@ export class GameRoom extends Room { } onJoin(client: Client, options: any) { - console.log(`[GameRoom] ${client.sessionId} joined room ${this.roomId}`); + console.log(`[GameRoom] ${client.sessionId} joined room ${this.roomId} with name: ${options.playerName}`); + // Use the playerName passed from the lobby - don't generate a new one! const playerName = options.playerName || "player"; - const uniqueName = NameManager.getInstance().generateUniquePlayerName(playerName, client.sessionId); - this.state.addPlayer(client.sessionId, uniqueName); + this.state.addPlayer(client.sessionId, playerName); client.send("playerInfo", { sessionId: client.sessionId, - name: uniqueName, + name: playerName, roomId: this.roomId }); @@ -58,7 +58,7 @@ export class GameRoom extends Room { const player = this.state.players.get(client.sessionId); if (player) { player.connected = false; - NameManager.getInstance().releasePlayerName(client.sessionId); + // Don't release the name here - it's managed by the LobbyRoom } if (this.state.gameStatus === GameStatus.PLAYING) { @@ -90,9 +90,7 @@ export class GameRoom extends Room { clearInterval(this.gameInterval); } - this.state.players.forEach(player => { - NameManager.getInstance().releasePlayerName(player.sessionId); - }); + // Don't release names here - they're managed by the LobbyRoom } private startGame() { diff --git a/server/src/rooms/LobbyRoom.ts b/server/src/rooms/LobbyRoom.ts index fcfae4b..7449780 100644 --- a/server/src/rooms/LobbyRoom.ts +++ b/server/src/rooms/LobbyRoom.ts @@ -85,20 +85,30 @@ export class LobbyRoom extends Room { if (!player || player.inGame) return; try { - const reservation = await matchMaker.joinOrCreate("game", { - playerName: player.name - }); + // First try to find an available room + const rooms = await matchMaker.query({ name: "game", locked: false }); + let targetRoomId: string; + + // Find a room with less than 2 players + const availableRoom = rooms.find(room => room.clients < 2); + + if (availableRoom) { + targetRoomId = availableRoom.roomId; + } else { + // If no room available, create a new one + const newRoom = await matchMaker.createRoom("game", {}); + targetRoomId = newRoom.roomId; + } this.state.setPlayerInGame(client.sessionId, true); - client.send("roomReservation", { - sessionId: reservation.sessionId, - room: reservation.room + // Send the roomId to the client + client.send("gameJoined", { + roomId: targetRoomId }); - setTimeout(() => { - client.leave(); - }, 1000); + // Don't auto-leave, let the client handle it + // The client will leave the lobby after successfully joining the game room } catch (error) { console.error("[LobbyRoom] Error in quick play:", error); @@ -113,20 +123,22 @@ export class LobbyRoom extends Room { if (!player || player.inGame) return; try { - const reservation = await matchMaker.joinById(roomId, { - playerName: player.name - }); + // Verify the room exists and is available + const rooms = await matchMaker.query({ roomId }); + + if (rooms.length === 0 || rooms[0].clients >= 2) { + throw new Error("Room not available"); + } this.state.setPlayerInGame(client.sessionId, true); - client.send("roomReservation", { - sessionId: reservation.sessionId, - room: reservation.room + // Send the roomId to the client + client.send("gameJoined", { + roomId: roomId }); - setTimeout(() => { - client.leave(); - }, 1000); + // Don't auto-leave, let the client handle it + // The client will leave the lobby after successfully joining the game room } catch (error) { console.error("[LobbyRoom] Error joining room:", error);