feat(chess): replace custom engine with chess.js and react-chessboard
Some checks failed
Deploy to Production / test (push) Failing after 39s
Some checks failed
Deploy to Production / test (push) Failing after 39s
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>
This commit is contained in:
@@ -15,68 +15,64 @@ describe("chessPlugin", () => {
|
||||
});
|
||||
|
||||
describe("createInitialState", () => {
|
||||
it("should create a board with pieces in starting positions", () => {
|
||||
it("should create initial FEN position", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
expect(state.board.length).toBe(8);
|
||||
expect(state.board[0].length).toBe(8);
|
||||
expect(state.currentTurn).toBe("white");
|
||||
expect(state.fen).toBe("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
|
||||
expect(state.players.white).toBe(PLAYER_WHITE);
|
||||
expect(state.players.black).toBe(PLAYER_BLACK);
|
||||
expect(state.moveHistory).toEqual([]);
|
||||
expect(state.status).toBe("playing");
|
||||
});
|
||||
|
||||
it("should place white pawns on row 6 and black pawns on row 1", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
for (let col = 0; col < 8; col++) {
|
||||
expect(state.board[6][col]).toEqual({ type: "pawn", color: "white" });
|
||||
expect(state.board[1][col]).toEqual({ type: "pawn", color: "black" });
|
||||
}
|
||||
});
|
||||
|
||||
it("should place rooks in corners", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
expect(state.board[0][0]).toEqual({ type: "rook", color: "black" });
|
||||
expect(state.board[0][7]).toEqual({ type: "rook", color: "black" });
|
||||
expect(state.board[7][0]).toEqual({ type: "rook", color: "white" });
|
||||
expect(state.board[7][7]).toEqual({ type: "rook", color: "white" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleAction — move", () => {
|
||||
it("should allow white pawn to move forward on white's turn", () => {
|
||||
it("should allow a legal pawn move", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
const result = chessPlugin.handleAction(state, { type: "move", from: [6, 4], to: [4, 4] }, PLAYER_WHITE);
|
||||
const result = chessPlugin.handleAction(state, { type: "move", from: "e2", to: "e4" }, PLAYER_WHITE);
|
||||
expect(result.ok).toBe(true);
|
||||
if (result.ok) {
|
||||
expect(result.state.board[4][4]).toEqual({ type: "pawn", color: "white" });
|
||||
expect(result.state.board[6][4]).toBeNull();
|
||||
expect(result.state.currentTurn).toBe("black");
|
||||
expect(result.state.fen).not.toBe(state.fen);
|
||||
expect(result.state.moveHistory).toEqual(["e4"]);
|
||||
}
|
||||
});
|
||||
|
||||
it("should reject move when it is not the player's turn", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
const result = chessPlugin.handleAction(state, { type: "move", from: [1, 4], to: [3, 4] }, PLAYER_BLACK);
|
||||
const result = chessPlugin.handleAction(state, { type: "move", from: "e7", to: "e5" }, PLAYER_BLACK);
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("should reject moving opponent's piece", () => {
|
||||
it("should reject an illegal move", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
const result = chessPlugin.handleAction(state, { type: "move", from: [1, 4], to: [3, 4] }, PLAYER_WHITE);
|
||||
const result = chessPlugin.handleAction(state, { type: "move", from: "e2", to: "e5" }, PLAYER_WHITE);
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("should reject move from empty square", () => {
|
||||
it("should reject a non-player's move", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
const result = chessPlugin.handleAction(state, { type: "move", from: [4, 4], to: [3, 4] }, PLAYER_WHITE);
|
||||
const result = chessPlugin.handleAction(state, { type: "move", from: "e2", to: "e4" }, "random_player");
|
||||
expect(result.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("should reject out-of-bounds coordinates", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
const result = chessPlugin.handleAction(state, { type: "move", from: [8, 0], to: [7, 0] }, PLAYER_WHITE);
|
||||
expect(result.ok).toBe(false);
|
||||
it("should detect checkmate", () => {
|
||||
// Scholar's mate: 1.e4 e5 2.Bc4 Nc6 3.Qh5 Nf6 4.Qxf7#
|
||||
let state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
const moves = [
|
||||
{ player: PLAYER_WHITE, from: "e2", to: "e4" },
|
||||
{ player: PLAYER_BLACK, from: "e7", to: "e5" },
|
||||
{ player: PLAYER_WHITE, from: "f1", to: "c4" },
|
||||
{ player: PLAYER_BLACK, from: "b8", to: "c6" },
|
||||
{ player: PLAYER_WHITE, from: "d1", to: "h5" },
|
||||
{ player: PLAYER_BLACK, from: "g8", to: "f6" },
|
||||
{ player: PLAYER_WHITE, from: "h5", to: "f7" },
|
||||
];
|
||||
for (const m of moves) {
|
||||
const result = chessPlugin.handleAction(state, { type: "move", from: m.from, to: m.to }, m.player);
|
||||
expect(result.ok).toBe(true);
|
||||
if (result.ok) state = result.state;
|
||||
}
|
||||
expect(state.status).toBe("checkmate");
|
||||
expect(state.winner).toBe(PLAYER_WHITE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -93,17 +89,9 @@ describe("chessPlugin", () => {
|
||||
});
|
||||
|
||||
describe("getPlayerView", () => {
|
||||
it("should return full state (chess has no hidden info)", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
const view = chessPlugin.getPlayerView(state, PLAYER_WHITE);
|
||||
expect(view).toEqual(state);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSpectatorView", () => {
|
||||
it("should return full state", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
const view = chessPlugin.getSpectatorView(state);
|
||||
const view = chessPlugin.getPlayerView(state, PLAYER_WHITE);
|
||||
expect(view).toEqual(state);
|
||||
});
|
||||
});
|
||||
@@ -111,16 +99,14 @@ describe("chessPlugin", () => {
|
||||
describe("isGameOver", () => {
|
||||
it("should return null for ongoing game", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
const result = chessPlugin.isGameOver!(state);
|
||||
expect(result).toBeNull();
|
||||
expect(chessPlugin.isGameOver!(state)).toBeNull();
|
||||
});
|
||||
|
||||
it("should return winner for forfeit", () => {
|
||||
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
|
||||
state.status = "forfeit";
|
||||
state.winner = PLAYER_BLACK;
|
||||
const result = chessPlugin.isGameOver!(state);
|
||||
expect(result).toEqual({ winner: PLAYER_BLACK, reason: "forfeit" });
|
||||
expect(chessPlugin.isGameOver!(state)).toEqual({ winner: PLAYER_BLACK, reason: "forfeit" });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user