import { Chess, type Move, type Square, type Color } from "chess.js"; export type Outcome = | { over: false } | { over: true; reason: "checkmate"; winner: Color } | { over: true; reason: "stalemate" | "insufficient" | "repetition" | "fifty" | "draw" }; export class Game { private chess: Chess; constructor(fen?: string) { this.chess = new Chess(fen); } get fen() { return this.chess.fen(); } get turn(): Color { return this.chess.turn(); } board() { return this.chess.board(); } legalFrom(square: Square): Move[] { return this.chess.moves({ square, verbose: true }); } legalTargets(square: Square): Move[] { return this.legalFrom(square); } allMoves(): Move[] { return this.chess.moves({ verbose: true }); } legalUci(): string[] { return this.allMoves().map((m) => m.from + m.to + (m.promotion ?? "")); } // Accepts a UCI-style from/to (plus promotion) and returns the applied move, or null. play(from: Square, to: Square, promotion?: "q" | "r" | "b" | "n"): Move | null { try { return this.chess.move({ from, to, promotion: promotion ?? "q" }); } catch { return null; } } playUci(uci: string): Move | null { const from = uci.slice(0, 2) as Square; const to = uci.slice(2, 4) as Square; const promo = uci.length > 4 ? (uci[4] as "q" | "r" | "b" | "n") : undefined; return this.play(from, to, promo); } undo(): Move | null { return this.chess.undo(); } history(): Move[] { return this.chess.history({ verbose: true }); } inCheck(): boolean { return this.chess.inCheck(); } get(square: Square) { return this.chess.get(square); } attackers(square: Square, color: Color): Square[] { return this.chess.attackers(square, color); } clone(): Game { return new Game(this.fen); } pieces(color: Color): { square: Square; type: string }[] { const out: { square: Square; type: string }[] = []; for (const row of this.chess.board()) { for (const cell of row) { if (cell && cell.color === color) out.push({ square: cell.square as Square, type: cell.type }); } } return out; } // The square of the side-to-move's king, used to paint the check marker. kingSquare(color: Color): Square | null { for (const row of this.chess.board()) { for (const cell of row) { if (cell && cell.type === "k" && cell.color === color) return cell.square; } } return null; } outcome(): Outcome { if (!this.chess.isGameOver()) return { over: false }; if (this.chess.isCheckmate()) { return { over: true, reason: "checkmate", winner: this.turn === "w" ? "b" : "w" }; } if (this.chess.isStalemate()) return { over: true, reason: "stalemate" }; if (this.chess.isInsufficientMaterial()) return { over: true, reason: "insufficient" }; if (this.chess.isThreefoldRepetition()) return { over: true, reason: "repetition" }; if (this.chess.isDrawByFiftyMoves?.()) return { over: true, reason: "fifty" }; return { over: true, reason: "draw" }; } pgn(): string { return this.chess.pgn(); } loadPgn(pgn: string): boolean { try { this.chess.loadPgn(pgn); return true; } catch { return false; } } load(fen: string): boolean { try { this.chess.load(fen); return true; } catch { return false; } } } export type { Move, Square, Color };