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