diff --git a/panel/src/lib/useGameRoom.ts b/panel/src/lib/useGameRoom.ts new file mode 100644 index 0000000..bcd044f --- /dev/null +++ b/panel/src/lib/useGameRoom.ts @@ -0,0 +1,115 @@ +import { useEffect, useState, useCallback } from "react"; +import { useWebSocket } from "./useWebSocket"; + +interface PlayerInfo { + discordId: string; + username: string; +} + +interface GameRoomState { + gameState: unknown; + players: PlayerInfo[]; + spectators: PlayerInfo[]; + roomStatus: "connecting" | "waiting" | "playing" | "finished" | "not_found"; + isSpectator: boolean; + gameOver: { winner: string | null; reason: string } | null; + error: string | null; +} + +export function useGameRoom(roomId: string, userId: string) { + const { send, subscribe, connected } = useWebSocket(); + const [state, setState] = useState({ + gameState: null, + players: [], + spectators: [], + roomStatus: "connecting", + isSpectator: false, + gameOver: null, + error: null, + }); + + useEffect(() => { + if (!connected) return; + + send({ type: "JOIN_ROOM", roomId, as: "player" }); + + const unsubscribe = subscribe((msg: any) => { + if (msg.roomId && msg.roomId !== roomId) return; + + switch (msg.type) { + case "GAME_STATE": + setState(prev => ({ ...prev, gameState: msg.state, roomStatus: "playing" })); + break; + + case "GAME_STARTED": + setState(prev => ({ ...prev, gameState: msg.state, roomStatus: "playing" })); + break; + + case "GAME_UPDATE": + setState(prev => ({ ...prev, gameState: msg.state })); + break; + + case "PLAYER_JOINED": + setState(prev => { + if (msg.as === "spectator") { + const isMe = msg.player.discordId === userId; + return { + ...prev, + spectators: [...prev.spectators.filter(s => s.discordId !== msg.player.discordId), msg.player], + isSpectator: isMe ? true : prev.isSpectator, + roomStatus: prev.roomStatus === "connecting" ? "waiting" : prev.roomStatus, + }; + } + return { + ...prev, + players: [...prev.players.filter(p => p.discordId !== msg.player.discordId), msg.player], + roomStatus: prev.roomStatus === "connecting" ? "waiting" : prev.roomStatus, + }; + }); + break; + + case "PLAYER_LEFT": + setState(prev => ({ + ...prev, + players: prev.players.filter(p => p.discordId !== msg.playerId), + spectators: prev.spectators.filter(s => s.discordId !== msg.playerId), + })); + break; + + case "GAME_ENDED": + setState(prev => ({ + ...prev, + roomStatus: "finished", + gameOver: { winner: msg.winner, reason: msg.reason }, + })); + break; + + case "ERROR": + if (msg.message === "Game already started" || msg.message === "Room is full") { + send({ type: "JOIN_ROOM", roomId, as: "spectator" }); + } else if (msg.message === "Room not found") { + setState(prev => ({ ...prev, roomStatus: "not_found" })); + } else { + setState(prev => ({ ...prev, error: msg.message })); + } + break; + } + }); + + return () => { + send({ type: "LEAVE_ROOM", roomId }); + unsubscribe(); + }; + }, [roomId, connected, userId, send, subscribe]); + + const sendAction = useCallback((action: unknown) => { + send({ type: "GAME_ACTION", roomId, action }); + setState(prev => ({ ...prev, error: null })); + }, [roomId, send]); + + const leaveRoom = useCallback(() => { + send({ type: "LEAVE_ROOM", roomId }); + }, [roomId, send]); + + return { ...state, sendAction, leaveRoom }; +}