feat(panel): add useGameRoom hook for per-room game state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
115
panel/src/lib/useGameRoom.ts
Normal file
115
panel/src/lib/useGameRoom.ts
Normal file
@@ -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<GameRoomState>({
|
||||||
|
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 };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user