feat(chess): add king basic move rules (P2.12)

This commit is contained in:
Joey Yakimowich-Payne 2026-04-16 14:52:51 -06:00
commit 83d99778db
No known key found for this signature in database
2 changed files with 106 additions and 0 deletions

View file

@ -0,0 +1,74 @@
import { describe, it, expect } from "vitest";
import { Session } from "@paratype/rete";
import type { EntityId } from "@paratype/rete";
import { getLegalKingMoves } from "./king.js";
const mkId = (n: number) => n as EntityId;
function insertPiece(
session: Session,
id: number,
type: string,
color: string,
square: number,
): EntityId {
const eid = mkId(id);
session.insert(eid, "PieceType", type);
session.insert(eid, "Color", color);
session.insert(eid, "Position", square);
return eid;
}
describe("getLegalKingMoves", () => {
it("king at e4 (28) has 8 moves on empty board", () => {
const session = new Session({ autoFire: false });
const king = insertPiece(session, 1, "king", "white", 28);
expect(getLegalKingMoves(session, king)).toHaveLength(8);
});
it("king at a1 (0) has 3 moves on empty board", () => {
const session = new Session({ autoFire: false });
const king = insertPiece(session, 1, "king", "white", 0);
expect(getLegalKingMoves(session, king)).toHaveLength(3);
});
it("king at h8 (63) has 3 moves on empty board", () => {
const session = new Session({ autoFire: false });
const king = insertPiece(session, 1, "king", "black", 63);
expect(getLegalKingMoves(session, king)).toHaveLength(3);
});
it("king blocked by own pieces", () => {
const session = new Session({ autoFire: false });
const king = insertPiece(session, 1, "king", "white", 28); // e4
insertPiece(session, 2, "pawn", "white", 27); // d4
insertPiece(session, 3, "pawn", "white", 29); // f4
const moves = getLegalKingMoves(session, king);
expect(moves.find(m => m.to === 27)).toBeUndefined();
expect(moves.find(m => m.to === 29)).toBeUndefined();
expect(moves).toHaveLength(6);
});
it("king can capture enemy piece", () => {
const session = new Session({ autoFire: false });
const king = insertPiece(session, 1, "king", "white", 28); // e4
insertPiece(session, 2, "pawn", "black", 27); // d4
const capture = getLegalKingMoves(session, king).find(m => m.to === 27);
expect(capture).toBeDefined();
expect(capture?.isCapture).toBe(true);
});
it("quiet moves are marked isCapture=false", () => {
const session = new Session({ autoFire: false });
const king = insertPiece(session, 1, "king", "white", 28);
const moves = getLegalKingMoves(session, king);
for (const m of moves) {
expect(m.isCapture).toBe(false);
}
});
it("returns empty list when pieceId has no Position/Color", () => {
const session = new Session({ autoFire: false });
expect(getLegalKingMoves(session, mkId(999))).toEqual([]);
});
});

View file

@ -0,0 +1,32 @@
/**
* King movement rules for @paratype/chess.
*
* Basic king moves: 1 square in any of 8 directions.
* Excludes:
* - Castling (P2.15)
* - Check-aware rejection / king-in-check filter (P2.18)
*
* Rules:
* - empty target square → legal quiet move
* - enemy on target → legal capture
* - ally on target → illegal
*/
import type { Session, EntityId } from "@paratype/rete";
import { kingCandidates } from "./primitives.js";
import type { LegalMove } from "./types.js";
import { getPiecePosition, getPieceColor, isAllyAt, isPieceAt } from "./board-queries.js";
/** Get all legal basic king moves (1 square, any direction). */
export function getLegalKingMoves(session: Session, pieceId: EntityId): LegalMove[] {
const from = getPiecePosition(session, pieceId);
const color = getPieceColor(session, pieceId);
if (from === null || color === null) return [];
const moves: LegalMove[] = [];
for (const to of kingCandidates(from)) {
if (isAllyAt(session, to, color)) continue;
const isCapture = isPieceAt(session, to);
moves.push({ pieceId, from, to, isCapture });
}
return moves;
}