Files
snatchgame/client/src/services/colyseus.ts
josedario87 1392e5a652 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
2025-08-06 02:58:29 -06:00

218 lines
5.9 KiB
TypeScript

import { Client, Room } from "colyseus.js";
import { ref, Ref } from "vue";
export interface PlayerData {
sessionId: string;
name: string;
clicks: number;
connected?: boolean;
}
export interface LobbyPlayer {
sessionId: string;
name: string;
inGame: boolean;
}
export interface AvailableRoom {
roomId: string;
playerCount: number;
status: string;
}
class ColyseusService {
private client: Client;
private currentRoom: Room | null = null;
public lobbyRoom: Ref<Room | null> = ref(null);
public gameRoom: Ref<Room | null> = ref(null);
public playerName: Ref<string> = ref("");
public sessionId: Ref<string> = ref("");
constructor() {
this.client = new Client("ws://localhost:3000");
}
async joinLobby(): Promise<Room> {
try {
const room = await this.client.joinOrCreate("lobby");
this.lobbyRoom.value = room;
this.currentRoom = room;
room.onMessage("welcome", (data) => {
this.sessionId.value = data.sessionId;
this.playerName.value = data.assignedName;
});
room.onMessage("nameUpdated", (data) => {
this.playerName.value = data.name;
});
return room;
} catch (error) {
console.error("Failed to join lobby:", error);
throw error;
}
}
async setPlayerName(name: string): Promise<void> {
if (this.lobbyRoom.value) {
this.lobbyRoom.value.send("setName", name);
}
}
async quickPlay(): Promise<Room> {
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<Room> {
try {
const gameRoom = await this.client.joinById(roomId, {
playerName: this.playerName.value
});
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;
}
}
sendClick(): void {
if (this.gameRoom.value) {
this.gameRoom.value.send("click");
}
}
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;
this.gameRoom.value = null;
this.lobbyRoom.value = null;
}
}
async fetchRooms(): Promise<any[]> {
try {
const response = await fetch("http://localhost:3000/api/rooms");
return await response.json();
} catch (error) {
console.error("Failed to fetch rooms:", error);
return [];
}
}
async fetchRoomStats(roomId: string): Promise<any> {
try {
const response = await fetch(`http://localhost:3000/api/rooms/${roomId}/stats`);
return await response.json();
} catch (error) {
console.error("Failed to fetch room stats:", error);
return null;
}
}
async pauseRoom(roomId: string): Promise<void> {
await fetch(`http://localhost:3000/api/rooms/${roomId}/pause`, { method: "POST" });
}
async resumeRoom(roomId: string): Promise<void> {
await fetch(`http://localhost:3000/api/rooms/${roomId}/resume`, { method: "POST" });
}
async restartRoom(roomId: string): Promise<void> {
await fetch(`http://localhost:3000/api/rooms/${roomId}/restart`, { method: "POST" });
}
async kickPlayer(roomId: string, playerId: string): Promise<void> {
await fetch(`http://localhost:3000/api/rooms/${roomId}/kick/${playerId}`, { method: "POST" });
}
async fetchGlobalStats(): Promise<any> {
try {
const response = await fetch("http://localhost:3000/api/stats");
return await response.json();
} catch (error) {
console.error("Failed to fetch global stats:", error);
return null;
}
}
}
export const colyseusService = new ColyseusService();