fix(chess): prevent duplicate players and fix move detection
Some checks failed
Deploy to Production / test (push) Failing after 38s

- Prevent same player from joining as both white and black
- Add validation to reject duplicate players in RoomManager
- Fix spectator status not resetting when joining as player
- Use ref to track latest chess state in ChessBoard for accurate move validation
This commit is contained in:
syntaxbullet
2026-04-02 15:36:05 +02:00
parent e521d3086f
commit 26a0e532f6
4 changed files with 19 additions and 4 deletions

View File

@@ -46,7 +46,8 @@ export class RoomManager {
const plugin = gameRegistry.get(room.gameSlug)!; const plugin = gameRegistry.get(room.gameSlug)!;
if (room.players.length >= plugin.maxPlayers && role !== "admin") 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 === "waiting" };
if (room.players.includes(playerId) && role === "admin") return { ok: false, error: "Already a player in this game" };
if (!room.players.includes(playerId) || role === "admin") { if (!room.players.includes(playerId) || role === "admin") {
room.players.push(playerId); room.players.push(playerId);

View File

@@ -18,6 +18,12 @@ export function ChessBoard({ state, myPlayerId, isSpectator, onAction, players }
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [boardWidth, setBoardWidth] = useState(400); const [boardWidth, setBoardWidth] = useState(400);
// Track latest state in ref to avoid stale closures
const chessRef = useRef(chess);
useEffect(() => {
chessRef.current = chess;
}, [chess]);
// Responsive board sizing // Responsive board sizing
useEffect(() => { useEffect(() => {
const container = containerRef.current; const container = containerRef.current;
@@ -56,7 +62,7 @@ export function ChessBoard({ state, myPlayerId, isSpectator, onAction, players }
function onDrop(sourceSquare: string, targetSquare: string): boolean { function onDrop(sourceSquare: string, targetSquare: string): boolean {
if (isSpectator || !isMyTurn) return false; if (isSpectator || !isMyTurn) return false;
const testGame = new Chess(chess.fen); const testGame = new Chess(chessRef.current.fen);
// Check if this is a promotion move // Check if this is a promotion move
const piece = testGame.get(sourceSquare as any); const piece = testGame.get(sourceSquare as any);

View File

@@ -56,13 +56,16 @@ export function useGameRoom(roomId: string, userId: string, role?: string) {
return { return {
...prev, ...prev,
spectators: [...prev.spectators.filter(s => s.discordId !== msg.player.discordId), msg.player], spectators: [...prev.spectators.filter(s => s.discordId !== msg.player.discordId), msg.player],
isSpectator: isMe ? true : prev.isSpectator, isSpectator: isMe || prev.isSpectator,
roomStatus: prev.roomStatus === "connecting" ? "waiting" : prev.roomStatus, roomStatus: prev.roomStatus === "connecting" ? "waiting" : prev.roomStatus,
}; };
} }
const isMe = msg.player.discordId === userId;
return { return {
...prev, ...prev,
players: [...prev.players.filter(p => p.discordId !== msg.player.discordId), msg.player], players: [...prev.players.filter(p => p.discordId !== msg.player.discordId), msg.player],
isSpectator: isMe ? false : prev.isSpectator,
roomStatus: prev.roomStatus === "connecting" ? "waiting" : prev.roomStatus, roomStatus: prev.roomStatus === "connecting" ? "waiting" : prev.roomStatus,
}; };
}); });

View File

@@ -23,9 +23,14 @@ export const chessPlugin: GamePlugin<ChessState, ChessAction> = {
createInitialState(players: string[]): ChessState { createInitialState(players: string[]): ChessState {
const game = new Chess(); const game = new Chess();
if (players[0] === players[1]) {
throw new Error("Cannot create chess game with same player for both sides");
}
return { return {
fen: game.fen(), fen: game.fen(),
players: { white: players[0], black: players[1] }, players: { white: players[0]!, black: players[1]! },
moveHistory: [], moveHistory: [],
status: "playing", status: "playing",
winner: null, winner: null,