fix(chess): admin users and move registration
Some checks failed
Deploy to Production / test (push) Failing after 29s
Some checks failed
Deploy to Production / test (push) Failing after 29s
- Add role field to JOIN_ROOM message schema - Allow admin users to join rooms and be added as players - Update panel to pass user role when joining game rooms - Fix chess move coordinates in tests (algebraic notation) - Ensure admin users can make moves for both sides
This commit is contained in:
@@ -85,7 +85,7 @@ describe("RoomManager", () => {
|
||||
const create = manager.createRoom("chess", "player1");
|
||||
if (!create.ok) throw new Error("Failed to create room");
|
||||
manager.joinRoom(create.roomId, "player2", "player");
|
||||
const result = manager.handleAction(create.roomId, "player1", { type: "move", from: [6, 4], to: [4, 4] });
|
||||
const result = manager.handleAction(create.roomId, "player1", { type: "move", from: "e2", to: "e4" });
|
||||
expect(result.ok).toBe(true);
|
||||
});
|
||||
|
||||
@@ -94,7 +94,7 @@ describe("RoomManager", () => {
|
||||
if (!create.ok) throw new Error("Failed to create room");
|
||||
manager.joinRoom(create.roomId, "player2", "player");
|
||||
manager.joinRoom(create.roomId, "spectator1", "spectator");
|
||||
const result = manager.handleAction(create.roomId, "spectator1", { type: "move", from: [6, 4], to: [4, 4] });
|
||||
const result = manager.handleAction(create.roomId, "spectator1", { type: "move", from: "e2", to: "e4" });
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,10 +45,12 @@ export class RoomManager {
|
||||
if (room.status !== "waiting") return { ok: false, error: "Game already started" };
|
||||
|
||||
const plugin = gameRegistry.get(room.gameSlug)!;
|
||||
if (room.players.length >= plugin.maxPlayers) return { ok: false, error: "Room is full" };
|
||||
if (room.players.length >= plugin.maxPlayers && role !== "admin") return { ok: false, error: "Room is full" };
|
||||
if (room.players.includes(playerId) && role !== "admin") return { ok: true, started: room.status === "playing" };
|
||||
|
||||
room.players.push(playerId);
|
||||
if (!room.players.includes(playerId) || role === "admin") {
|
||||
room.players.push(playerId);
|
||||
}
|
||||
|
||||
if (room.players.length >= plugin.maxPlayers) {
|
||||
room.state = plugin.createInitialState(room.players);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// --- Room types ---
|
||||
|
||||
export interface Room {
|
||||
id: string;
|
||||
gameSlug: string;
|
||||
@@ -29,19 +27,15 @@ export interface PlayerInfo {
|
||||
username: string;
|
||||
}
|
||||
|
||||
// --- Client → Server messages ---
|
||||
|
||||
export const GameWsClientSchema = z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal("CREATE_ROOM"), gameType: z.string() }),
|
||||
z.object({ type: z.literal("JOIN_ROOM"), roomId: z.string(), as: z.enum(["player", "spectator"]) }),
|
||||
z.object({ type: z.literal("JOIN_ROOM"), roomId: z.string(), as: z.enum(["player", "spectator"]), role: z.enum(["player", "admin"]).optional() }),
|
||||
z.object({ type: z.literal("LEAVE_ROOM"), roomId: z.string() }),
|
||||
z.object({ type: z.literal("GAME_ACTION"), roomId: z.string(), action: z.record(z.unknown()) }),
|
||||
]);
|
||||
|
||||
export type GameWsClientMessage = z.infer<typeof GameWsClientSchema>;
|
||||
|
||||
// --- Server → Client messages ---
|
||||
|
||||
export type GameWsServerMessage =
|
||||
| { type: "ROOM_LIST_UPDATE"; rooms: RoomSummary[] }
|
||||
| { type: "GAME_STATE"; roomId: string; state: unknown }
|
||||
|
||||
@@ -82,7 +82,7 @@ function AppRoutes() {
|
||||
|
||||
{/* Game routes (both roles) */}
|
||||
<Route path="/games" element={<GameLobby />} />
|
||||
<Route path="/:gameSlug/:roomId" element={<GameRoom userId={user.discordId} />} />
|
||||
<Route path="/:gameSlug/:roomId" element={<GameRoom userId={user.discordId} role={user.role} />} />
|
||||
|
||||
{/* Admin routes */}
|
||||
{user.role === "admin" && (
|
||||
|
||||
@@ -4,13 +4,13 @@ import { gameUIRegistry } from "./registry";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import "./chess";
|
||||
|
||||
export function GameRoom({ userId }: { userId: string }) {
|
||||
export function GameRoom({ userId, role }: { userId: string; role?: string }) {
|
||||
const { gameSlug, roomId } = useParams<{ gameSlug: string; roomId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
gameState, players, spectators, roomStatus,
|
||||
isSpectator, gameOver, error, sendAction, leaveRoom,
|
||||
} = useGameRoom(roomId!, userId);
|
||||
} = useGameRoom(roomId!, userId, role);
|
||||
|
||||
const plugin = gameSlug ? gameUIRegistry.get(gameSlug) : undefined;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ interface GameRoomState {
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export function useGameRoom(roomId: string, userId: string) {
|
||||
export function useGameRoom(roomId: string, userId: string, role?: string) {
|
||||
const { send, subscribe, connected } = useWebSocket();
|
||||
const [state, setState] = useState<GameRoomState>({
|
||||
gameState: null,
|
||||
@@ -31,7 +31,7 @@ export function useGameRoom(roomId: string, userId: string) {
|
||||
useEffect(() => {
|
||||
if (!connected) return;
|
||||
|
||||
send({ type: "JOIN_ROOM", roomId, as: "player" });
|
||||
send({ type: "JOIN_ROOM", roomId, as: "player", role: role ?? "player" });
|
||||
|
||||
const unsubscribe = subscribe((msg: any) => {
|
||||
if (msg.roomId && msg.roomId !== roomId) return;
|
||||
|
||||
Reference in New Issue
Block a user