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");
|
const create = manager.createRoom("chess", "player1");
|
||||||
if (!create.ok) throw new Error("Failed to create room");
|
if (!create.ok) throw new Error("Failed to create room");
|
||||||
manager.joinRoom(create.roomId, "player2", "player");
|
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);
|
expect(result.ok).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ describe("RoomManager", () => {
|
|||||||
if (!create.ok) throw new Error("Failed to create room");
|
if (!create.ok) throw new Error("Failed to create room");
|
||||||
manager.joinRoom(create.roomId, "player2", "player");
|
manager.joinRoom(create.roomId, "player2", "player");
|
||||||
manager.joinRoom(create.roomId, "spectator1", "spectator");
|
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);
|
expect(result.ok).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,10 +45,12 @@ export class RoomManager {
|
|||||||
if (room.status !== "waiting") return { ok: false, error: "Game already started" };
|
if (room.status !== "waiting") return { ok: false, error: "Game already started" };
|
||||||
|
|
||||||
const plugin = gameRegistry.get(room.gameSlug)!;
|
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" };
|
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) {
|
if (room.players.length >= plugin.maxPlayers) {
|
||||||
room.state = plugin.createInitialState(room.players);
|
room.state = plugin.createInitialState(room.players);
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
// --- Room types ---
|
|
||||||
|
|
||||||
export interface Room {
|
export interface Room {
|
||||||
id: string;
|
id: string;
|
||||||
gameSlug: string;
|
gameSlug: string;
|
||||||
@@ -29,19 +27,15 @@ export interface PlayerInfo {
|
|||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Client → Server messages ---
|
|
||||||
|
|
||||||
export const GameWsClientSchema = z.discriminatedUnion("type", [
|
export const GameWsClientSchema = z.discriminatedUnion("type", [
|
||||||
z.object({ type: z.literal("CREATE_ROOM"), gameType: z.string() }),
|
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("LEAVE_ROOM"), roomId: z.string() }),
|
||||||
z.object({ type: z.literal("GAME_ACTION"), roomId: z.string(), action: z.record(z.unknown()) }),
|
z.object({ type: z.literal("GAME_ACTION"), roomId: z.string(), action: z.record(z.unknown()) }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type GameWsClientMessage = z.infer<typeof GameWsClientSchema>;
|
export type GameWsClientMessage = z.infer<typeof GameWsClientSchema>;
|
||||||
|
|
||||||
// --- Server → Client messages ---
|
|
||||||
|
|
||||||
export type GameWsServerMessage =
|
export type GameWsServerMessage =
|
||||||
| { type: "ROOM_LIST_UPDATE"; rooms: RoomSummary[] }
|
| { type: "ROOM_LIST_UPDATE"; rooms: RoomSummary[] }
|
||||||
| { type: "GAME_STATE"; roomId: string; state: unknown }
|
| { type: "GAME_STATE"; roomId: string; state: unknown }
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ function AppRoutes() {
|
|||||||
|
|
||||||
{/* Game routes (both roles) */}
|
{/* Game routes (both roles) */}
|
||||||
<Route path="/games" element={<GameLobby />} />
|
<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 */}
|
{/* Admin routes */}
|
||||||
{user.role === "admin" && (
|
{user.role === "admin" && (
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import { gameUIRegistry } from "./registry";
|
|||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import "./chess";
|
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 { gameSlug, roomId } = useParams<{ gameSlug: string; roomId: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
gameState, players, spectators, roomStatus,
|
gameState, players, spectators, roomStatus,
|
||||||
isSpectator, gameOver, error, sendAction, leaveRoom,
|
isSpectator, gameOver, error, sendAction, leaveRoom,
|
||||||
} = useGameRoom(roomId!, userId);
|
} = useGameRoom(roomId!, userId, role);
|
||||||
|
|
||||||
const plugin = gameSlug ? gameUIRegistry.get(gameSlug) : undefined;
|
const plugin = gameSlug ? gameUIRegistry.get(gameSlug) : undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface GameRoomState {
|
|||||||
error: string | null;
|
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 { send, subscribe, connected } = useWebSocket();
|
||||||
const [state, setState] = useState<GameRoomState>({
|
const [state, setState] = useState<GameRoomState>({
|
||||||
gameState: null,
|
gameState: null,
|
||||||
@@ -31,7 +31,7 @@ export function useGameRoom(roomId: string, userId: string) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!connected) return;
|
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) => {
|
const unsubscribe = subscribe((msg: any) => {
|
||||||
if (msg.roomId && msg.roomId !== roomId) return;
|
if (msg.roomId && msg.roomId !== roomId) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user