Files
aurorabot/shared/games/chess/plugin.ts
syntaxbullet 0dadc82f84
Some checks failed
Deploy to Production / test (push) Failing after 39s
feat(chess): replace custom engine with chess.js and react-chessboard
Swap the custom move validation and Unicode piece grid for chess.js
(full rules engine with check/checkmate/castling/en passant/promotion)
and react-chessboard (drag-and-drop SVG board). Board styled to match
the purple dark theme and auto-orients to the player's color.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:22:27 +02:00

103 lines
3.6 KiB
TypeScript

import { Chess } from "chess.js";
import type { GamePlugin, GameResult, GameOverResult } from "../types";
import type { ChessState, ChessAction } from "./types";
function getPlayerColor(state: ChessState, playerId: string): "white" | "black" | null {
if (state.players.white === playerId) return "white";
if (state.players.black === playerId) return "black";
return null;
}
function deriveStatus(game: Chess): ChessState["status"] {
if (game.isCheckmate()) return "checkmate";
if (game.isStalemate()) return "stalemate";
if (game.isDraw()) return "draw";
return "playing";
}
export const chessPlugin: GamePlugin<ChessState, ChessAction> = {
slug: "chess",
name: "Chess",
minPlayers: 2,
maxPlayers: 2,
createInitialState(players: string[]): ChessState {
const game = new Chess();
return {
fen: game.fen(),
players: { white: players[0], black: players[1] },
moveHistory: [],
status: "playing",
winner: null,
};
},
handleAction(state: ChessState, action: ChessAction, playerId: string): GameResult<ChessState> {
if (state.status !== "playing") {
return { ok: false, error: "Game is already over" };
}
if (action.type === "forfeit") {
const color = getPlayerColor(state, playerId);
if (!color) return { ok: false, error: "You are not a player in this game" };
const winner = color === "white" ? state.players.black : state.players.white;
return { ok: true, state: { ...state, status: "forfeit", winner } };
}
if (action.type === "move") {
const playerColor = getPlayerColor(state, playerId);
if (!playerColor) return { ok: false, error: "You are not a player in this game" };
const game = new Chess(state.fen);
const turn = game.turn() === "w" ? "white" : "black";
if (playerColor !== turn) return { ok: false, error: "It is not your turn" };
let move;
try {
move = game.move({
from: action.from,
to: action.to,
promotion: action.promotion ?? "q",
});
} catch {
return { ok: false, error: "Invalid move" };
}
if (!move) return { ok: false, error: "Invalid move" };
const status = deriveStatus(game);
const winner = status === "checkmate"
? (turn === "white" ? state.players.white : state.players.black)
: null;
return {
ok: true,
state: {
...state,
fen: game.fen(),
moveHistory: [...state.moveHistory, move.san],
status,
winner,
},
};
}
return { ok: false, error: "Unknown action type" };
},
getPlayerView(state: ChessState): ChessState { return state; },
getSpectatorView(state: ChessState): ChessState { return state; },
isGameOver(state: ChessState): GameOverResult | null {
if (state.status === "playing") return null;
return { winner: state.winner, reason: state.status };
},
onPlayerDisconnect(state: ChessState, playerId: string): ChessState {
const color = getPlayerColor(state, playerId);
if (!color) return state;
const winner = color === "white" ? state.players.black : state.players.white;
return { ...state, status: "forfeit", winner };
},
};