diff --git a/panel/src/games/GameLobby.tsx b/panel/src/games/GameLobby.tsx index 136198c..784a3e1 100644 --- a/panel/src/games/GameLobby.tsx +++ b/panel/src/games/GameLobby.tsx @@ -1,3 +1,163 @@ -export function GameLobby() { - return
Game Lobby โ€” loading...
; +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { useWebSocket } from "../lib/useWebSocket"; +import { gameUIRegistry } from "./registry"; +import "./chess"; + +interface RoomSummary { + id: string; + gameSlug: string; + gameName: string; + host: string; + playerCount: number; + maxPlayers: number; + spectatorCount: number; + status: "waiting" | "playing" | "finished"; +} + +export function GameLobby() { + const { send, subscribe, connected } = useWebSocket(); + const navigate = useNavigate(); + const [rooms, setRooms] = useState([]); + const [filter, setFilter] = useState(null); + const [showCreate, setShowCreate] = useState(false); + + const gameTypes = gameUIRegistry.list(); + + useEffect(() => { + if (!connected) return; + + const unsubscribe = subscribe((msg: any) => { + if (msg.type === "ROOM_LIST_UPDATE") { + setRooms(msg.rooms); + } + if (msg.type === "ROOM_CREATED") { + navigate(`/${msg.gameSlug}/${msg.roomId}`); + } + }); + + return unsubscribe; + }, [connected, subscribe, navigate]); + + const filteredRooms = filter ? rooms.filter(r => r.gameSlug === filter) : rooms; + const activeRooms = filteredRooms.filter(r => r.status !== "finished"); + + function createRoom(gameSlug: string) { + send({ type: "CREATE_ROOM", gameType: gameSlug }); + setShowCreate(false); + } + + return ( +
+
+
+

Games

+

Browse and create game rooms

+
+ +
+ +
+ + {gameTypes.map(g => ( + + ))} +
+ +
+
+ Active Rooms + ({activeRooms.length}) +
+ {activeRooms.length === 0 ? ( +
+ No active rooms. Create one to get started! +
+ ) : ( +
+ {activeRooms.map(room => { + const plugin = gameUIRegistry.get(room.gameSlug); + return ( +
+
+ {plugin?.icon ?? "๐ŸŽฎ"} +
+
{room.gameName}
+
+ + {room.status === "waiting" ? "Waiting" : "Playing"} + + {room.playerCount}/{room.maxPlayers} players + {room.spectatorCount > 0 && ยท ๐Ÿ‘ {room.spectatorCount}} +
+
+
+ +
+ ); + })} +
+ )} +
+ + {showCreate && ( +
setShowCreate(false)}> +
e.stopPropagation()}> +

Create a Room

+
+ {gameTypes.map(g => ( + + ))} +
+ +
+
+ )} +
+ ); } diff --git a/panel/src/games/GameRoom.tsx b/panel/src/games/GameRoom.tsx index 67edbad..15ab19e 100644 --- a/panel/src/games/GameRoom.tsx +++ b/panel/src/games/GameRoom.tsx @@ -1,3 +1,118 @@ +import { useParams, useNavigate } from "react-router-dom"; +import { useGameRoom } from "../lib/useGameRoom"; +import { gameUIRegistry } from "./registry"; +import { Loader2 } from "lucide-react"; +import "./chess"; + export function GameRoom({ userId }: { userId: string }) { - return
Game Room โ€” loading...
; + const { gameSlug, roomId } = useParams<{ gameSlug: string; roomId: string }>(); + const navigate = useNavigate(); + const { + gameState, players, spectators, roomStatus, + isSpectator, gameOver, error, sendAction, leaveRoom, + } = useGameRoom(roomId!, userId); + + const plugin = gameSlug ? gameUIRegistry.get(gameSlug) : undefined; + + if (!plugin) { + return ( +
+
Unknown Game
+

The game type "{gameSlug}" doesn't exist.

+ +
+ ); + } + + if (roomStatus === "not_found") { + return ( +
+
Room Not Found
+

This room no longer exists or has expired.

+ +
+ ); + } + + if (roomStatus === "connecting") { + return ( +
+ +
+ ); + } + + const GameComponent = plugin.component; + + return ( +
+
+
+ {plugin.icon} +
+

{plugin.name}

+
+ + {roomStatus === "waiting" ? "Waiting" : roomStatus === "playing" ? "Playing" : "Finished"} + + {isSpectator && Spectating} + ๐Ÿ‘ {spectators.length} +
+
+
+ +
+ + {error && ( +
+ {error} +
+ )} + + {gameOver && ( +
+
+ {gameOver.winner + ? `Winner: ${players.find(p => p.discordId === gameOver.winner)?.username ?? gameOver.winner}` + : "Draw!"} +
+
Reason: {gameOver.reason}
+
+ )} + + {roomStatus === "waiting" && ( +
+
+ Waiting for players ({players.length}/2) +
+
+ Share this URL to invite: {window.location.href} +
+
+ )} + + {(roomStatus === "playing" || roomStatus === "finished") && gameState && ( + + )} +
+ ); }