feat: implement competitive clicker MVP with Colyseus.js

- Add real-time multiplayer game server with Colyseus
- Implement unique player naming system with auto-increment
- Create lobby system with automatic matchmaking
- Build 10-minute competitive clicking game rooms (max 2 players)
- Add admin dashboard for game management (pause/resume/restart/kick)
- Implement Vue 3 client with professional UI
- Add WebSocket communication with state synchronization
- Include TypeScript throughout with proper typing
- Create REST API for admin operations
- Add reconnection support and error handling
This commit is contained in:
2025-08-06 02:32:18 -06:00
commit a28bc286a1
30 changed files with 7053 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
import { Room, Client, matchMaker } from "colyseus";
import { LobbyState, AvailableRoom } from "./schemas/LobbyState";
import { NameManager } from "../utils/nameManager";
export class LobbyRoom extends Room<LobbyState> {
private updateInterval?: NodeJS.Timeout;
onCreate(options: any) {
this.setState(new LobbyState());
this.setPrivate(false);
this.onMessage("setName", (client, playerName: string) => {
this.handleSetName(client, playerName);
});
this.onMessage("quickPlay", (client) => {
this.handleQuickPlay(client);
});
this.onMessage("joinRoom", (client, roomId: string) => {
this.handleJoinRoom(client, roomId);
});
this.updateInterval = setInterval(() => {
this.updateAvailableRooms();
}, 2000);
}
onJoin(client: Client, options: any) {
console.log(`[LobbyRoom] ${client.sessionId} joined lobby`);
const defaultName = `guest`;
const uniqueName = NameManager.getInstance().generateUniquePlayerName(defaultName, client.sessionId);
this.state.addPlayer(client.sessionId, uniqueName);
client.send("welcome", {
sessionId: client.sessionId,
assignedName: uniqueName
});
this.updateAvailableRooms();
}
onLeave(client: Client, consented: boolean) {
console.log(`[LobbyRoom] ${client.sessionId} left lobby`);
const player = this.state.players.get(client.sessionId);
if (player) {
NameManager.getInstance().releasePlayerName(client.sessionId);
}
this.state.removePlayer(client.sessionId);
}
onDispose() {
console.log("[LobbyRoom] Disposing lobby room");
if (this.updateInterval) {
clearInterval(this.updateInterval);
}
this.state.players.forEach(player => {
NameManager.getInstance().releasePlayerName(player.sessionId);
});
}
private handleSetName(client: Client, playerName: string) {
const currentPlayer = this.state.players.get(client.sessionId);
if (!currentPlayer) return;
NameManager.getInstance().releasePlayerName(client.sessionId);
const uniqueName = NameManager.getInstance().generateUniquePlayerName(playerName, client.sessionId);
currentPlayer.name = uniqueName;
client.send("nameUpdated", {
name: uniqueName
});
}
private async handleQuickPlay(client: Client) {
const player = this.state.players.get(client.sessionId);
if (!player || player.inGame) return;
try {
const reservation = await matchMaker.joinOrCreate("game", {
playerName: player.name
});
this.state.setPlayerInGame(client.sessionId, true);
client.send("roomReservation", {
sessionId: reservation.sessionId,
room: reservation.room
});
setTimeout(() => {
client.leave();
}, 1000);
} catch (error) {
console.error("[LobbyRoom] Error in quick play:", error);
client.send("error", {
message: "Could not find or create a game room"
});
}
}
private async handleJoinRoom(client: Client, roomId: string) {
const player = this.state.players.get(client.sessionId);
if (!player || player.inGame) return;
try {
const reservation = await matchMaker.joinById(roomId, {
playerName: player.name
});
this.state.setPlayerInGame(client.sessionId, true);
client.send("roomReservation", {
sessionId: reservation.sessionId,
room: reservation.room
});
setTimeout(() => {
client.leave();
}, 1000);
} catch (error) {
console.error("[LobbyRoom] Error joining room:", error);
client.send("error", {
message: "Could not join the selected room"
});
}
}
private async updateAvailableRooms() {
try {
const rooms = await matchMaker.query({ name: "game" });
const availableRooms = rooms
.filter(room => !room.locked && room.clients < 2)
.map(room => new AvailableRoom(
room.roomId,
room.clients,
room.metadata?.gameStatus || "waiting"
));
this.state.updateAvailableRooms(availableRooms);
} catch (error) {
console.error("[LobbyRoom] Error updating available rooms:", error);
}
}
}