diff --git a/packages/chess/src/schema.test.ts b/packages/chess/src/schema.test.ts new file mode 100644 index 0000000..d766b21 --- /dev/null +++ b/packages/chess/src/schema.test.ts @@ -0,0 +1,62 @@ +// @vitest-environment node +import { describe, it, expect } from "vitest"; +import { + GAME_ENTITY, chessFact, oppositeColor, PROMOTION_PIECES, + type Square, +} from "./schema.js"; +import type { EntityId } from "@paratype/rete"; + +const mkId = (n: number) => n as EntityId; + +describe("GAME_ENTITY", () => { + it("is EntityId 0", () => { expect(GAME_ENTITY).toBe(0); }); +}); + +describe("chessFact()", () => { + it("creates typed PieceType fact", () => { + const f = chessFact(mkId(1), "PieceType", "pawn"); + expect(f).toEqual({ id: 1, attr: "PieceType", value: "pawn" }); + }); + it("creates numeric Position fact", () => { + const f = chessFact(mkId(2), "Position", 28); + expect(f.value).toBe(28); + expect(typeof f.value).toBe("number"); + }); + it("creates game-level Turn fact using GAME_ENTITY", () => { + const f = chessFact(GAME_ENTITY, "Turn", "white"); + expect(f.id).toBe(0); + expect(f.attr).toBe("Turn"); + }); + it("creates HasMoved boolean fact", () => { + const f = chessFact(mkId(3), "HasMoved", false); + expect(f.value).toBe(false); + }); +}); + +describe("oppositeColor()", () => { + it("white -> black", () => { expect(oppositeColor("white")).toBe("black"); }); + it("black -> white", () => { expect(oppositeColor("black")).toBe("white"); }); +}); + +describe("PROMOTION_PIECES", () => { + it("has exactly 4 entries", () => { expect(PROMOTION_PIECES).toHaveLength(4); }); + it("excludes king and pawn", () => { + expect(PROMOTION_PIECES).not.toContain("king"); + expect(PROMOTION_PIECES).not.toContain("pawn"); + }); + it("includes queen, rook, bishop, knight", () => { + expect(PROMOTION_PIECES).toContain("queen"); + expect(PROMOTION_PIECES).toContain("rook"); + }); +}); + +describe("Square encoding", () => { + it("a1=0, h1=7, a8=56, h8=63, e4=28", () => { + const a1: Square = 0; + const h8: Square = 63; + const e4: Square = 28; // rank3*8 + file4 + expect(a1).toBe(0); + expect(h8).toBe(63); + expect(e4).toBe(28); + }); +}); diff --git a/packages/chess/src/schema.ts b/packages/chess/src/schema.ts new file mode 100644 index 0000000..d2fd3fa --- /dev/null +++ b/packages/chess/src/schema.ts @@ -0,0 +1,68 @@ +/** + * Chess attribute schema for @paratype/chess. + * All EAV attribute types for the chess game. + * Squares: 0..63 where square = rank * 8 + file + * file: 0=a, 1=b, ..., 7=h + * rank: 0=rank1, 1=rank2, ..., 7=rank8 + * e.g. a1=0, h1=7, a8=56, h8=63, e4=28 + */ +import type { EntityId } from "@paratype/rete"; + +export type PieceType = "pawn" | "knight" | "bishop" | "rook" | "queen" | "king"; +export type PieceColor = "white" | "black"; +export type Square = number; // 0..63 +export type GameStatus = "active" | "checkmate" | "stalemate" | "draw"; +export type GameResult = PieceColor | "draw"; + +/** EntityId reserved for game-level facts (Turn, HalfmoveClock, etc.). */ +export const GAME_ENTITY: EntityId = 0 as EntityId; + +/** + * Maps attribute name to its TypeScript type. + * Used for typed fact construction and WM queries. + */ +export interface ChessAttrMap { + // Piece attributes + PieceType: PieceType; + Color: PieceColor; + Position: Square; + HasMoved: boolean; + // Game-level attributes + Turn: PieceColor; + HalfmoveClock: number; + FullmoveNumber: number; + EnPassantTarget: Square | null; + GameStatus: GameStatus; + Winner: GameResult | null; + // Custom rule attributes (from RULES.md presets) + Hp: number; + PoisonedSquare: boolean; +} + +export type ChessAttrKey = keyof ChessAttrMap; + +/** A typed chess fact triple. */ +export interface ChessFact { + readonly id: EntityId; + readonly attr: K; + readonly value: ChessAttrMap[K]; +} + +/** Construct a typed chess fact. */ +export function chessFact( + id: EntityId, + attr: K, + value: ChessAttrMap[K], +): ChessFact { + return { id, attr, value }; +} + +/** Return the opposite color. */ +export function oppositeColor(color: PieceColor): PieceColor { + return color === "white" ? "black" : "white"; +} + +/** Valid promotion piece types (excludes pawn and king). */ +export const PROMOTION_PIECES: ReadonlyArray = [ + "queen", "rook", "bishop", "knight", +];