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
This commit is contained in:
2025-08-06 02:58:29 -06:00
parent a28bc286a1
commit 1392e5a652
5 changed files with 192 additions and 93 deletions

View File

@@ -61,67 +61,68 @@ class ColyseusService {
} }
} }
async quickPlay(): Promise<void> { async quickPlay(): Promise<Room> {
if (this.lobbyRoom.value) { if (!this.lobbyRoom.value) {
return new Promise((resolve, reject) => { throw new Error("Not in lobby");
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");
});
} }
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<void> { async joinGameRoom(roomId: string): Promise<Room> {
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<void> {
try { try {
const room = await this.client.consumeSeatReservation(reservation); const gameRoom = await this.client.joinById(roomId, {
this.gameRoom.value = room; playerName: this.playerName.value
this.currentRoom = room;
room.onMessage("playerInfo", (data) => {
this.sessionId.value = data.sessionId;
this.playerName.value = data.name;
}); });
if (this.lobbyRoom.value) { this.gameRoom.value = gameRoom;
this.lobbyRoom.value.leave(); this.currentRoom = gameRoom;
this.lobbyRoom.value = null;
} // Don't register message handlers here - let the Game component handle them
return gameRoom;
} catch (error) { } catch (error) {
console.error("Failed to join game room:", error); console.error("Failed to join game room:", error);
throw 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 { leaveCurrentRoom(): void {
console.log('leaveCurrentRoom called - THIS SHOULD NOT BE USED');
// This method is deprecated - use leaveLobby() or leaveGame() instead
if (this.currentRoom) { if (this.currentRoom) {
this.currentRoom.leave(); this.currentRoom.leave();
this.currentRoom = null; this.currentRoom = null;

View File

@@ -37,6 +37,7 @@
<div class="waiting-message"> <div class="waiting-message">
<div class="spinner"></div> <div class="spinner"></div>
<h2>Waiting for opponent...</h2> <h2>Waiting for opponent...</h2>
<p>Players in room: {{ players.length }}/2</p>
<p>Game will start when another player joins</p> <p>Game will start when another player joins</p>
</div> </div>
</div> </div>
@@ -90,15 +91,45 @@ const sessionId = computed(() => colyseusService.sessionId.value);
let clickTimeout: NodeJS.Timeout; let clickTimeout: NodeJS.Timeout;
onMounted(() => { 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; const room = colyseusService.gameRoom.value;
console.log('Current game room:', room);
if (!room) { if (!room) {
console.error('No game room found, redirecting to lobby...');
router.push('/'); router.push('/');
return; return;
} }
console.log('Setting up game room listeners...');
const $ = getStateCallbacks(room); 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) => { $(room.state).listen("gameStatus", (value: string) => {
gameStatus.value = value; gameStatus.value = value;
}); });
@@ -112,12 +143,16 @@ onMounted(() => {
}); });
$(room.state).players.onAdd((player: any) => { $(room.state).players.onAdd((player: any) => {
players.value.push({ // Check if player already exists before adding
sessionId: player.sessionId, const exists = players.value.find(p => p.sessionId === player.sessionId);
name: player.name, if (!exists) {
clicks: player.clicks, players.value.push({
connected: player.connected sessionId: player.sessionId,
}); name: player.name,
clicks: player.clicks,
connected: player.connected
});
}
$(player).listen("clicks", (value: number) => { $(player).listen("clicks", (value: number) => {
const p = players.value.find(p => p.sessionId === player.sessionId); 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", () => { room.onMessage("gameStart", () => {
console.log("Game started!"); console.log("Game started!");
gameStatus.value = 'playing';
}); });
room.onMessage("gameEnd", (data) => { room.onMessage("gameEnd", (data) => {
console.log("Game ended!", data); console.log("Game ended!", data);
gameStatus.value = 'finished';
}); });
room.onMessage("gamePaused", () => { room.onMessage("gamePaused", () => {
console.log("Game paused"); console.log("Game paused");
gameStatus.value = 'paused';
}); });
room.onMessage("gameRestart", () => { room.onMessage("gameRestart", () => {
console.log("Game restarting"); console.log("Game restarting");
players.value.forEach(p => p.clicks = 0); players.value.forEach(p => p.clicks = 0);
gameStatus.value = 'waiting';
}); });
}); });
@@ -180,7 +225,7 @@ function formatTime(seconds: number): string {
} }
function leaveGame() { function leaveGame() {
colyseusService.leaveCurrentRoom(); colyseusService.leaveGame();
router.push('/'); router.push('/');
} }
</script> </script>

View File

@@ -131,7 +131,8 @@ onMounted(async () => {
}); });
onUnmounted(() => { onUnmounted(() => {
colyseusService.leaveCurrentRoom(); // Don't leave the current room - it might be a game room now
console.log('Lobby component unmounting...');
}); });
async function updateName() { async function updateName() {
@@ -143,9 +144,21 @@ async function updateName() {
async function handleQuickPlay() { async function handleQuickPlay() {
isJoining.value = true; isJoining.value = true;
console.log('Starting quickPlay...');
try { try {
await colyseusService.quickPlay(); const gameRoom = await colyseusService.quickPlay();
router.push('/game'); 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) { } catch (error) {
console.error('Failed to join game:', error); console.error('Failed to join game:', error);
isJoining.value = false; isJoining.value = false;
@@ -155,7 +168,13 @@ async function handleQuickPlay() {
async function joinRoom(roomId: string) { async function joinRoom(roomId: string) {
isJoining.value = true; isJoining.value = true;
try { 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'); router.push('/game');
} catch (error) { } catch (error) {
console.error('Failed to join room:', error); console.error('Failed to join room:', error);

View File

@@ -34,16 +34,16 @@ export class GameRoom extends Room<GameState> {
} }
onJoin(client: Client, options: any) { 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 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", { client.send("playerInfo", {
sessionId: client.sessionId, sessionId: client.sessionId,
name: uniqueName, name: playerName,
roomId: this.roomId roomId: this.roomId
}); });
@@ -58,7 +58,7 @@ export class GameRoom extends Room<GameState> {
const player = this.state.players.get(client.sessionId); const player = this.state.players.get(client.sessionId);
if (player) { if (player) {
player.connected = false; 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) { if (this.state.gameStatus === GameStatus.PLAYING) {
@@ -90,9 +90,7 @@ export class GameRoom extends Room<GameState> {
clearInterval(this.gameInterval); clearInterval(this.gameInterval);
} }
this.state.players.forEach(player => { // Don't release names here - they're managed by the LobbyRoom
NameManager.getInstance().releasePlayerName(player.sessionId);
});
} }
private startGame() { private startGame() {

View File

@@ -85,20 +85,30 @@ export class LobbyRoom extends Room<LobbyState> {
if (!player || player.inGame) return; if (!player || player.inGame) return;
try { try {
const reservation = await matchMaker.joinOrCreate("game", { // First try to find an available room
playerName: player.name 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); this.state.setPlayerInGame(client.sessionId, true);
client.send("roomReservation", { // Send the roomId to the client
sessionId: reservation.sessionId, client.send("gameJoined", {
room: reservation.room roomId: targetRoomId
}); });
setTimeout(() => { // Don't auto-leave, let the client handle it
client.leave(); // The client will leave the lobby after successfully joining the game room
}, 1000);
} catch (error) { } catch (error) {
console.error("[LobbyRoom] Error in quick play:", error); console.error("[LobbyRoom] Error in quick play:", error);
@@ -113,20 +123,22 @@ export class LobbyRoom extends Room<LobbyState> {
if (!player || player.inGame) return; if (!player || player.inGame) return;
try { try {
const reservation = await matchMaker.joinById(roomId, { // Verify the room exists and is available
playerName: player.name 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); this.state.setPlayerInGame(client.sessionId, true);
client.send("roomReservation", { // Send the roomId to the client
sessionId: reservation.sessionId, client.send("gameJoined", {
room: reservation.room roomId: roomId
}); });
setTimeout(() => { // Don't auto-leave, let the client handle it
client.leave(); // The client will leave the lobby after successfully joining the game room
}, 1000);
} catch (error) { } catch (error) {
console.error("[LobbyRoom] Error joining room:", error); console.error("[LobbyRoom] Error joining room:", error);