feat(chess): add capture resolution (P2.14)
This commit is contained in:
parent
83d99778db
commit
483e4ef686
2 changed files with 108 additions and 0 deletions
63
packages/chess/src/rules/capture.test.ts
Normal file
63
packages/chess/src/rules/capture.test.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { Session } from "@paratype/rete";
|
||||
import { applyCapture, getCapturablePieces, canCapture } from "./capture.js";
|
||||
import type { EntityId } from "@paratype/rete";
|
||||
|
||||
const mkId = (n: number) => n as EntityId;
|
||||
const ins = (s: Session, id: number, type: string, color: string, sq: number) => {
|
||||
const eid = mkId(id);
|
||||
s.insert(eid, "PieceType", type); s.insert(eid, "Color", color); s.insert(eid, "Position", sq);
|
||||
return eid;
|
||||
};
|
||||
|
||||
describe("applyCapture", () => {
|
||||
it("removes all facts for the captured piece", () => {
|
||||
const session = new Session({ autoFire: false });
|
||||
const black = ins(session, 2, "knight", "black", 28);
|
||||
applyCapture(session, black);
|
||||
expect(session.contains(black, "PieceType")).toBe(false);
|
||||
expect(session.contains(black, "Color")).toBe(false);
|
||||
expect(session.contains(black, "Position")).toBe(false);
|
||||
});
|
||||
|
||||
it("does not affect other pieces", () => {
|
||||
const session = new Session({ autoFire: false });
|
||||
const white = ins(session, 1, "pawn", "white", 20);
|
||||
const black = ins(session, 2, "knight", "black", 28);
|
||||
applyCapture(session, black);
|
||||
expect(session.contains(white, "Position")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCapturablePieces", () => {
|
||||
it("returns all enemy piece ids", () => {
|
||||
const session = new Session({ autoFire: false });
|
||||
ins(session, 1, "pawn", "white", 20);
|
||||
ins(session, 2, "pawn", "white", 21);
|
||||
ins(session, 3, "rook", "black", 50);
|
||||
ins(session, 4, "bishop", "black", 51);
|
||||
const capturable = getCapturablePieces(session, "white");
|
||||
expect(capturable).toContain(mkId(3));
|
||||
expect(capturable).toContain(mkId(4));
|
||||
expect(capturable).not.toContain(mkId(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe("canCapture", () => {
|
||||
it("returns true when enemy on square", () => {
|
||||
const session = new Session({ autoFire: false });
|
||||
ins(session, 1, "rook", "black", 28);
|
||||
expect(canCapture(session, 28, "white")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when own piece on square", () => {
|
||||
const session = new Session({ autoFire: false });
|
||||
ins(session, 1, "rook", "white", 28);
|
||||
expect(canCapture(session, 28, "white")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false when square is empty", () => {
|
||||
const session = new Session({ autoFire: false });
|
||||
expect(canCapture(session, 28, "white")).toBe(false);
|
||||
});
|
||||
});
|
||||
45
packages/chess/src/rules/capture.ts
Normal file
45
packages/chess/src/rules/capture.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Capture resolution for @paratype/chess.
|
||||
* When a capture occurs, the captured piece's facts are retracted from WM.
|
||||
*/
|
||||
import type { Session, EntityId } from "@paratype/rete";
|
||||
import type { PieceColor } from "../schema.js";
|
||||
|
||||
/** All known chess fact attributes for a piece entity. */
|
||||
const PIECE_ATTRS = ["PieceType", "Color", "Position", "HasMoved", "Hp"] as const;
|
||||
|
||||
/**
|
||||
* Apply a capture: retract all known facts for the captured piece.
|
||||
* Called after confirming the capture is legal.
|
||||
*/
|
||||
export function applyCapture(session: Session, capturedId: EntityId): void {
|
||||
for (const attr of PIECE_ATTRS) {
|
||||
if (session.contains(capturedId, attr)) {
|
||||
session.retract(capturedId, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all enemy piece entity IDs that a given color could potentially capture.
|
||||
* Returns the IDs of all pieces belonging to the opposite color.
|
||||
*/
|
||||
export function getCapturablePieces(session: Session, attackingColor: PieceColor): EntityId[] {
|
||||
const enemyColor: PieceColor = attackingColor === "white" ? "black" : "white";
|
||||
const facts = session.allFacts();
|
||||
return facts
|
||||
.filter(f => f.attr === "Color" && f.value === enemyColor && f.id > 0)
|
||||
.map(f => f.id as EntityId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if capturing the piece at the given square is legal
|
||||
* (i.e., it's an enemy piece, not an ally).
|
||||
*/
|
||||
export function canCapture(session: Session, square: number, attackingColor: PieceColor): boolean {
|
||||
const facts = session.allFacts();
|
||||
const positionFact = facts.find(f => f.attr === "Position" && f.value === square);
|
||||
if (!positionFact) return false; // no piece at square
|
||||
const colorFact = facts.find(f => f.id === positionFact.id && f.attr === "Color");
|
||||
return colorFact?.value !== undefined && colorFact.value !== attackingColor;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue