From 0059d99bfefa6432a2b8ad433f8ae02256239375 Mon Sep 17 00:00:00 2001 From: Steven Stavrakis Date: Mon, 3 Jun 2024 15:43:10 -0400 Subject: [PATCH 1/6] comments for everyone --- src/attacks.ts | 134 +++++++++++++++++--- src/board.ts | 92 ++++++++++++-- src/chess.ts | 310 ++++++++++++++++++++++++++++++++++++++++++++++- src/compat.ts | 17 +++ src/fen.ts | 153 ++++++++++++++++++++++- src/san.ts | 40 ++++++ src/setup.ts | 157 +++++++++++++++++++++++- src/squareSet.ts | 236 ++++++++++++++++++++++++++++++++++++ src/transform.ts | 38 ++++++ src/types.ts | 104 +++++++++++++++- src/util.ts | 91 +++++++++++++- 11 files changed, 1328 insertions(+), 44 deletions(-) diff --git a/src/attacks.ts b/src/attacks.ts index 7cdbd357..281880b5 100644 --- a/src/attacks.ts +++ b/src/attacks.ts @@ -16,6 +16,12 @@ import { SquareSet } from './squareSet.js'; import { BySquare, Color, Piece, Square } from './types.js'; import { squareFile, squareRank } from './util.js'; +/** + * Computes the range of squares that can be reached from a given square by a set of deltas. + * @param {Square} square The starting square. + * @param {number[]} deltas An array of deltas representing the offsets from the starting square. + * @returns {SquareSet} The set of squares that can be reached from the starting square. + */ const computeRange = (square: Square, deltas: number[]): SquareSet => { let range = SquareSet.empty(); for (const delta of deltas) { @@ -27,50 +33,97 @@ const computeRange = (square: Square, deltas: number[]): SquareSet => { return range; }; +/** + * Creates a table of values for each square on the chessboard by applying a given function. + * @template T The type of the values in the table. + * @param {(square: Square) => T} f The function to apply to each square. + * @returns {BySquare} The table of values for each square. + */ const tabulate = (f: (square: Square) => T): BySquare => { const table = []; for (let square = 0; square < 64; square++) table[square] = f(square); return table; }; -const KING_ATTACKS = tabulate(sq => computeRange(sq, [-9, -8, -7, -1, 1, 7, 8, 9])); -const KNIGHT_ATTACKS = tabulate(sq => computeRange(sq, [-17, -15, -10, -6, 6, 10, 15, 17])); -const PAWN_ATTACKS = { +/** + * A pre-computed table of king attacks for each square on the chessboard. + * @type {BySquare} + */ +const KING_ATTACKS: BySquare = tabulate(sq => computeRange(sq, [-9, -8, -7, -1, 1, 7, 8, 9])); + +/** + * A pre-computed table of knight attacks for each square on the chessboard. + * @type {BySquare} + */ +const KNIGHT_ATTACKS: BySquare = tabulate(sq => computeRange(sq, [-17, -15, -10, -6, 6, 10, 15, 17])); + +/** + * A pre-computed table of pawn attacks for each square on the chessboard, separated by color. + * @type {{ white: BySquare, black: BySquare }} + */ +const PAWN_ATTACKS: { white: BySquare, black: BySquare } = { white: tabulate(sq => computeRange(sq, [7, 9])), black: tabulate(sq => computeRange(sq, [-7, -9])), }; /** - * Gets squares attacked or defended by a king on `square`. + * Returns the set of squares attacked by a king on a given square. + * @param {Square} square The square occupied by the king. + * @returns {SquareSet} The set of squares attacked by the king. */ export const kingAttacks = (square: Square): SquareSet => KING_ATTACKS[square]; /** - * Gets squares attacked or defended by a knight on `square`. + * Returns the set of squares attacked by a knight on a given square. + * @param {Square} square The square occupied by the knight. + * @returns {SquareSet} The set of squares attacked by the knight. */ export const knightAttacks = (square: Square): SquareSet => KNIGHT_ATTACKS[square]; /** - * Gets squares attacked or defended by a pawn of the given `color` - * on `square`. + * Returns the set of squares attacked by a pawn of a given color on a given square. + * @param {Color} color The color of the pawn. + * @param {Square} square The square occupied by the pawn. + * @returns {SquareSet} The set of squares attacked by the pawn. */ export const pawnAttacks = (color: Color, square: Square): SquareSet => PAWN_ATTACKS[color][square]; + +/** + * A pre-computed table of file ranges for each square on the chessboard. + */ const FILE_RANGE = tabulate(sq => SquareSet.fromFile(squareFile(sq)).without(sq)); + +/** + * A pre-computed table of rank ranges for each square on the chessboard. + */ const RANK_RANGE = tabulate(sq => SquareSet.fromRank(squareRank(sq)).without(sq)); +/** + * A pre-computed table of diagonal ranges for each square on the chessboard. + */ const DIAG_RANGE = tabulate(sq => { const diag = new SquareSet(0x0804_0201, 0x8040_2010); const shift = 8 * (squareRank(sq) - squareFile(sq)); return (shift >= 0 ? diag.shl64(shift) : diag.shr64(-shift)).without(sq); }); +/** + * A pre-computed table of anti-diagonal ranges for each square on the chessboard. + */ const ANTI_DIAG_RANGE = tabulate(sq => { const diag = new SquareSet(0x1020_4080, 0x0102_0408); const shift = 8 * (squareRank(sq) + squareFile(sq) - 7); return (shift >= 0 ? diag.shl64(shift) : diag.shr64(-shift)).without(sq); }); +/** + * Computes the attacks on a given bit position using a hyperbola quintessence algorithm. + * @param {SquareSet} bit The bit position to compute attacks for. + * @param {SquareSet} range The range of squares to consider for attacks. + * @param {SquareSet} occupied The set of occupied squares on the chessboard. + * @returns {SquareSet} The set of squares that attack the given bit position. + */ const hyperbola = (bit: SquareSet, range: SquareSet, occupied: SquareSet): SquareSet => { let forward = occupied.intersect(range); let reverse = forward.bswap64(); // Assumes no more than 1 bit per rank @@ -79,9 +132,21 @@ const hyperbola = (bit: SquareSet, range: SquareSet, occupied: SquareSet): Squar return forward.xor(reverse.bswap64()).intersect(range); }; +/** + * Computes the file attacks for a given square and occupied squares on the chessboard. + * @param {Square} square The square to compute file attacks for. + * @param {SquareSet} occupied The set of occupied squares on the chessboard. + * @returns {SquareSet} The set of squares that attack the given square along the file. + */ const fileAttacks = (square: Square, occupied: SquareSet): SquareSet => hyperbola(SquareSet.fromSquare(square), FILE_RANGE[square], occupied); +/** + * Computes the rank attacks for a given square and occupied squares on the chessboard. + * @param {Square} square The square to compute rank attacks for. + * @param {SquareSet} occupied The set of occupied squares on the chessboard. + * @returns {SquareSet} The set of squares that attack the given square along the rank. + */ const rankAttacks = (square: Square, occupied: SquareSet): SquareSet => { const range = RANK_RANGE[square]; let forward = occupied.intersect(range); @@ -92,8 +157,13 @@ const rankAttacks = (square: Square, occupied: SquareSet): SquareSet => { }; /** - * Gets squares attacked or defended by a bishop on `square`, given `occupied` - * squares. + * Returns squares attacked or defended by a bishop on `square`, given `occupied` squares. + * + * @param {Square} square The bitboard square index where the bishop is located. + * @param {SquareSet} occupied The set of occupied squares on the chessboard. + * @returns {SquareSet} The set of squares attacked or defended by the bishop. + * @description Uses `occupied` to determine blocked squares and exclude them from the result. + * The hyperbola quintessence algorithm is used to efficiently compute the bishop attacks. */ export const bishopAttacks = (square: Square, occupied: SquareSet): SquareSet => { const bit = SquareSet.fromSquare(square); @@ -101,22 +171,37 @@ export const bishopAttacks = (square: Square, occupied: SquareSet): SquareSet => }; /** - * Gets squares attacked or defended by a rook on `square`, given `occupied` - * squares. + * Returns squares attacked or defended by a rook on `square`, given `occupied` squares. + * + * @param {Square} square The bitboard square index where the rook is located. + * @param {SquareSet} occupied The set of occupied squares on the chessboard. + * @returns {SquareSet} The set of squares attacked or defended by the rook. + * @description Uses `occupied` to determine blocked squares and exclude them from the result. + * The hyperbola quintessence algorithm is used to efficiently compute the rook attacks. */ export const rookAttacks = (square: Square, occupied: SquareSet): SquareSet => fileAttacks(square, occupied).xor(rankAttacks(square, occupied)); /** - * Gets squares attacked or defended by a queen on `square`, given `occupied` - * squares. + * Returns squares attacked or defended by a queen on `square`, given `occupied` squares. + * + * @param {Square} square The bitboard square index where the queen is located. + * @param {SquareSet} occupied The set of occupied squares on the chessboard. + * @returns {SquareSet} The set of squares attacked or defended by the queen. + * @description Uses `occupied` to determine blocked squares and exclude them from the result. + * The hyperbola quintessence algorithm is used to efficiently compute the queen attacks. */ export const queenAttacks = (square: Square, occupied: SquareSet): SquareSet => bishopAttacks(square, occupied).xor(rookAttacks(square, occupied)); /** - * Gets squares attacked or defended by a `piece` on `square`, given - * `occupied` squares. + * Returns squares attacked or defended by a `piece` on `square`, given `occupied` squares. + * + * @param {Piece} piece The chess piece object. + * @param {Square} square The bitboard square index where the piece is located. + * @param {SquareSet} occupied The set of occupied squares on the chessboard. + * @returns {SquareSet} The set of squares attacked or defended by the piece. + * @description Uses `occupied` to determine blocked squares and exclude them from the result. */ export const attacks = (piece: Piece, square: Square, occupied: SquareSet): SquareSet => { switch (piece.role) { @@ -136,8 +221,12 @@ export const attacks = (piece: Piece, square: Square, occupied: SquareSet): Squa }; /** - * Gets all squares of the rank, file or diagonal with the two squares - * `a` and `b`, or an empty set if they are not aligned. + * Returns all squares of the rank, file, or diagonal with the two squares `a` and `b`. + * + * @param {Square} a The first bitboard square index. + * @param {Square} b The second bitboard square index. + * @returns {SquareSet} The set of squares aligned with `a` and `b`. + * @description Returns an empty set if `a` and `b` are not on the same rank, file, or diagonal. */ export const ray = (a: Square, b: Square): SquareSet => { const other = SquareSet.fromSquare(b); @@ -149,10 +238,15 @@ export const ray = (a: Square, b: Square): SquareSet => { }; /** - * Gets all squares between `a` and `b` (bounds not included), or an empty set - * if they are not on the same rank, file or diagonal. + * Returns all squares between `a` and `b` (bounds not included). + * Works in all directions and diagonals. + * + * @param {Square} a The first bitboard square index. + * @param {Square} b The second bitboard square index. + * @returns {SquareSet} The set of squares between `a` and `b`. + * @description Returns an empty set if `a` and `b` are not on the same rank, file, or diagonal. */ export const between = (a: Square, b: Square): SquareSet => ray(a, b) .intersect(SquareSet.full().shl64(a).xor(SquareSet.full().shl64(b))) - .withoutFirst(); + .withoutFirst(); \ No newline at end of file diff --git a/src/board.ts b/src/board.ts index 14deafec..1cf5638c 100644 --- a/src/board.ts +++ b/src/board.ts @@ -12,26 +12,41 @@ import { ByColor, ByRole, Color, COLORS, Piece, Role, ROLES, Square } from './ty export class Board implements Iterable<[Square, Piece]>, ByRole, ByColor { /** * All occupied squares. + * @type {SquareSet} */ occupied: SquareSet; + /** - * All squares occupied by pieces known to be promoted. This information is - * relevant in chess variants like Crazyhouse. + * All squares occupied by pieces known to be promoted. + * This information is relevant in chess variants like Crazyhouse. + * @type {SquareSet} */ promoted: SquareSet; + /** @type {SquareSet} */ white: SquareSet; + /** @type {SquareSet} */ black: SquareSet; + /** @type {SquareSet} */ pawn: SquareSet; + /** @type {SquareSet} */ knight: SquareSet; + /** @type {SquareSet} */ bishop: SquareSet; + /** @type {SquareSet} */ rook: SquareSet; + /** @type {SquareSet} */ queen: SquareSet; + /** @type {SquareSet} */ king: SquareSet; - private constructor() {} + private constructor() { } + /** + * Creates a new board with the default starting position for standard chess. + * @returns {Board} The default board. + */ static default(): Board { const board = new Board(); board.reset(); @@ -54,12 +69,19 @@ export class Board implements Iterable<[Square, Piece]>, ByRole, ByCo this.king = new SquareSet(0x10, 0x1000_0000); } + /** + * Creates a new empty board. + * @returns {Board} The empty board. + */ static empty(): Board { const board = new Board(); board.clear(); return board; } + /** + * Clears the board by removing all pieces. + */ clear(): void { this.occupied = SquareSet.empty(); this.promoted = SquareSet.empty(); @@ -67,6 +89,10 @@ export class Board implements Iterable<[Square, Piece]>, ByRole, ByCo for (const role of ROLES) this[role] = SquareSet.empty(); } + /** + * Creates a clone of the current board. + * @returns {Board} The cloned board. + */ clone(): Board { const board = new Board(); board.occupied = this.occupied; @@ -76,12 +102,22 @@ export class Board implements Iterable<[Square, Piece]>, ByRole, ByCo return board; } + /** + * Gets the color of the piece on the given square. + * @param {Square} square The square to check. + * @returns {Color | undefined} The color of the piece on the square, or undefined if the square is empty. + */ getColor(square: Square): Color | undefined { if (this.white.has(square)) return 'white'; if (this.black.has(square)) return 'black'; return; } + /** + * Gets the role of the piece on the given square. + * @param {Square} square The square to check. + * @returns {Role | undefined} The role of the piece on the square, or undefined if the square is empty. + */ getRole(square: Square): Role | undefined { for (const role of ROLES) { if (this[role].has(square)) return role; @@ -89,6 +125,11 @@ export class Board implements Iterable<[Square, Piece]>, ByRole, ByCo return; } + /** + * Gets the piece on the given square. + * @param {Square} square The square to check. + * @returns {Piece | undefined} The piece on the square, or undefined if the square is empty. + */ get(square: Square): Piece | undefined { const color = this.getColor(square); if (!color) return; @@ -98,7 +139,9 @@ export class Board implements Iterable<[Square, Piece]>, ByRole, ByCo } /** - * Removes and returns the piece from the given `square`, if any. + * Removes and returns the piece from the given square, if any. + * @param {Square} square The square to remove the piece from. + * @returns {Piece | undefined} The removed piece, or undefined if the square was empty. */ take(square: Square): Piece | undefined { const piece = this.get(square); @@ -112,8 +155,10 @@ export class Board implements Iterable<[Square, Piece]>, ByRole, ByCo } /** - * Put `piece` onto `square`, potentially replacing an existing piece. - * Returns the existing piece, if any. + * Puts a piece onto the given square, potentially replacing an existing piece. + * @param {Square} square The square to put the piece on. + * @param {Piece} piece The piece to put on the square. + * @returns {Piece | undefined} The replaced piece, or undefined if the square was empty. */ set(square: Square, piece: Piece): Piece | undefined { const old = this.take(square); @@ -124,37 +169,68 @@ export class Board implements Iterable<[Square, Piece]>, ByRole, ByCo return old; } + /** + * Checks if the given square is occupied by a piece. + * @param {Square} square The square to check. + * @returns {boolean} True if the square is occupied, false otherwise. + */ has(square: Square): boolean { return this.occupied.has(square); } + /** + * Iterates over all occupied squares and their corresponding pieces. + * @yields {[Square, Piece]} The square and piece for each occupied square. + */ *[Symbol.iterator](): Iterator<[Square, Piece]> { for (const square of this.occupied) { yield [square, this.get(square)!]; } } + /** + * Gets the set of squares occupied by pieces of the given color and role. + * @param {Color} color The color of the pieces. + * @param {Role} role The role of the pieces. + * @returns {SquareSet} The set of squares occupied by pieces of the given color and role. + */ pieces(color: Color, role: Role): SquareSet { return this[color].intersect(this[role]); } + /** + * Gets the set of squares occupied by rooks and queens. + * @returns {SquareSet} The set of squares occupied by rooks and queens. + */ rooksAndQueens(): SquareSet { return this.rook.union(this.queen); } + /** + * Gets the set of squares occupied by bishops and queens. + * @returns {SquareSet} The set of squares occupied by bishops and queens. + */ bishopsAndQueens(): SquareSet { return this.bishop.union(this.queen); } /** - * Finds the unique king of the given `color`, if any. + * Finds the unique king of the given color, if any. + * @param {Color} color The color of the king. + * @returns {Square | undefined} The square of the king, or undefined if the king is not on the board. */ kingOf(color: Color): Square | undefined { return this.pieces(color, 'king').singleSquare(); } } +/** + * Checks if two boards are equal. + * @param {Board} left The first board. + * @param {Board} right The second board. + * @returns {boolean} True if the boards are equal, false otherwise. + */ export const boardEquals = (left: Board, right: Board): boolean => left.white.equals(right.white) && left.promoted.equals(right.promoted) - && ROLES.every(role => left[role].equals(right[role])); + && ROLES.every(role => left[role].equals(right[role])); \ No newline at end of file diff --git a/src/chess.ts b/src/chess.ts index 50b3e32e..d2b4548c 100644 --- a/src/chess.ts +++ b/src/chess.ts @@ -30,6 +30,11 @@ import { } from './types.js'; import { defined, kingCastlesTo, opposite, rookCastlesTo, squareRank } from './util.js'; +/** + * Enum representing illegal setup states. + * @readonly + * @enum {string} + */ export enum IllegalSetup { Empty = 'ERR_EMPTY', OppositeCheck = 'ERR_OPPOSITE_CHECK', @@ -38,8 +43,20 @@ export enum IllegalSetup { Variant = 'ERR_VARIANT', } -export class PositionError extends Error {} - +/** + * Custom error class for position errors. + * @extends Error + */ +export class PositionError extends Error { } + +/** + * Calculates the attacking squares for a given square and attacker color. + * @param {Square} square - The target square. + * @param {Color} attacker - The attacking color. + * @param {Board} board - The chess board. + * @param {SquareSet} occupied - The occupied squares on the board. + * @returns {SquareSet} The squares from which the target square is attacked. + */ const attacksTo = (square: Square, attacker: Color, board: Board, occupied: SquareSet): SquareSet => board[attacker].intersect( rookAttacks(square, occupied) @@ -50,13 +67,23 @@ const attacksTo = (square: Square, attacker: Color, board: Board, occupied: Squa .union(pawnAttacks(opposite(attacker), square).intersect(board.pawn)), ); +/** +* TODO: Not sure what this is +*/ export class Castles { + /** @type {SquareSet} */ castlingRights: SquareSet; + /** @type {ByColor>} */ rook: ByColor>; + /** @type {ByColor>} */ path: ByColor>; - private constructor() {} + private constructor() { } + /** + * Creates a new Castles instance with default castling rights. + * @returns {Castles} The default Castles instance. + */ static default(): Castles { const castles = new Castles(); castles.castlingRights = SquareSet.corners(); @@ -71,6 +98,10 @@ export class Castles { return castles; } + /** + * Creates a new empty Castles instance. + * @returns {Castles} The empty Castles instance. + */ static empty(): Castles { const castles = new Castles(); castles.castlingRights = SquareSet.empty(); @@ -85,6 +116,10 @@ export class Castles { return castles; } + /** + * Creates a clone of the current Castles instance. + * @returns {Castles} The cloned Castles instance. + */ clone(): Castles { const castles = new Castles(); castles.castlingRights = this.castlingRights; @@ -99,6 +134,13 @@ export class Castles { return castles; } + /** + * Adds castling rights for the given color and side. + * @param {Color} color - The color. + * @param {CastlingSide} side - The castling side. + * @param {Square} king - The king's square. + * @param {Square} rook - The rook's square. + */ private add(color: Color, side: CastlingSide, king: Square, rook: Square): void { const kingTo = kingCastlesTo(color, side); const rookTo = rookCastlesTo(color, side); @@ -111,6 +153,11 @@ export class Castles { .without(rook); } + /** + * Creates a Castles instance from the given setup. + * @param {Setup} setup - The chess setup. + * @returns {Castles} The Castles instance derived from the setup. + */ static fromSetup(setup: Setup): Castles { const castles = Castles.empty(); const rooks = setup.castlingRights.intersect(setup.board.rook); @@ -127,6 +174,10 @@ export class Castles { return castles; } + /** + * Discards castling rights for the rook on the given square. + * @param {Square} square - The square of the rook. + */ discardRook(square: Square): void { if (this.castlingRights.has(square)) { this.castlingRights = this.castlingRights.without(square); @@ -138,6 +189,10 @@ export class Castles { } } + /** + * Discards castling rights for the given color. + * @param {Color} color - The color to discard castling rights for. + */ discardColor(color: Color): void { this.castlingRights = this.castlingRights.diff(SquareSet.backrank(color)); this.rook[color].a = undefined; @@ -145,6 +200,16 @@ export class Castles { } } +/** + * TODO: Not sure what this is + * Represents the context of a chess position. + * @interface + * @property {Square | undefined} king - The square of the king. + * @property {SquareSet} blockers - The set of blocking squares. + * @property {SquareSet} checkers - The set of checking squares. + * @property {boolean} variantEnd - Whether the variant has ended. + * @property {boolean} mustCapture - Whether a capture is required. + */ export interface Context { king: Square | undefined; blockers: SquareSet; @@ -153,18 +218,60 @@ export interface Context { mustCapture: boolean; } +/** + * Abstract base class for chess positions. + * @abstract + */ export abstract class Position { + /** + * The current Board. + */ board: Board; + + /** + * Represents the taken pieces. + */ pockets: Material | undefined; + + /** + * The current turn. + */ turn: Color; + + /** + * TODO: Not sure what this is + */ castles: Castles; + + /** + * TODO: Not sure what this is + */ epSquare: Square | undefined; + + /** + * TODO: Not sure what this is + */ remainingChecks: RemainingChecks | undefined; + /** + * Number of times pieces have been moved. + * For example: 1.e4 is a halfmove, but 1.e4 e5 is a fullmove. + */ halfmoves: number; + /** + * Number of times both sides have played a move. + * For example: 1.e4 e5 1.e5 is a fullmove, but 1.e4 e5 1.e5 e6 is two fullmoves. + */ fullmoves: number; - protected constructor(readonly rules: Rules) {} + /** + * Creates a new Position instance. + * @param {Rules} rules - The chess rules. + */ + protected constructor(readonly rules: Rules) { } + /** + * Resets the position to the starting position. + */ reset() { this.board = Board.default(); this.pockets = undefined; @@ -176,6 +283,11 @@ export abstract class Position { this.fullmoves = 1; } + /** + * Sets up the position from the given setup without validation. + * @param {Setup} setup - The chess setup. + * @protected + */ protected setupUnchecked(setup: Setup) { this.board = setup.board.clone(); this.board.promoted = SquareSet.empty(); @@ -200,16 +312,33 @@ export abstract class Position { // - hasInsufficientMaterial() // - isStandardMaterial() + /** + * Calculates the attacking squares for the king on the given square by the given color. + * @param {Square} square - The square of the king. + * @param {Color} attacker - The attacking color. + * @param {SquareSet} occupied - The occupied squares on the board. + * @returns {SquareSet} The squares from which the king is attacked. + */ kingAttackers(square: Square, attacker: Color, occupied: SquareSet): SquareSet { return attacksTo(square, attacker, this.board, occupied); } + /** + * Executes a capture at the given square. + * @param {Square} square - The square where the capture occurs. + * @param {Piece} captured - The captured piece. + * @protected + */ protected playCaptureAt(square: Square, captured: Piece): void { this.halfmoves = 0; if (captured.role === 'rook') this.castles.discardRook(square); if (this.pockets) this.pockets[opposite(captured.color)][captured.promoted ? 'pawn' : captured.role]++; } + /** + * Returns the context of the current position. + * @returns {Context} The position context. + */ ctx(): Context { const variantEnd = this.isVariantEnd(); const king = this.board.kingOf(this.turn); @@ -235,6 +364,10 @@ export abstract class Position { }; } + /** + * Creates a clone of the current position. + * @returns {Position} The cloned position. + */ clone(): Position { const pos = new (this as any).constructor(); pos.board = this.board.clone(); @@ -248,6 +381,11 @@ export abstract class Position { return pos; } + /** + * Validates the current position. + * @returns {Result} The validation result. + * @protected + */ protected validate(): Result { if (this.board.occupied.isEmpty()) return Result.err(new PositionError(IllegalSetup.Empty)); if (this.board.king.size() !== 2) return Result.err(new PositionError(IllegalSetup.Kings)); @@ -267,10 +405,21 @@ export abstract class Position { return Result.ok(undefined); } + /** + * Calculates the possible destination squares for a drop move. + * @param {Context} [_ctx] The optional context for the move generation. + * @returns {SquareSet} The set of possible destination squares for a drop move. + */ dropDests(_ctx?: Context): SquareSet { return SquareSet.empty(); } + /** + * Calculates the possible destination squares for a piece on a given square. + * @param {Square} square The square of the piece. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {SquareSet} The set of possible destination squares. + */ dests(square: Square, ctx?: Context): SquareSet { ctx = ctx || this.ctx(); if (ctx.variantEnd) return SquareSet.empty(); @@ -323,14 +472,28 @@ export abstract class Position { return pseudo; } + /** + * TODO: Not sure what this is + * @returns {boolean} Whether the position is a variant end. + */ isVariantEnd(): boolean { return false; } + /** + * TODO: Not sure what this is + * @param _ctx + * @returns + */ variantOutcome(_ctx?: Context): Outcome | undefined { return; } + /** + * Returns whether the given side has insufficient material to continue the game. + * @param {Color} color the side to check + * @returns {boolean} `true` if the side has insufficient material, `false` otherwise. + */ hasInsufficientMaterial(color: Color): boolean { if (this.board[color].intersect(this.board.pawn.union(this.board.rooksAndQueens())).nonEmpty()) return false; if (this.board[color].intersects(this.board.knight)) { @@ -349,6 +512,10 @@ export abstract class Position { // The following should be identical in all subclasses + /** + * Returns a `Setup` instance representing the current position. + * @returns {Setup} The setup instance. + */ toSetup(): Setup { return { board: this.board.clone(), @@ -362,10 +529,19 @@ export abstract class Position { }; } + /** + * Returns whether both sides have insufficient material to continue the game. + * @returns {boolean} `true` if both sides have insufficient material to continue the game, `false` otherwise. + */ isInsufficientMaterial(): boolean { return COLORS.every(color => this.hasInsufficientMaterial(color)); } + /** + * Checks if there are any possible destination squares for the current player's moves. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {boolean} `true` if there are possible destination squares, `false` otherwise. + */ hasDests(ctx?: Context): boolean { ctx = ctx || this.ctx(); for (const square of this.board[this.turn]) { @@ -373,7 +549,12 @@ export abstract class Position { } return this.dropDests(ctx).nonEmpty(); } - + /** + * Checks if a given move is legal in the current position. + * @param {Move} move The move to check for legality. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {boolean} `true` if the move is legal, `false` otherwise. + */ isLegal(move: Move, ctx?: Context): boolean { if (isDrop(move)) { if (!this.pockets || this.pockets[this.turn][move.role] <= 0) return false; @@ -388,26 +569,50 @@ export abstract class Position { } } + /** + * Checks if the current position is a check. + * @returns {boolean} `true` if the current position is a check, `false` otherwise. + */ isCheck(): boolean { const king = this.board.kingOf(this.turn); return defined(king) && this.kingAttackers(king, opposite(this.turn), this.board.occupied).nonEmpty(); } + /** + * Checks if the current position is an end position. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {boolean} `true` if the current position is an end position, `false` otherwise. + */ isEnd(ctx?: Context): boolean { if (ctx ? ctx.variantEnd : this.isVariantEnd()) return true; return this.isInsufficientMaterial() || !this.hasDests(ctx); } + /** + * Checks if the current position is a checkmate. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {boolean} `true` if the current position is a checkmate, `false` otherwise. + */ isCheckmate(ctx?: Context): boolean { ctx = ctx || this.ctx(); return !ctx.variantEnd && ctx.checkers.nonEmpty() && !this.hasDests(ctx); } + /** + * Checks if the current position is a stalemate. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {boolean} `true` if the current position is a stalemate, `false` otherwise. + */ isStalemate(ctx?: Context): boolean { ctx = ctx || this.ctx(); return !ctx.variantEnd && ctx.checkers.isEmpty() && !this.hasDests(ctx); } + /** + * Determines the outcome of the current position. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {Outcome | undefined} The outcome of the current position, or undefined if the position is not an end position. + */ outcome(ctx?: Context): Outcome | undefined { const variantOutcome = this.variantOutcome(ctx); if (variantOutcome) return variantOutcome; @@ -417,6 +622,11 @@ export abstract class Position { else return; } + /** + * Calculates all possible destination squares for each piece of the current player. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {Map} A map of source squares to their corresponding sets of possible destination squares. + */ allDests(ctx?: Context): Map { ctx = ctx || this.ctx(); const d = new Map(); @@ -427,6 +637,11 @@ export abstract class Position { return d; } + /** + * Plays the given move to the board. + * @param {Move} move A move to be played. + * @returns {void} + */ play(move: Move): void { const turn = this.turn; const epSquare = this.epSquare; @@ -490,23 +705,43 @@ export class Chess extends Position { super('chess'); } + /** + * Create a new chess game with a default setup. + * @returns {Chess} + */ static default(): Chess { const pos = new this(); pos.reset(); return pos; } + /** + * Create a new, unchecked chess game from a setup. + * TODO: There is validation, but I'm not sure what it is. + * @param {Setup} setup - The chess setup. + * @returns Chess or an error. + */ static fromSetup(setup: Setup): Result { const pos = new this(); pos.setupUnchecked(setup); return pos.validate().map(_ => pos); } + /** + * Clone the current chess game. + * @returns {Chess} The cloned chess game. + */ clone(): Chess { return super.clone() as Chess; } } +/** + * Returns the square the en passant can be played to from given position and square. + * @param pos {Position} + * @param square {Square} + * @returns {Square | undefined} Any square the en passant can be played to. + */ const validEpSquare = (pos: Position, square: Square | undefined): Square | undefined => { if (!defined(square)) return; const epRank = pos.turn === 'white' ? 5 : 2; @@ -518,6 +753,11 @@ const validEpSquare = (pos: Position, square: Square | undefined): Square | unde return square; }; +/** + * Finds and returns all legal en passant squares in the position. + * @param {Position} pos + * @returns {Square | undefined} + */ const legalEpSquare = (pos: Position): Square | undefined => { if (!defined(pos.epSquare)) return; const ctx = pos.ctx(); @@ -529,6 +769,13 @@ const legalEpSquare = (pos: Position): Square | undefined => { return; }; +/** + * TODO: Not sure what this does + * @param {Position} pos + * @param {Square} pawnFrom + * @param {Context} ctx + * @returns {boolean} `true` if can capture, `false` otherwise + */ const canCaptureEp = (pos: Position, pawnFrom: Square, ctx: Context): boolean => { if (!defined(pos.epSquare)) return false; if (!pawnAttacks(pos.turn, pawnFrom).has(pos.epSquare)) return false; @@ -545,19 +792,37 @@ const canCaptureEp = (pos: Position, pawnFrom: Square, ctx: Context): boolean => .isEmpty(); }; + +/** + * Checks various castling conditions and returns a set of squares that can be castled to. + * @param {Position} pos + * @param {CastlingSide} side + * @param {Context} ctx + * @returns {SquareSet} A set of squares that can be castled to. Can be empty. + */ const castlingDest = (pos: Position, side: CastlingSide, ctx: Context): SquareSet => { if (!defined(ctx.king) || ctx.checkers.nonEmpty()) return SquareSet.empty(); const rook = pos.castles.rook[pos.turn][side]; if (!defined(rook)) return SquareSet.empty(); + + // If any square in the castilng path is occupied, return an empty set if (pos.castles.path[pos.turn][side].intersects(pos.board.occupied)) return SquareSet.empty(); + // Find the castling square const kingTo = kingCastlesTo(pos.turn, side); + + // Find the path of the king to the castling square const kingPath = between(ctx.king, kingTo); + + // Remove the king position const occ = pos.board.occupied.without(ctx.king); + + for (const sq of kingPath) { if (pos.kingAttackers(sq, opposite(pos.turn), occ).nonEmpty()) return SquareSet.empty(); } + const rookTo = rookCastlesTo(pos.turn, side); const after = pos.board.occupied.toggle(ctx.king).toggle(rook).toggle(rookTo); if (pos.kingAttackers(kingTo, opposite(pos.turn), after).nonEmpty()) return SquareSet.empty(); @@ -603,6 +868,14 @@ export const equalsIgnoreMoves = (left: Position, right: Position): boolean => && ((right.remainingChecks && left.remainingChecks?.equals(right.remainingChecks)) || (!left.remainingChecks && !right.remainingChecks)); +/** + * TODO: unsure + * + * I believe this takes in a move and a position, and from that determines what side is being castled to? + * @param pos + * @param move + * @returns {CastlingSide | undefined} + */ export const castlingSide = (pos: Position, move: Move): CastlingSide | undefined => { if (isDrop(move)) return; const delta = move.to - move.from; @@ -611,6 +884,12 @@ export const castlingSide = (pos: Position, move: Move): CastlingSide | undefine return delta > 0 ? 'h' : 'a'; }; +/** + * TODO: unsure + * @param pos + * @param move + * @returns + */ export const normalizeMove = (pos: Position, move: Move): Move => { const side = castlingSide(pos, move); if (!side) return move; @@ -621,6 +900,14 @@ export const normalizeMove = (pos: Position, move: Move): Move => { }; }; +/** + * TODO: unsure + * + * I think this determines whether a given side has more pieces than normal? + * @param board + * @param color + * @returns + */ export const isStandardMaterialSide = (board: Board, color: Color): boolean => { const promoted = Math.max(board.pieces(color, 'queen').size() - 1, 0) + Math.max(board.pieces(color, 'rook').size() - 2, 0) @@ -630,9 +917,22 @@ export const isStandardMaterialSide = (board: Board, color: Color): boolean => { return board.pieces(color, 'pawn').size() + promoted <= 8; }; +/** + * TODO: unsure + * + * I think this returns whether the total amount of material on the board is within the bounds + * of what is expected in standard chess. + * @param pos + * @returns + */ export const isStandardMaterial = (pos: Chess): boolean => COLORS.every(color => isStandardMaterialSide(pos.board, color)); +/** + * TODO: no clue here + * @param pos + * @returns + */ export const isImpossibleCheck = (pos: Position): boolean => { const ourKing = pos.board.kingOf(pos.turn); if (!defined(ourKing)) return false; diff --git a/src/compat.ts b/src/compat.ts index fbc4fcdf..a00ce5d5 100644 --- a/src/compat.ts +++ b/src/compat.ts @@ -22,6 +22,8 @@ export interface ChessgroundDestsOpts { * Includes both possible representations of castling moves (unless * `chess960` mode is enabled), so that the `rookCastles` option will work * correctly. + * @param {Position} pos + * @param {ChessgroundDestsOpts} [opts] */ export const chessgroundDests = (pos: Position, opts?: ChessgroundDestsOpts): Map => { const result = new Map(); @@ -43,6 +45,11 @@ export const chessgroundDests = (pos: Position, opts?: ChessgroundDestsOpts): Ma return result; }; +/** + * Converts a move to Chessground format. + * @param {Move} move + * @returns {SquareName[]} + */ export const chessgroundMove = (move: Move): SquareName[] => isDrop(move) ? [makeSquare(move.to)] : [makeSquare(move.from), makeSquare(move.to)]; @@ -59,6 +66,11 @@ export const scalachessCharPair = (move: Move): string => : 35 + move.to, ); +/** + * Converts chessops chess variant names to lichess chess rule names + * @param variant + * @returns {Rules} + */ export const lichessRules = ( variant: | 'standard' @@ -88,6 +100,11 @@ export const lichessRules = ( } }; +/** + * Conversts chessops rule name to lichess variant name. + * @param rules + * @returns + */ export const lichessVariant = ( rules: Rules, ): 'standard' | 'antichess' | 'kingOfTheHill' | 'threeCheck' | 'atomic' | 'horde' | 'racingKings' | 'crazyhouse' => { diff --git a/src/fen.ts b/src/fen.ts index b257fc72..072dc8de 100644 --- a/src/fen.ts +++ b/src/fen.ts @@ -12,6 +12,9 @@ export const EMPTY_BOARD_FEN = '8/8/8/8/8/8/8/8'; export const EMPTY_EPD = EMPTY_BOARD_FEN + ' w - -'; export const EMPTY_FEN = EMPTY_EPD + ' 0 1'; +/** + * ENUM representing the possible FEN errors + */ export enum InvalidFen { Fen = 'ERR_FEN', Board = 'ERR_BOARD', @@ -24,7 +27,7 @@ export enum InvalidFen { Fullmoves = 'ERR_FULLMOVES', } -export class FenError extends Error {} +export class FenError extends Error { } const nthIndexOf = (haystack: string, needle: string, n: number): number => { let index = haystack.indexOf(needle); @@ -42,6 +45,12 @@ const charToPiece = (ch: string): Piece | undefined => { return role && { role, color: ch.toLowerCase() === ch ? 'black' : 'white' }; }; +/** + * TODO: what is a "boardPart"? + * Takes a FEN and produces a Board object representing it + * @param boardPart + * @returns {Result} + */ export const parseBoardFen = (boardPart: string): Result => { const board = Board.empty(); let rank = 7; @@ -72,6 +81,28 @@ export const parseBoardFen = (boardPart: string): Result => { return Result.ok(board); }; +/** + * Parses the pockets part of a FEN (Forsyth-Edwards Notation) string and returns a Material object. + * + * @param {string} pocketPart - The pockets part of the FEN string. + * @returns {Result} The parsed Material object if successful, or a FenError if parsing fails. + * + * @throws {FenError} Throws a FenError if the pockets part is invalid. + * + * @example + * const pocketPart = "RNBQKBNRPPPPPPPP"; + * const result = parsePockets(pocketPart); + * if (result.isOk()) { + * const pockets = result.value; + * // Access pockets properties + * const whitePawns = pockets.white.pawn; + * const blackRooks = pockets.black.rook; + * // ... + * } else { + * const error = result.error; + * console.error("Pockets parsing error:", error); + * } + */ export const parsePockets = (pocketPart: string): Result => { if (pocketPart.length > 64) return Result.err(new FenError(InvalidFen.Pockets)); const pockets = Material.empty(); @@ -83,6 +114,13 @@ export const parsePockets = (pocketPart: string): Result => return Result.ok(pockets); }; +/** + * Parses the castling part of a FEN string and returns the corresponding castling rights as a SquareSet. + * + * @param {Board} board - The chess board. + * @param {string} castlingPart - The castling part of the FEN string. + * @returns {Result} The castling rights as a SquareSet if parsing is successful, or a FenError if parsing fails. + */ export const parseCastlingFen = (board: Board, castlingPart: string): Result => { let castlingRights = SquareSet.empty(); if (castlingPart === '-') return Result.ok(castlingRights); @@ -109,6 +147,32 @@ export const parseCastlingFen = (board: Board, castlingPart: string): Result} The RemainingChecks object if parsing is successful, or a FenError if parsing fails. + * + * @example + * // Provided some arbitrary FEN containing a check + * // Parsing remaining checks in the format "+2+3" + * const result1 = parseRemainingChecks("+2+3"); + * if (result1.isOk()) { + * const remainingChecks = result1.value; // RemainingChecks object with white: 1, black: 0 + * } + * + * @example + * // Provided some arbitrary FEN containing a check + * // Parsing remaining checks in the format "2+3" + * const result2 = parseRemainingChecks("2+3"); + * if (result2.isOk()) { + * const remainingChecks = result2.value; // RemainingChecks object with white: 2, black: 3 + * } + * + * @throws {FenError} Throws a FenError if the remaining checks part is invalid. + */ export const parseRemainingChecks = (part: string): Result => { const parts = part.split('+'); if (parts.length === 3 && parts[0] === '') { @@ -128,6 +192,28 @@ export const parseRemainingChecks = (part: string): Result} The parsed Setup object if successful, or a FenError if parsing fails. + * + * @throws {FenError} Throws a FenError if the FEN string is invalid. + * + * @example + * const fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + * const result = parseFen(fen); + * if (result.isOk()) { + * const setup = result.value; + * // Access setup properties + * const board = setup.board; + * const pockets = setup.pockets; + * // ... + * } else { + * const error = result.error; + * console.error("FEN parsing error:", error); + * } + */ export const parseFen = (fen: string): Result => { const parts = fen.split(/[\s_]+/); const boardPart = parts.shift()!; @@ -217,6 +303,18 @@ export interface FenOpts { epd?: boolean; } +/** + * Parses a string representation of a chess piece and returns the corresponding Piece object. + * + * @param {string} str - The string representation of the piece. + * @returns {Piece | undefined} The parsed Piece object, or undefined if the string is invalid. + * + * @example + * const piece1 = parsePiece('R'); // { color: 'white', role: 'rook', promoted: false } + * const piece2 = parsePiece('n~'); // { color: 'black', role: 'knight', promoted: true } + * const piece3 = parsePiece(''); // undefined + * const piece4 = parsePiece('Qx'); // undefined + */ export const parsePiece = (str: string): Piece | undefined => { if (!str) return; const piece = charToPiece(str[0]); @@ -226,6 +324,19 @@ export const parsePiece = (str: string): Piece | undefined => { return piece; }; +/** + * Converts a Piece object to its string representation. + * + * @param {Piece} piece - The Piece object to convert. + * @returns {string} The string representation of the piece. + * + * @example + * const piece1 = { color: 'white', role: 'rook', promoted: false }; + * const str1 = makePiece(piece1); // 'R' + * + * const piece2 = { color: 'black', role: 'knight', promoted: true }; + * const str2 = makePiece(piece2); // 'n~' + */ export const makePiece = (piece: Piece): string => { let r = roleToChar(piece.role); if (piece.color === 'white') r = r.toUpperCase(); @@ -233,6 +344,12 @@ export const makePiece = (piece: Piece): string => { return r; }; +/** + * Converts a Board object to its FEN (Forsyth-Edwards Notation) string representation. + * + * @param {Board} board - The Board object to convert. + * @returns {string} The FEN string representation of the board. + */ export const makeBoardFen = (board: Board): string => { let fen = ''; let empty = 0; @@ -261,12 +378,32 @@ export const makeBoardFen = (board: Board): string => { return fen; }; + +/** + * Converts a MaterialSide object to its string representation. + * + * @param {MaterialSide} material - The MaterialSide object to convert. + * @returns {string} The string representation of the material. + */ export const makePocket = (material: MaterialSide): string => ROLES.map(role => roleToChar(role).repeat(material[role])).join(''); +/** + * Converts a Material object to its string representation. + * + * @param {Material} pocket - The Material object to convert. + * @returns {string} The string representation of the pocket. + */ export const makePockets = (pocket: Material): string => makePocket(pocket.white).toUpperCase() + makePocket(pocket.black); +/** + * Converts the castling rights of a board to its FEN string representation. + * + * @param {Board} board - The Board object. + * @param {SquareSet} castlingRights - The castling rights as a SquareSet. + * @returns {string} The FEN string representation of the castling rights. + */ export const makeCastlingFen = (board: Board, castlingRights: SquareSet): string => { let fen = ''; for (const color of COLORS) { @@ -288,8 +425,22 @@ export const makeCastlingFen = (board: Board, castlingRights: SquareSet): string return fen || '-'; }; +/** + * Converts a RemainingChecks object to its string representation. + * + * @param {RemainingChecks} checks - The RemainingChecks object to convert. + * @returns {string} The string representation of the remaining checks. + */ export const makeRemainingChecks = (checks: RemainingChecks): string => `${checks.white}+${checks.black}`; + +/** + * Converts a Setup object to its FEN string representation. + * + * @param {Setup} setup - The Setup object to convert. + * @param {FenOpts} [opts] - Optional FEN formatting options. + * @returns {string} The FEN string representation of the setup. + */ export const makeFen = (setup: Setup, opts?: FenOpts): string => [ makeBoardFen(setup.board) + (setup.pockets ? `[${makePockets(setup.pockets)}]` : ''), diff --git a/src/san.ts b/src/san.ts index 6ebb8e2c..f9aefbd4 100644 --- a/src/san.ts +++ b/src/san.ts @@ -4,6 +4,14 @@ import { SquareSet } from './squareSet.js'; import { CastlingSide, FILE_NAMES, isDrop, Move, RANK_NAMES, SquareName } from './types.js'; import { charToRole, defined, makeSquare, opposite, parseSquare, roleToChar, squareFile, squareRank } from './util.js'; +/** + * Generates the SAN (Standard Algebraic Notation) representation of a move + * in the given position without the move suffix (#, +). + * + * @param {Position} pos - The chess position. + * @param {Move} move - The move to generate the SAN for. + * @returns {string} The SAN representation of the move. + */ const makeSanWithoutSuffix = (pos: Position, move: Move): string => { let san = ''; if (isDrop(move)) { @@ -52,6 +60,14 @@ const makeSanWithoutSuffix = (pos: Position, move: Move): string => { return san; }; +/** + * Generates the SAN (Standard Algebraic Notation) representation of a move + * in the given position and plays the move on the position. + * + * @param {Position} pos - The chess position. + * @param {Move} move - The move to generate the SAN for and play. + * @returns {string} The SAN representation of the move with the move suffix. + */ export const makeSanAndPlay = (pos: Position, move: Move): string => { const san = makeSanWithoutSuffix(pos, move); pos.play(move); @@ -60,6 +76,14 @@ export const makeSanAndPlay = (pos: Position, move: Move): string => { return san; }; +/** + * Generates the SAN (Standard Algebraic Notation) representation of a variation + * (sequence of moves) in the given position. + * + * @param {Position} pos - The starting position of the variation. + * @param {Move[]} variation - The sequence of moves in the variation. + * @returns {string} The SAN representation of the variation. + */ export const makeSanVariation = (pos: Position, variation: Move[]): string => { pos = pos.clone(); const line = []; @@ -77,8 +101,24 @@ export const makeSanVariation = (pos: Position, variation: Move[]): string => { return line.join(''); }; +/** + * Generates the SAN (Standard Algebraic Notation) representation of a move + * in the given position without modifying the position. + * + * @param {Position} pos - The chess position. + * @param {Move} move - The move to generate the SAN for. + * @returns {string} The SAN representation of the move. + */ export const makeSan = (pos: Position, move: Move): string => makeSanAndPlay(pos.clone(), move); +/** + * Parses a SAN (Standard Algebraic Notation) string and returns the corresponding move + * in the given position. + * + * @param {Position} pos - The chess position. + * @param {string} san - The SAN string to parse. + * @returns {Move | undefined} The parsed move, or undefined if the SAN is invalid or ambiguous. + */ export const parseSan = (pos: Position, san: string): Move | undefined => { const ctx = pos.ctx(); diff --git a/src/setup.ts b/src/setup.ts index b54b727e..0ff1ed2e 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -2,121 +2,250 @@ import { Board, boardEquals } from './board.js'; import { SquareSet } from './squareSet.js'; import { ByColor, ByRole, Color, Role, ROLES, Square } from './types.js'; +/** + * Represents the material configuration for one side (color) in a chess position. + * @implements {ByRole} + * @property {number} pawn - The number of pawns on the side. + * @property {number} knight - The number of knights on the side. + * @property {number} bishop - The number of bishops on the side. + * @property {number} rook - The number of rooks on the side. + * @property {number} queen - The number of queens on the side. + * @property {number} king - The number of kings on the side. + */ export class MaterialSide implements ByRole { + /** @type {number} */ pawn: number; + /** @type {number} */ knight: number; + /** @type {number} */ bishop: number; + /** @type {number} */ rook: number; + /** @type {number} */ queen: number; + /** @type {number} */ king: number; private constructor() {} + /** + * Creates an empty MaterialSide instance. + * @returns {MaterialSide} The empty MaterialSide instance. + */ static empty(): MaterialSide { const m = new MaterialSide(); for (const role of ROLES) m[role] = 0; return m; } + /** + * Creates a MaterialSide instance from a Board for a specific color. + * @param {Board} board - The Board to create the MaterialSide from. + * @param {Color} color - The color to create the MaterialSide for. + * @returns {MaterialSide} The MaterialSide instance derived from the Board. + */ static fromBoard(board: Board, color: Color): MaterialSide { const m = new MaterialSide(); for (const role of ROLES) m[role] = board.pieces(color, role).size(); return m; } + /** + * Creates a clone of the MaterialSide instance. + * @returns {MaterialSide} The cloned MaterialSide instance. + */ clone(): MaterialSide { const m = new MaterialSide(); for (const role of ROLES) m[role] = this[role]; return m; } + /** + * Checks if the MaterialSide instance is equal to another MaterialSide instance. + * @param {MaterialSide} other - The other MaterialSide instance to compare. + * @returns {boolean} True if the MaterialSide instances are equal, false otherwise. + */ equals(other: MaterialSide): boolean { return ROLES.every(role => this[role] === other[role]); } + /** + * Adds another MaterialSide instance to the current MaterialSide instance. + * @param {MaterialSide} other - The MaterialSide instance to add. + * @returns {MaterialSide} A new MaterialSide instance representing the sum. + */ add(other: MaterialSide): MaterialSide { const m = new MaterialSide(); for (const role of ROLES) m[role] = this[role] + other[role]; return m; } + /** + * Subtracts another MaterialSide instance from the current MaterialSide instance. + * @param {MaterialSide} other - The MaterialSide instance to subtract. + * @returns {MaterialSide} A new MaterialSide instance representing the difference. + */ subtract(other: MaterialSide): MaterialSide { const m = new MaterialSide(); for (const role of ROLES) m[role] = this[role] - other[role]; return m; } + /** + * Checks if the MaterialSide is not empty (has pieces). + * @returns {boolean} True if the MaterialSide is not empty, false otherwise. + */ nonEmpty(): boolean { return ROLES.some(role => this[role] > 0); } + /** + * Checks if the MaterialSide is empty (no pieces). + * @returns {boolean} True if the MaterialSide is empty, false otherwise. + */ isEmpty(): boolean { return !this.nonEmpty(); } + /** + * Checks if the MaterialSide has pawns. + * @returns {boolean} True if the MaterialSide has pawns, false otherwise. + */ hasPawns(): boolean { return this.pawn > 0; } + /** + * Checks if the MaterialSide has non-pawn pieces. + * @returns {boolean} True if the MaterialSide has non-pawn pieces, false otherwise. + */ hasNonPawns(): boolean { return this.knight > 0 || this.bishop > 0 || this.rook > 0 || this.queen > 0 || this.king > 0; } + /** + * Calculates the total size of the MaterialSide (number of pieces). + * @returns {number} The total size of the MaterialSide. + */ size(): number { return this.pawn + this.knight + this.bishop + this.rook + this.queen + this.king; } } +/** + * Represents the material configuration of a chess position. + * @implements {ByColor} + * @property {MaterialSide} white - The material configuration for white. + * @property {MaterialSide} black - The material configuration for black. + */ export class Material implements ByColor { + /** + * Creates a new Material instance. + * @param {MaterialSide} white - The material configuration for white. + * @param {MaterialSide} black - The material configuration for black. + */ constructor( public white: MaterialSide, public black: MaterialSide, ) {} + /** + * Creates an empty Material instance. + * @returns {Material} The empty Material instance. + */ static empty(): Material { return new Material(MaterialSide.empty(), MaterialSide.empty()); } + /** + * Creates a Material instance from a Board. + * @param {Board} board - The Board to create the Material from. + * @returns {Material} The Material instance derived from the Board. + */ static fromBoard(board: Board): Material { return new Material(MaterialSide.fromBoard(board, 'white'), MaterialSide.fromBoard(board, 'black')); } + /** + * Creates a clone of the Material instance. + * @returns {Material} The cloned Material instance. + */ clone(): Material { return new Material(this.white.clone(), this.black.clone()); } + /** + * Checks if the Material instance is equal to another Material instance. + * @param {Material} other - The other Material instance to compare. + * @returns {boolean} True if the Material instances are equal, false otherwise. + */ equals(other: Material): boolean { return this.white.equals(other.white) && this.black.equals(other.black); } + /** + * Adds another Material instance to the current Material instance. + * @param {Material} other - The Material instance to add. + * @returns {Material} A new Material instance representing the sum. + */ add(other: Material): Material { return new Material(this.white.add(other.white), this.black.add(other.black)); } + /** + * Subtracts another Material instance from the current Material instance. + * @param {Material} other - The Material instance to subtract. + * @returns {Material} A new Material instance representing the difference. + */ subtract(other: Material): Material { return new Material(this.white.subtract(other.white), this.black.subtract(other.black)); } + /** + * Counts the number of pieces of a specific role. + * @param {Role} role - The role to count. + * @returns {number} The count of pieces with the specified role. + */ count(role: Role): number { return this.white[role] + this.black[role]; } + /** + * Calculates the total size of the Material (number of pieces). + * @returns {number} The total size of the Material. + */ size(): number { return this.white.size() + this.black.size(); } + /** + * Checks if the Material is empty (no pieces). + * @returns {boolean} True if the Material is empty, false otherwise. + */ isEmpty(): boolean { return this.white.isEmpty() && this.black.isEmpty(); } + /** + * Checks if the Material is not empty (has pieces). + * @returns {boolean} True if the Material is not empty, false otherwise. + */ nonEmpty(): boolean { return !this.isEmpty(); } + /** + * Checks if the Material has pawns. + * @returns {boolean} True if the Material has pawns, false otherwise. + */ hasPawns(): boolean { return this.white.hasPawns() || this.black.hasPawns(); } + /** + * Checks if the Material has non-pawn pieces. + * @returns {boolean} True if the Material has non-pawn pieces, false otherwise. + */ hasNonPawns(): boolean { return this.white.hasNonPawns() || this.black.hasNonPawns(); } @@ -142,7 +271,16 @@ export class RemainingChecks implements ByColor { } /** - * A not necessarily legal chess or chess variant position. + * Represents the setup of a chess position. + * @interface Setup + * @property {Board} board - The chess board. + * @property {Material | undefined} pockets - The material in the pockets (optional). + * @property {Color} turn - The color of the side to move. + * @property {SquareSet} castlingRights - The castling rights. + * @property {Square | undefined} epSquare - The en passant square (optional). + * @property {RemainingChecks | undefined} remainingChecks - The remaining checks (optional). + * @property {number} halfmoves - The number of halfmoves since the last pawn advance or capture. + * @property {number} fullmoves - The number of fullmoves. */ export interface Setup { board: Board; @@ -155,6 +293,10 @@ export interface Setup { fullmoves: number; } +/** + * Creates a default setup for a standard chess position. + * @returns {Setup} The default setup. + */ export const defaultSetup = (): Setup => ({ board: Board.default(), pockets: undefined, @@ -166,6 +308,11 @@ export const defaultSetup = (): Setup => ({ fullmoves: 1, }); +/** + * Creates a clone of a given setup. + * @param {Setup} setup - The setup to clone. + * @returns {Setup} The cloned setup. + */ export const setupClone = (setup: Setup): Setup => ({ board: setup.board.clone(), pockets: setup.pockets?.clone(), @@ -177,6 +324,12 @@ export const setupClone = (setup: Setup): Setup => ({ fullmoves: setup.fullmoves, }); +/** + * Checks if two setups are equal. + * @param {Setup} left - The first setup. + * @param {Setup} right - The second setup. + * @returns {boolean} True if the setups are equal, false otherwise. + */ export const setupEquals = (left: Setup, right: Setup): boolean => boardEquals(left.board, right.board) && ((right.pockets && left.pockets?.equals(right.pockets)) || (!left.pockets && !right.pockets)) @@ -186,4 +339,4 @@ export const setupEquals = (left: Setup, right: Setup): boolean => && ((right.remainingChecks && left.remainingChecks?.equals(right.remainingChecks)) || (!left.remainingChecks && !right.remainingChecks)) && left.halfmoves === right.halfmoves - && left.fullmoves === right.fullmoves; + && left.fullmoves === right.fullmoves; \ No newline at end of file diff --git a/src/squareSet.ts b/src/squareSet.ts index 7d1cc110..61b1b81a 100644 --- a/src/squareSet.ts +++ b/src/squareSet.ts @@ -30,86 +30,220 @@ export class SquareSet implements Iterable { this.hi = hi | 0; } + /** + * Returns a square set containing the given square. + * @param square + * @returns + */ static fromSquare(square: Square): SquareSet { return square >= 32 ? new SquareSet(0, 1 << (square - 32)) : new SquareSet(1 << square, 0); } + /** + * Returns a square set containing all squares on the given rank. + * @param rank A rank number (0-7) + * @returns {SquareSet} + */ static fromRank(rank: number): SquareSet { return new SquareSet(0xff, 0).shl64(8 * rank); } + /** + * Returns a square set containing all squares on the given file. + * @param file A file number (0-7) + * @returns {SquareSet} + */ static fromFile(file: number): SquareSet { return new SquareSet(0x0101_0101 << file, 0x0101_0101 << file); } + /** + * Returns an empty square set. + * @returns {SquareSet} + */ static empty(): SquareSet { return new SquareSet(0, 0); } + /** + * Returns a square set containing all squares. + * @returns {SquareSet} + */ static full(): SquareSet { return new SquareSet(0xffff_ffff, 0xffff_ffff); } + /** + * Returns a square set containing all corner squares. + * @returns {SquareSet} + */ static corners(): SquareSet { return new SquareSet(0x81, 0x8100_0000); } + /** + * TODO: Not sure about this one. + * Returns a square set containing the four center squares. + * @returns {SquareSet} + */ static center(): SquareSet { return new SquareSet(0x1800_0000, 0x18); } + /** + * Returns a square set containing all squares on the back ranks of both sides. + * @returns {SquareSet} + */ static backranks(): SquareSet { return new SquareSet(0xff, 0xff00_0000); } + /** + * Returns a square set containing all squares on the back rank of the given color. + * @param color The color of the back rank + * @returns {SquareSet} + */ static backrank(color: Color): SquareSet { return color === 'white' ? new SquareSet(0xff, 0) : new SquareSet(0, 0xff00_0000); } + /** + * Returns a square set containing all dark squares. + * @returns {SquareSet} + */ static lightSquares(): SquareSet { return new SquareSet(0x55aa_55aa, 0x55aa_55aa); } + /** + * Returns a square set containing all light squares. + * @returns {SquareSet} + */ static darkSquares(): SquareSet { return new SquareSet(0xaa55_aa55, 0xaa55_aa55); } + /** + * Returns the complement of the current SquareSet. + * + * The complement of a SquareSet is a new SquareSet that contains all the squares + * that are not present in the original set. + * + * @returns {SquareSet} A new SquareSet representing the complement of the current set. + */ complement(): SquareSet { return new SquareSet(~this.lo, ~this.hi); } + /** + * Performs a bitwise XOR operation between the current SquareSet and another SquareSet. + * + * The XOR operation returns a new SquareSet that contains the squares that are present + * in either the current set or the other set, but not both. + * + * @param {SquareSet} other - The SquareSet to perform the XOR operation with. + * @returns {SquareSet} A new SquareSet representing the result of the XOR operation. + */ xor(other: SquareSet): SquareSet { return new SquareSet(this.lo ^ other.lo, this.hi ^ other.hi); } + /** + * Performs a bitwise OR operation between the current SquareSet and another SquareSet. + * + * The OR operation returns a new SquareSet that contains the squares that are present + * in either the current set or the other set, or both. + * + * @param {SquareSet} other - The SquareSet to perform the OR operation with. + * @returns {SquareSet} A new SquareSet representing the result of the OR operation. + */ union(other: SquareSet): SquareSet { return new SquareSet(this.lo | other.lo, this.hi | other.hi); } + /** + * Performs a bitwise AND operation between the current SquareSet and another SquareSet. + * + * The AND operation returns a new SquareSet that contains the squares that are present + * in both the current set and the other set. + * + * @param {SquareSet} other - The SquareSet to perform the AND operation with. + * @returns {SquareSet} A new SquareSet representing the result of the AND operation. + */ intersect(other: SquareSet): SquareSet { return new SquareSet(this.lo & other.lo, this.hi & other.hi); } + /** + * Performs a bitwise AND NOT operation between the current SquareSet and another SquareSet. + * + * The AND NOT operation returns a new SquareSet that contains the squares that are present + * in the current set, but not in the other set. + * + * @param {SquareSet} other - The SquareSet to perform the AND NOT operation with. + * @returns {SquareSet} A new SquareSet representing the result of the AND NOT operation. + */ diff(other: SquareSet): SquareSet { return new SquareSet(this.lo & ~other.lo, this.hi & ~other.hi); } + /** + * Checks if the current SquareSet intersects with another SquareSet. + * + * Two SquareSets are considered to intersect if they have at least one square in common. + * + * @param {SquareSet} other - The SquareSet to check for intersection. + * @returns {boolean} True if the current set intersects with the other set, false otherwise. + */ intersects(other: SquareSet): boolean { return this.intersect(other).nonEmpty(); } + /** + * Checks if the current SquareSet is disjoint with another SquareSet. + * + * Two SquareSets are considered to be disjoint if they have no squares in common. + * + * @param {SquareSet} other - The SquareSet to check for disjointness. + * @returns {boolean} True if the current set is disjoint with the other set, false otherwise. + */ isDisjoint(other: SquareSet): boolean { return this.intersect(other).isEmpty(); } + /** + * Checks if the current SquareSet is a superset of another SquareSet. + * + * A SquareSet is a superset of another SquareSet if every square in the other set is also present in the current set. + * + * @param {SquareSet} other - The SquareSet to check for supersetness. + * @returns {boolean} True if the current set is a superset of the other set, false otherwise. + */ supersetOf(other: SquareSet): boolean { return other.diff(this).isEmpty(); } + /** + * Checks if the current SquareSet is a subset of another SquareSet. + * + * A SquareSet is a subset of another SquareSet if every square in the current set is also present in the other set. + * + * @param {SquareSet} other - The SquareSet to check for subsetness. + * @returns {boolean} True if the current set is a subset of the other set, false otherwise. + */ subsetOf(other: SquareSet): boolean { return this.diff(other).isEmpty(); } + /** + * Performs a logical right shift operation on the SquareSet by the specified number of positions. + * + * The right shift operation shifts the bits of the SquareSet towards the right by the given + * number of positions. The vacated bits on the left side are filled with zeros. + * + * @param {number} shift - The number of positions to shift the bits to the right. + * @returns {SquareSet} A new SquareSet representing the result of the right shift operation. + */ shr64(shift: number): SquareSet { if (shift >= 64) return SquareSet.empty(); if (shift >= 32) return new SquareSet(this.hi >>> (shift - 32), 0); @@ -117,6 +251,15 @@ export class SquareSet implements Iterable { return this; } + /** + * Performs a logical left shift operation on the SquareSet by the specified number of positions. + * + * The left shift operation shifts the bits of the SquareSet towards the left by the given + * number of positions. The vacated bits on the right side are filled with zeros. + * + * @param {number} shift - The number of positions to shift the bits to the left. + * @returns {SquareSet} A new SquareSet representing the result of the left shift operation. + */ shl64(shift: number): SquareSet { if (shift >= 64) return SquareSet.empty(); if (shift >= 32) return new SquareSet(0, this.lo << (shift - 32)); @@ -124,83 +267,176 @@ export class SquareSet implements Iterable { return this; } + /** + * Swaps the bytes of the SquareSet in a 64-bit manner. + * + * @returns {SquareSet} A new SquareSet with the bytes swapped. + */ bswap64(): SquareSet { return new SquareSet(bswap32(this.hi), bswap32(this.lo)); } + /** + * Reverses the bits of the SquareSet in a 64-bit manner. + * + * @returns {SquareSet} A new SquareSet with the bits reversed. + */ rbit64(): SquareSet { return new SquareSet(rbit32(this.hi), rbit32(this.lo)); } + /** + * Subtracts another SquareSet from the current SquareSet in a 64-bit manner. + * + * @param {SquareSet} other - The SquareSet to subtract. + * @returns {SquareSet} A new SquareSet representing the result of the subtraction. + */ minus64(other: SquareSet): SquareSet { const lo = this.lo - other.lo; const c = ((lo & other.lo & 1) + (other.lo >>> 1) + (lo >>> 1)) >>> 31; return new SquareSet(lo, this.hi - (other.hi + c)); } + /** + * Checks if the current SquareSet is equal to another SquareSet. + * + * @param {SquareSet} other - The SquareSet to compare with. + * @returns {boolean} True if the SquareSets are equal, false otherwise. + */ equals(other: SquareSet): boolean { return this.lo === other.lo && this.hi === other.hi; } + /** + * Returns the number of squares in the SquareSet. + * + * @returns {number} The count of squares in the SquareSet. + */ size(): number { return popcnt32(this.lo) + popcnt32(this.hi); } + /** + * Checks if the SquareSet is empty. + * + * @returns {boolean} True if the SquareSet is empty, false otherwise. + */ isEmpty(): boolean { return this.lo === 0 && this.hi === 0; } + /** + * Checks if the SquareSet is not empty. + * + * @returns {boolean} True if the SquareSet is not empty, false otherwise. + */ nonEmpty(): boolean { return this.lo !== 0 || this.hi !== 0; } + /** + * Checks if the SquareSet contains a specific square. + * + * @param {Square} square - The square to check for presence. + * @returns {boolean} True if the SquareSet contains the square, false otherwise. + */ has(square: Square): boolean { return (square >= 32 ? this.hi & (1 << (square - 32)) : this.lo & (1 << square)) !== 0; } + /** + * Sets or unsets a square in the SquareSet. + * + * @param {Square} square - The square to set or unset. + * @param {boolean} on - True to set the square, false to unset it. + * @returns {SquareSet} A new SquareSet with the square set or unset. + */ set(square: Square, on: boolean): SquareSet { return on ? this.with(square) : this.without(square); } + /** + * Adds a square to the SquareSet. + * + * @param {Square} square - The square to add. + * @returns {SquareSet} A new SquareSet with the square added. + */ with(square: Square): SquareSet { return square >= 32 ? new SquareSet(this.lo, this.hi | (1 << (square - 32))) : new SquareSet(this.lo | (1 << square), this.hi); } + /** + * Removes a square from the SquareSet. + * + * @param {Square} square - The square to remove. + * @returns {SquareSet} A new SquareSet with the square removed. + */ without(square: Square): SquareSet { return square >= 32 ? new SquareSet(this.lo, this.hi & ~(1 << (square - 32))) : new SquareSet(this.lo & ~(1 << square), this.hi); } + /** + * Toggles the presence of a square in the SquareSet. + * + * @param {Square} square - The square to toggle. + * @returns {SquareSet} A new SquareSet with the square toggled. + */ toggle(square: Square): SquareSet { return square >= 32 ? new SquareSet(this.lo, this.hi ^ (1 << (square - 32))) : new SquareSet(this.lo ^ (1 << square), this.hi); } + /** + * Returns the last square in the SquareSet. + * + * @returns {Square | undefined} The last square in the SquareSet, or undefined if the set is empty. + */ last(): Square | undefined { if (this.hi !== 0) return 63 - Math.clz32(this.hi); if (this.lo !== 0) return 31 - Math.clz32(this.lo); return; } + /** + * Returns the first square in the SquareSet. + * + * @returns {Square | undefined} The first square in the SquareSet, or undefined if the set is empty. + */ first(): Square | undefined { if (this.lo !== 0) return 31 - Math.clz32(this.lo & -this.lo); if (this.hi !== 0) return 63 - Math.clz32(this.hi & -this.hi); return; } + /** + * Returns a new SquareSet with the first square removed. + * + * @returns {SquareSet} A new SquareSet with the first square removed. + */ withoutFirst(): SquareSet { if (this.lo !== 0) return new SquareSet(this.lo & (this.lo - 1), this.hi); return new SquareSet(0, this.hi & (this.hi - 1)); } + /** + * Checks if the SquareSet contains more than one square. + * + * @returns {boolean} True if the SquareSet contains more than one square, false otherwise. + */ moreThanOne(): boolean { return (this.hi !== 0 && this.lo !== 0) || (this.lo & (this.lo - 1)) !== 0 || (this.hi & (this.hi - 1)) !== 0; } + /** + * Returns the single square in the SquareSet if it contains only one square. + * + * @returns {Square | undefined} The single square in the SquareSet, or undefined if the set is empty or contains more than one square. + */ singleSquare(): Square | undefined { return this.moreThanOne() ? undefined : this.last(); } diff --git a/src/transform.ts b/src/transform.ts index ea8ffd99..1c7a699c 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -4,8 +4,20 @@ import { SquareSet } from './squareSet.js'; import { COLORS, ROLES } from './types.js'; import { defined } from './util.js'; +/** +* Flips a SquareSet vertically. +* +* @param {SquareSet} s - The SquareSet to flip. +* @returns {SquareSet} The flipped SquareSet. +*/ export const flipVertical = (s: SquareSet): SquareSet => s.bswap64(); +/** +* Flips a SquareSet horizontally. +* +* @param {SquareSet} s - The SquareSet to flip. +* @returns {SquareSet} The flipped SquareSet. +*/ export const flipHorizontal = (s: SquareSet): SquareSet => { const k1 = new SquareSet(0x55555555, 0x55555555); const k2 = new SquareSet(0x33333333, 0x33333333); @@ -16,6 +28,12 @@ export const flipHorizontal = (s: SquareSet): SquareSet => { return s; }; +/** +* Flips a SquareSet diagonally. +* +* @param {SquareSet} s - The SquareSet to flip. +* @returns {SquareSet} The flipped SquareSet. +*/ export const flipDiagonal = (s: SquareSet): SquareSet => { let t = s.xor(s.shl64(28)).intersect(new SquareSet(0, 0x0f0f0f0f)); s = s.xor(t.xor(t.shr64(28))); @@ -26,8 +44,21 @@ export const flipDiagonal = (s: SquareSet): SquareSet => { return s; }; +/** +* Rotates a SquareSet by 180 degrees. +* +* @param {SquareSet} s - The SquareSet to rotate. +* @returns {SquareSet} The rotated SquareSet. +*/ export const rotate180 = (s: SquareSet): SquareSet => s.rbit64(); +/** +* Transforms a Board by applying a transformation function to each SquareSet. +* +* @param {Board} board - The Board to transform. +* @param {function(SquareSet): SquareSet} f - The transformation function. +* @returns {Board} The transformed Board. +*/ export const transformBoard = (board: Board, f: (s: SquareSet) => SquareSet): Board => { const b = Board.empty(); b.occupied = f(board.occupied); @@ -37,6 +68,13 @@ export const transformBoard = (board: Board, f: (s: SquareSet) => SquareSet): Bo return b; }; +/** +* Transforms a Setup by applying a transformation function to each SquareSet. +* +* @param {Setup} setup - The Setup to transform. +* @param {function(SquareSet): SquareSet} f - The transformation function. +* @returns {Setup} The transformed Setup. +*/ export const transformSetup = (setup: Setup, f: (s: SquareSet) => SquareSet): Setup => ({ board: transformBoard(setup.board, f), pockets: setup.pockets?.clone(), diff --git a/src/types.ts b/src/types.ts index 4310dd76..b5dc53e2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,76 +1,162 @@ +/** + * An array of file names in a chess board. + * @constant + */ export const FILE_NAMES = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] as const; +/** + * The type representing a file name in a chess board. + */ export type FileName = (typeof FILE_NAMES)[number]; +/** + * An array of rank names in a chess board. + * @constant + */ export const RANK_NAMES = ['1', '2', '3', '4', '5', '6', '7', '8'] as const; +/** + * The type representing a rank name in a chess board. + */ export type RankName = (typeof RANK_NAMES)[number]; +/** + * The type representing a square on the chess board. + * + * A number between 0 and 63, inclusive. + */ export type Square = number; +/** + * The type representing the name of a square on the chess board. + */ export type SquareName = `${FileName}${RankName}`; /** - * Indexable by square indices. + * The type representing an array indexed by squares. + * @template T */ export type BySquare = T[]; +/** + * An array of chess piece colors. + * @constant + */ export const COLORS = ['white', 'black'] as const; +/** + * The type representing a chess piece color. + */ export type Color = (typeof COLORS)[number]; /** - * Indexable by `white` and `black`. + * The type representing an object indexed by colors. + * @template T */ export type ByColor = { [color in Color]: T; }; +/** + * An array of chess piece roles. + * @constant + */ export const ROLES = ['pawn', 'knight', 'bishop', 'rook', 'queen', 'king'] as const; +/** + * The type representing a chess piece role. + */ export type Role = (typeof ROLES)[number]; /** - * Indexable by `pawn`, `knight`, `bishop`, `rook`, `queen`, and `king`. + * The type representing an object indexed by roles. + * @template T */ export type ByRole = { [role in Role]: T; }; +/** + * An array of castling sides. + * @constant + */ export const CASTLING_SIDES = ['a', 'h'] as const; +/** + * The type representing a castling side. + */ export type CastlingSide = (typeof CASTLING_SIDES)[number]; /** - * Indexable by `a` and `h`. + * The type representing an object indexed by castling sides. + * @template T */ export type ByCastlingSide = { [side in CastlingSide]: T; }; +/** + * An interface representing a chess piece. + * @interface Piece + * @property {Role} role - The role of the piece. + * @property {Color} color - The color of the piece. + * @property {boolean} [promoted] - Whether the piece is promoted (optional). + */ export interface Piece { role: Role; color: Color; promoted?: boolean; } +/** + * An interface representing a normal chess move. + * @interface NormalMove + * @property {Square} from - The starting square of the move. + * @property {Square} to - The destination square of the move. + * @property {Role} [promotion] - The role to promote the pawn to (optional). + */ export interface NormalMove { from: Square; to: Square; promotion?: Role; } +/** + * An interface representing a drop move in chess variants. + * @interface DropMove + * @property {Role} role - The role of the piece being dropped. + * @property {Square} to - The square where the piece is dropped. + */ export interface DropMove { role: Role; to: Square; } +/** + * The type representing a chess move (either a normal move or a drop move). + */ export type Move = NormalMove | DropMove; +/** + * A type guard function to check if a move is a drop move. + * @function isDrop + * @param {Move} v - The move to check. + * @returns {v is DropMove} - Returns true if the move is a drop move. + */ export const isDrop = (v: Move): v is DropMove => 'role' in v; +/** + * A type guard function to check if a move is a normal move. + * @function isNormal + * @param {Move} v - The move to check. + * @returns {v is NormalMove} - Returns true if the move is a normal move. + */ export const isNormal = (v: Move): v is NormalMove => 'from' in v; +/** + * An array of chess variant rules. + * @constant + */ export const RULES = [ 'chess', 'antichess', @@ -82,8 +168,16 @@ export const RULES = [ 'crazyhouse', ] as const; +/** + * The type representing a chess variant rule. + */ export type Rules = (typeof RULES)[number]; +/** + * An interface representing the outcome of a chess game. + * @interface Outcome + * @property {Color | undefined} winner - The color of the winning side, or undefined if the game is a draw. + */ export interface Outcome { winner: Color | undefined; -} +} \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index 1b68ba02..43e4ad38 100644 --- a/src/util.ts +++ b/src/util.ts @@ -11,17 +11,55 @@ import { SquareName, } from './types.js'; +/** + * A type guard function to check if a value is defined (not undefined). + * @function defined + * @template A + * @param {A | undefined} v The value to check. + * @returns {v is A} Returns true if the value is defined (not undefined). + */ export const defined = (v: A | undefined): v is A => v !== undefined; +/** + * A function to get the opposite color of a given chess piece color. + * @function opposite + * @param {Color} color The color to get the opposite of. + * @returns {Color} The opposite color. + */ export const opposite = (color: Color): Color => (color === 'white' ? 'black' : 'white'); +/** + * A function to get the rank of a square on the chess board. + * @function squareRank + * @param {Square} square The square to get the rank of. + * @returns {number} The rank of the square (0-7). + */ export const squareRank = (square: Square): number => square >> 3; +/** + * A function to get the file of a square on the chess board. + * @function squareFile + * @param {Square} square The square to get the file of. + * @returns {number} The file of the square (0-7). + */ export const squareFile = (square: Square): number => square & 0x7; +/** + * A function to get the square corresponding to the given file and rank coordinates. + * @function squareFromCoords + * @param {number} file The file coordinate (0-7). + * @param {number} rank The rank coordinate (0-7). + * @returns {Square | undefined} The corresponding square if the coordinates are valid, or undefined if the coordinates are out of bounds. + */ export const squareFromCoords = (file: number, rank: number): Square | undefined => 0 <= file && file < 8 && 0 <= rank && rank < 8 ? file + 8 * rank : undefined; +/** + * A function to convert a chess piece role to its corresponding character representation. + * @function roleToChar + * @param {Role} role - The chess piece role. + * @returns {string} - The character representation of the role. + */ export const roleToChar = (role: Role): string => { switch (role) { case 'pawn': @@ -39,6 +77,12 @@ export const roleToChar = (role: Role): string => { } }; +/** + * A function to convert a character to its corresponding chess piece role. + * @function charToRole + * @param {string} ch - The character to convert. + * @returns {Role | undefined} - The corresponding chess piece role, or undefined if the character is not valid. + */ export function charToRole(ch: 'p' | 'n' | 'b' | 'r' | 'q' | 'k' | 'P' | 'N' | 'B' | 'R' | 'Q' | 'K'): Role; export function charToRole(ch: string): Role | undefined; export function charToRole(ch: string): Role | undefined { @@ -60,6 +104,12 @@ export function charToRole(ch: string): Role | undefined { } } +/** + * A function to parse a square name and return the corresponding square. + * @function parseSquare + * @param {string} str - The square name to parse. + * @returns {Square | undefined} - The corresponding square, or undefined if the square name is not valid. + */ export function parseSquare(str: SquareName): Square; export function parseSquare(str: string): Square | undefined; export function parseSquare(str: string): Square | undefined { @@ -67,9 +117,21 @@ export function parseSquare(str: string): Square | undefined { return squareFromCoords(str.charCodeAt(0) - 'a'.charCodeAt(0), str.charCodeAt(1) - '1'.charCodeAt(0)); } +/** + * A function to convert a square to its corresponding square name. + * @function makeSquare + * @param {Square} square - The square to convert. + * @returns {SquareName} - The corresponding square name. + */ export const makeSquare = (square: Square): SquareName => (FILE_NAMES[squareFile(square)] + RANK_NAMES[squareRank(square)]) as SquareName; +/** + * A function to parse a UCI (Universal Chess Interface) string and return the corresponding move. + * @function parseUci + * @param {string} str - The UCI string to parse. + * @returns {Move | undefined} - The corresponding move, or undefined if the UCI string is not valid. + */ export const parseUci = (str: string): Move | undefined => { if (str[1] === '@' && str.length === 4) { const role = charToRole(str[0]); @@ -88,6 +150,13 @@ export const parseUci = (str: string): Move | undefined => { return; }; +/** + * A function to check if two moves are equal. + * @function moveEquals + * @param {Move} left - The first move to compare. + * @param {Move} right - The second move to compare. + * @returns {boolean} - True if the moves are equal, false otherwise. + */ export const moveEquals = (left: Move, right: Move): boolean => { if (left.to !== right.to) return false; if (isDrop(left)) return isDrop(right) && left.role === right.role; @@ -95,16 +164,32 @@ export const moveEquals = (left: Move, right: Move): boolean => { }; /** - * Converts a move to UCI notation, like `g1f3` for a normal move, - * `a7a8q` for promotion to a queen, and `Q@f7` for a Crazyhouse drop. + * A function to convert a move to its corresponding UCI string representation. + * @function makeUci + * @param {Move} move - The move to convert. + * @returns {string} - The corresponding UCI string representation of the move. */ export const makeUci = (move: Move): string => isDrop(move) ? `${roleToChar(move.role).toUpperCase()}@${makeSquare(move.to)}` : makeSquare(move.from) + makeSquare(move.to) + (move.promotion ? roleToChar(move.promotion) : ''); +/** + * A function to get the square where the king castles to for a given color and castling side. + * @function kingCastlesTo + * @param {Color} color - The color of the king. + * @param {CastlingSide} side - The castling side ('a' for queenside, 'h' for kingside). + * @returns {Square} - The square where the king castles to. + */ export const kingCastlesTo = (color: Color, side: CastlingSide): Square => color === 'white' ? (side === 'a' ? 2 : 6) : side === 'a' ? 58 : 62; +/** + * A function to get the square where the rook castles to for a given color and castling side. + * @function rookCastlesTo + * @param {Color} color - The color of the rook. + * @param {CastlingSide} side - The castling side ('a' for queenside, 'h' for kingside). + * @returns {Square} - The square where the rook castles to. + */ export const rookCastlesTo = (color: Color, side: CastlingSide): Square => - color === 'white' ? (side === 'a' ? 3 : 5) : side === 'a' ? 59 : 61; + color === 'white' ? (side === 'a' ? 3 : 5) : side === 'a' ? 59 : 61; \ No newline at end of file From 730933a72b705c7d7f6a58717f47334199cb8738 Mon Sep 17 00:00:00 2001 From: Steven Stavrakis Date: Mon, 3 Jun 2024 16:03:39 -0400 Subject: [PATCH 2/6] remove redundant symbol in jsdoc --- src/chess.ts | 78 +++++++++++++++++++++++++----------------------- src/fen.ts | 30 +++++++++---------- src/san.ts | 20 ++++++------- src/setup.ts | 62 +++++++++++++++++++------------------- src/squareSet.ts | 36 +++++++++++----------- src/transform.ts | 16 +++++----- src/types.ts | 22 +++++++------- src/util.ts | 42 +++++++++++++------------- 8 files changed, 155 insertions(+), 151 deletions(-) diff --git a/src/chess.ts b/src/chess.ts index d2b4548c..abc30362 100644 --- a/src/chess.ts +++ b/src/chess.ts @@ -51,10 +51,10 @@ export class PositionError extends Error { } /** * Calculates the attacking squares for a given square and attacker color. - * @param {Square} square - The target square. - * @param {Color} attacker - The attacking color. - * @param {Board} board - The chess board. - * @param {SquareSet} occupied - The occupied squares on the board. + * @param {Square} square The target square. + * @param {Color} attacker The attacking color. + * @param {Board} board The chess board. + * @param {SquareSet} occupied The occupied squares on the board. * @returns {SquareSet} The squares from which the target square is attacked. */ const attacksTo = (square: Square, attacker: Color, board: Board, occupied: SquareSet): SquareSet => @@ -136,10 +136,10 @@ export class Castles { /** * Adds castling rights for the given color and side. - * @param {Color} color - The color. - * @param {CastlingSide} side - The castling side. - * @param {Square} king - The king's square. - * @param {Square} rook - The rook's square. + * @param {Color} color The color. + * @param {CastlingSide} side The castling side. + * @param {Square} king The king's square. + * @param {Square} rook The rook's square. */ private add(color: Color, side: CastlingSide, king: Square, rook: Square): void { const kingTo = kingCastlesTo(color, side); @@ -155,7 +155,7 @@ export class Castles { /** * Creates a Castles instance from the given setup. - * @param {Setup} setup - The chess setup. + * @param {Setup} setup The chess setup. * @returns {Castles} The Castles instance derived from the setup. */ static fromSetup(setup: Setup): Castles { @@ -176,7 +176,7 @@ export class Castles { /** * Discards castling rights for the rook on the given square. - * @param {Square} square - The square of the rook. + * @param {Square} square The square of the rook. */ discardRook(square: Square): void { if (this.castlingRights.has(square)) { @@ -191,7 +191,7 @@ export class Castles { /** * Discards castling rights for the given color. - * @param {Color} color - The color to discard castling rights for. + * @param {Color} color The color to discard castling rights for. */ discardColor(color: Color): void { this.castlingRights = this.castlingRights.diff(SquareSet.backrank(color)); @@ -204,11 +204,11 @@ export class Castles { * TODO: Not sure what this is * Represents the context of a chess position. * @interface - * @property {Square | undefined} king - The square of the king. - * @property {SquareSet} blockers - The set of blocking squares. - * @property {SquareSet} checkers - The set of checking squares. - * @property {boolean} variantEnd - Whether the variant has ended. - * @property {boolean} mustCapture - Whether a capture is required. + * @property {Square | undefined} king The square of the king. + * @property {SquareSet} blockers The set of blocking squares. + * @property {SquareSet} checkers The set of checking squares. + * @property {boolean} variantEnd Whether the variant has ended. + * @property {boolean} mustCapture Whether a capture is required. */ export interface Context { king: Square | undefined; @@ -224,49 +224,53 @@ export interface Context { */ export abstract class Position { /** - * The current Board. + * The board state of the position. + * @type {Board} */ board: Board; /** - * Represents the taken pieces. + * The pocket pieces (captured pieces) of the position, if any. + * @type {Material | undefined} */ pockets: Material | undefined; /** - * The current turn. + * The color of the side to move in the position. + * @type {Color} */ turn: Color; /** - * TODO: Not sure what this is + * The castling rights of the position. + * @type {Castles} */ castles: Castles; /** - * TODO: Not sure what this is + * The en passant square of the position, if any. + * @type {Square | undefined} */ epSquare: Square | undefined; /** - * TODO: Not sure what this is + * The remaining checks count of the position, if applicable. + * @type {RemainingChecks | undefined} */ remainingChecks: RemainingChecks | undefined; + /** - * Number of times pieces have been moved. - * For example: 1.e4 is a halfmove, but 1.e4 e5 is a fullmove. + * The number of halfmoves since the last pawn advance or capture. + * @type {number} */ halfmoves: number; + /** - * Number of times both sides have played a move. - * For example: 1.e4 e5 1.e5 is a fullmove, but 1.e4 e5 1.e5 e6 is two fullmoves. + * The number of fullmoves (each player's turn counts as one fullmove). + * @type {number} */ fullmoves: number; - /** - * Creates a new Position instance. - * @param {Rules} rules - The chess rules. - */ protected constructor(readonly rules: Rules) { } /** @@ -285,7 +289,7 @@ export abstract class Position { /** * Sets up the position from the given setup without validation. - * @param {Setup} setup - The chess setup. + * @param {Setup} setup The chess setup. * @protected */ protected setupUnchecked(setup: Setup) { @@ -314,9 +318,9 @@ export abstract class Position { /** * Calculates the attacking squares for the king on the given square by the given color. - * @param {Square} square - The square of the king. - * @param {Color} attacker - The attacking color. - * @param {SquareSet} occupied - The occupied squares on the board. + * @param {Square} square The square of the king. + * @param {Color} attacker The attacking color. + * @param {SquareSet} occupied The occupied squares on the board. * @returns {SquareSet} The squares from which the king is attacked. */ kingAttackers(square: Square, attacker: Color, occupied: SquareSet): SquareSet { @@ -325,8 +329,8 @@ export abstract class Position { /** * Executes a capture at the given square. - * @param {Square} square - The square where the capture occurs. - * @param {Piece} captured - The captured piece. + * @param {Square} square The square where the capture occurs. + * @param {Piece} captured The captured piece. * @protected */ protected playCaptureAt(square: Square, captured: Piece): void { @@ -718,7 +722,7 @@ export class Chess extends Position { /** * Create a new, unchecked chess game from a setup. * TODO: There is validation, but I'm not sure what it is. - * @param {Setup} setup - The chess setup. + * @param {Setup} setup The chess setup. * @returns Chess or an error. */ static fromSetup(setup: Setup): Result { diff --git a/src/fen.ts b/src/fen.ts index 072dc8de..79e956db 100644 --- a/src/fen.ts +++ b/src/fen.ts @@ -84,7 +84,7 @@ export const parseBoardFen = (boardPart: string): Result => { /** * Parses the pockets part of a FEN (Forsyth-Edwards Notation) string and returns a Material object. * - * @param {string} pocketPart - The pockets part of the FEN string. + * @param {string} pocketPart The pockets part of the FEN string. * @returns {Result} The parsed Material object if successful, or a FenError if parsing fails. * * @throws {FenError} Throws a FenError if the pockets part is invalid. @@ -117,8 +117,8 @@ export const parsePockets = (pocketPart: string): Result => /** * Parses the castling part of a FEN string and returns the corresponding castling rights as a SquareSet. * - * @param {Board} board - The chess board. - * @param {string} castlingPart - The castling part of the FEN string. + * @param {Board} board The chess board. + * @param {string} castlingPart The castling part of the FEN string. * @returns {Result} The castling rights as a SquareSet if parsing is successful, or a FenError if parsing fails. */ export const parseCastlingFen = (board: Board, castlingPart: string): Result => { @@ -152,7 +152,7 @@ export const parseCastlingFen = (board: Board, castlingPart: string): Result} The RemainingChecks object if parsing is successful, or a FenError if parsing fails. * * @example @@ -195,7 +195,7 @@ export const parseRemainingChecks = (part: string): Result} The parsed Setup object if successful, or a FenError if parsing fails. * * @throws {FenError} Throws a FenError if the FEN string is invalid. @@ -306,7 +306,7 @@ export interface FenOpts { /** * Parses a string representation of a chess piece and returns the corresponding Piece object. * - * @param {string} str - The string representation of the piece. + * @param {string} str The string representation of the piece. * @returns {Piece | undefined} The parsed Piece object, or undefined if the string is invalid. * * @example @@ -327,7 +327,7 @@ export const parsePiece = (str: string): Piece | undefined => { /** * Converts a Piece object to its string representation. * - * @param {Piece} piece - The Piece object to convert. + * @param {Piece} piece The Piece object to convert. * @returns {string} The string representation of the piece. * * @example @@ -347,7 +347,7 @@ export const makePiece = (piece: Piece): string => { /** * Converts a Board object to its FEN (Forsyth-Edwards Notation) string representation. * - * @param {Board} board - The Board object to convert. + * @param {Board} board The Board object to convert. * @returns {string} The FEN string representation of the board. */ export const makeBoardFen = (board: Board): string => { @@ -382,7 +382,7 @@ export const makeBoardFen = (board: Board): string => { /** * Converts a MaterialSide object to its string representation. * - * @param {MaterialSide} material - The MaterialSide object to convert. + * @param {MaterialSide} material The MaterialSide object to convert. * @returns {string} The string representation of the material. */ export const makePocket = (material: MaterialSide): string => @@ -391,7 +391,7 @@ export const makePocket = (material: MaterialSide): string => /** * Converts a Material object to its string representation. * - * @param {Material} pocket - The Material object to convert. + * @param {Material} pocket The Material object to convert. * @returns {string} The string representation of the pocket. */ export const makePockets = (pocket: Material): string => @@ -400,8 +400,8 @@ export const makePockets = (pocket: Material): string => /** * Converts the castling rights of a board to its FEN string representation. * - * @param {Board} board - The Board object. - * @param {SquareSet} castlingRights - The castling rights as a SquareSet. + * @param {Board} board The Board object. + * @param {SquareSet} castlingRights The castling rights as a SquareSet. * @returns {string} The FEN string representation of the castling rights. */ export const makeCastlingFen = (board: Board, castlingRights: SquareSet): string => { @@ -428,7 +428,7 @@ export const makeCastlingFen = (board: Board, castlingRights: SquareSet): string /** * Converts a RemainingChecks object to its string representation. * - * @param {RemainingChecks} checks - The RemainingChecks object to convert. + * @param {RemainingChecks} checks The RemainingChecks object to convert. * @returns {string} The string representation of the remaining checks. */ export const makeRemainingChecks = (checks: RemainingChecks): string => `${checks.white}+${checks.black}`; @@ -437,8 +437,8 @@ export const makeRemainingChecks = (checks: RemainingChecks): string => `${check /** * Converts a Setup object to its FEN string representation. * - * @param {Setup} setup - The Setup object to convert. - * @param {FenOpts} [opts] - Optional FEN formatting options. + * @param {Setup} setup The Setup object to convert. + * @param {FenOpts} [opts] Optional FEN formatting options. * @returns {string} The FEN string representation of the setup. */ export const makeFen = (setup: Setup, opts?: FenOpts): string => diff --git a/src/san.ts b/src/san.ts index f9aefbd4..1e3cc3af 100644 --- a/src/san.ts +++ b/src/san.ts @@ -8,8 +8,8 @@ import { charToRole, defined, makeSquare, opposite, parseSquare, roleToChar, squ * Generates the SAN (Standard Algebraic Notation) representation of a move * in the given position without the move suffix (#, +). * - * @param {Position} pos - The chess position. - * @param {Move} move - The move to generate the SAN for. + * @param {Position} pos The chess position. + * @param {Move} move The move to generate the SAN for. * @returns {string} The SAN representation of the move. */ const makeSanWithoutSuffix = (pos: Position, move: Move): string => { @@ -64,8 +64,8 @@ const makeSanWithoutSuffix = (pos: Position, move: Move): string => { * Generates the SAN (Standard Algebraic Notation) representation of a move * in the given position and plays the move on the position. * - * @param {Position} pos - The chess position. - * @param {Move} move - The move to generate the SAN for and play. + * @param {Position} pos The chess position. + * @param {Move} move The move to generate the SAN for and play. * @returns {string} The SAN representation of the move with the move suffix. */ export const makeSanAndPlay = (pos: Position, move: Move): string => { @@ -80,8 +80,8 @@ export const makeSanAndPlay = (pos: Position, move: Move): string => { * Generates the SAN (Standard Algebraic Notation) representation of a variation * (sequence of moves) in the given position. * - * @param {Position} pos - The starting position of the variation. - * @param {Move[]} variation - The sequence of moves in the variation. + * @param {Position} pos The starting position of the variation. + * @param {Move[]} variation The sequence of moves in the variation. * @returns {string} The SAN representation of the variation. */ export const makeSanVariation = (pos: Position, variation: Move[]): string => { @@ -105,8 +105,8 @@ export const makeSanVariation = (pos: Position, variation: Move[]): string => { * Generates the SAN (Standard Algebraic Notation) representation of a move * in the given position without modifying the position. * - * @param {Position} pos - The chess position. - * @param {Move} move - The move to generate the SAN for. + * @param {Position} pos The chess position. + * @param {Move} move The move to generate the SAN for. * @returns {string} The SAN representation of the move. */ export const makeSan = (pos: Position, move: Move): string => makeSanAndPlay(pos.clone(), move); @@ -115,8 +115,8 @@ export const makeSan = (pos: Position, move: Move): string => makeSanAndPlay(pos * Parses a SAN (Standard Algebraic Notation) string and returns the corresponding move * in the given position. * - * @param {Position} pos - The chess position. - * @param {string} san - The SAN string to parse. + * @param {Position} pos The chess position. + * @param {string} san The SAN string to parse. * @returns {Move | undefined} The parsed move, or undefined if the SAN is invalid or ambiguous. */ export const parseSan = (pos: Position, san: string): Move | undefined => { diff --git a/src/setup.ts b/src/setup.ts index 0ff1ed2e..f0b4d612 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -5,12 +5,12 @@ import { ByColor, ByRole, Color, Role, ROLES, Square } from './types.js'; /** * Represents the material configuration for one side (color) in a chess position. * @implements {ByRole} - * @property {number} pawn - The number of pawns on the side. - * @property {number} knight - The number of knights on the side. - * @property {number} bishop - The number of bishops on the side. - * @property {number} rook - The number of rooks on the side. - * @property {number} queen - The number of queens on the side. - * @property {number} king - The number of kings on the side. + * @property {number} pawn The number of pawns on the side. + * @property {number} knight The number of knights on the side. + * @property {number} bishop The number of bishops on the side. + * @property {number} rook The number of rooks on the side. + * @property {number} queen The number of queens on the side. + * @property {number} king The number of kings on the side. */ export class MaterialSide implements ByRole { /** @type {number} */ @@ -40,8 +40,8 @@ export class MaterialSide implements ByRole { /** * Creates a MaterialSide instance from a Board for a specific color. - * @param {Board} board - The Board to create the MaterialSide from. - * @param {Color} color - The color to create the MaterialSide for. + * @param {Board} board The Board to create the MaterialSide from. + * @param {Color} color The color to create the MaterialSide for. * @returns {MaterialSide} The MaterialSide instance derived from the Board. */ static fromBoard(board: Board, color: Color): MaterialSide { @@ -62,7 +62,7 @@ export class MaterialSide implements ByRole { /** * Checks if the MaterialSide instance is equal to another MaterialSide instance. - * @param {MaterialSide} other - The other MaterialSide instance to compare. + * @param {MaterialSide} other The other MaterialSide instance to compare. * @returns {boolean} True if the MaterialSide instances are equal, false otherwise. */ equals(other: MaterialSide): boolean { @@ -71,7 +71,7 @@ export class MaterialSide implements ByRole { /** * Adds another MaterialSide instance to the current MaterialSide instance. - * @param {MaterialSide} other - The MaterialSide instance to add. + * @param {MaterialSide} other The MaterialSide instance to add. * @returns {MaterialSide} A new MaterialSide instance representing the sum. */ add(other: MaterialSide): MaterialSide { @@ -82,7 +82,7 @@ export class MaterialSide implements ByRole { /** * Subtracts another MaterialSide instance from the current MaterialSide instance. - * @param {MaterialSide} other - The MaterialSide instance to subtract. + * @param {MaterialSide} other The MaterialSide instance to subtract. * @returns {MaterialSide} A new MaterialSide instance representing the difference. */ subtract(other: MaterialSide): MaterialSide { @@ -135,14 +135,14 @@ export class MaterialSide implements ByRole { /** * Represents the material configuration of a chess position. * @implements {ByColor} - * @property {MaterialSide} white - The material configuration for white. - * @property {MaterialSide} black - The material configuration for black. + * @property {MaterialSide} white The material configuration for white. + * @property {MaterialSide} black The material configuration for black. */ export class Material implements ByColor { /** * Creates a new Material instance. - * @param {MaterialSide} white - The material configuration for white. - * @param {MaterialSide} black - The material configuration for black. + * @param {MaterialSide} white The material configuration for white. + * @param {MaterialSide} black The material configuration for black. */ constructor( public white: MaterialSide, @@ -159,7 +159,7 @@ export class Material implements ByColor { /** * Creates a Material instance from a Board. - * @param {Board} board - The Board to create the Material from. + * @param {Board} board The Board to create the Material from. * @returns {Material} The Material instance derived from the Board. */ static fromBoard(board: Board): Material { @@ -176,7 +176,7 @@ export class Material implements ByColor { /** * Checks if the Material instance is equal to another Material instance. - * @param {Material} other - The other Material instance to compare. + * @param {Material} other The other Material instance to compare. * @returns {boolean} True if the Material instances are equal, false otherwise. */ equals(other: Material): boolean { @@ -185,7 +185,7 @@ export class Material implements ByColor { /** * Adds another Material instance to the current Material instance. - * @param {Material} other - The Material instance to add. + * @param {Material} other The Material instance to add. * @returns {Material} A new Material instance representing the sum. */ add(other: Material): Material { @@ -194,7 +194,7 @@ export class Material implements ByColor { /** * Subtracts another Material instance from the current Material instance. - * @param {Material} other - The Material instance to subtract. + * @param {Material} other The Material instance to subtract. * @returns {Material} A new Material instance representing the difference. */ subtract(other: Material): Material { @@ -203,7 +203,7 @@ export class Material implements ByColor { /** * Counts the number of pieces of a specific role. - * @param {Role} role - The role to count. + * @param {Role} role The role to count. * @returns {number} The count of pieces with the specified role. */ count(role: Role): number { @@ -273,14 +273,14 @@ export class RemainingChecks implements ByColor { /** * Represents the setup of a chess position. * @interface Setup - * @property {Board} board - The chess board. - * @property {Material | undefined} pockets - The material in the pockets (optional). - * @property {Color} turn - The color of the side to move. - * @property {SquareSet} castlingRights - The castling rights. - * @property {Square | undefined} epSquare - The en passant square (optional). - * @property {RemainingChecks | undefined} remainingChecks - The remaining checks (optional). - * @property {number} halfmoves - The number of halfmoves since the last pawn advance or capture. - * @property {number} fullmoves - The number of fullmoves. + * @property {Board} board The chess board. + * @property {Material | undefined} pockets The material in the pockets (optional). + * @property {Color} turn The color of the side to move. + * @property {SquareSet} castlingRights The castling rights. + * @property {Square | undefined} epSquare The en passant square (optional). + * @property {RemainingChecks | undefined} remainingChecks The remaining checks (optional). + * @property {number} halfmoves The number of halfmoves since the last pawn advance or capture. + * @property {number} fullmoves The number of fullmoves. */ export interface Setup { board: Board; @@ -310,7 +310,7 @@ export const defaultSetup = (): Setup => ({ /** * Creates a clone of a given setup. - * @param {Setup} setup - The setup to clone. + * @param {Setup} setup The setup to clone. * @returns {Setup} The cloned setup. */ export const setupClone = (setup: Setup): Setup => ({ @@ -326,8 +326,8 @@ export const setupClone = (setup: Setup): Setup => ({ /** * Checks if two setups are equal. - * @param {Setup} left - The first setup. - * @param {Setup} right - The second setup. + * @param {Setup} left The first setup. + * @param {Setup} right The second setup. * @returns {boolean} True if the setups are equal, false otherwise. */ export const setupEquals = (left: Setup, right: Setup): boolean => diff --git a/src/squareSet.ts b/src/squareSet.ts index 61b1b81a..9db2b793 100644 --- a/src/squareSet.ts +++ b/src/squareSet.ts @@ -141,7 +141,7 @@ export class SquareSet implements Iterable { * The XOR operation returns a new SquareSet that contains the squares that are present * in either the current set or the other set, but not both. * - * @param {SquareSet} other - The SquareSet to perform the XOR operation with. + * @param {SquareSet} other The SquareSet to perform the XOR operation with. * @returns {SquareSet} A new SquareSet representing the result of the XOR operation. */ xor(other: SquareSet): SquareSet { @@ -154,7 +154,7 @@ export class SquareSet implements Iterable { * The OR operation returns a new SquareSet that contains the squares that are present * in either the current set or the other set, or both. * - * @param {SquareSet} other - The SquareSet to perform the OR operation with. + * @param {SquareSet} other The SquareSet to perform the OR operation with. * @returns {SquareSet} A new SquareSet representing the result of the OR operation. */ union(other: SquareSet): SquareSet { @@ -167,7 +167,7 @@ export class SquareSet implements Iterable { * The AND operation returns a new SquareSet that contains the squares that are present * in both the current set and the other set. * - * @param {SquareSet} other - The SquareSet to perform the AND operation with. + * @param {SquareSet} other The SquareSet to perform the AND operation with. * @returns {SquareSet} A new SquareSet representing the result of the AND operation. */ intersect(other: SquareSet): SquareSet { @@ -180,7 +180,7 @@ export class SquareSet implements Iterable { * The AND NOT operation returns a new SquareSet that contains the squares that are present * in the current set, but not in the other set. * - * @param {SquareSet} other - The SquareSet to perform the AND NOT operation with. + * @param {SquareSet} other The SquareSet to perform the AND NOT operation with. * @returns {SquareSet} A new SquareSet representing the result of the AND NOT operation. */ diff(other: SquareSet): SquareSet { @@ -192,7 +192,7 @@ export class SquareSet implements Iterable { * * Two SquareSets are considered to intersect if they have at least one square in common. * - * @param {SquareSet} other - The SquareSet to check for intersection. + * @param {SquareSet} other The SquareSet to check for intersection. * @returns {boolean} True if the current set intersects with the other set, false otherwise. */ intersects(other: SquareSet): boolean { @@ -204,7 +204,7 @@ export class SquareSet implements Iterable { * * Two SquareSets are considered to be disjoint if they have no squares in common. * - * @param {SquareSet} other - The SquareSet to check for disjointness. + * @param {SquareSet} other The SquareSet to check for disjointness. * @returns {boolean} True if the current set is disjoint with the other set, false otherwise. */ isDisjoint(other: SquareSet): boolean { @@ -216,7 +216,7 @@ export class SquareSet implements Iterable { * * A SquareSet is a superset of another SquareSet if every square in the other set is also present in the current set. * - * @param {SquareSet} other - The SquareSet to check for supersetness. + * @param {SquareSet} other The SquareSet to check for supersetness. * @returns {boolean} True if the current set is a superset of the other set, false otherwise. */ supersetOf(other: SquareSet): boolean { @@ -228,7 +228,7 @@ export class SquareSet implements Iterable { * * A SquareSet is a subset of another SquareSet if every square in the current set is also present in the other set. * - * @param {SquareSet} other - The SquareSet to check for subsetness. + * @param {SquareSet} other The SquareSet to check for subsetness. * @returns {boolean} True if the current set is a subset of the other set, false otherwise. */ subsetOf(other: SquareSet): boolean { @@ -241,7 +241,7 @@ export class SquareSet implements Iterable { * The right shift operation shifts the bits of the SquareSet towards the right by the given * number of positions. The vacated bits on the left side are filled with zeros. * - * @param {number} shift - The number of positions to shift the bits to the right. + * @param {number} shift The number of positions to shift the bits to the right. * @returns {SquareSet} A new SquareSet representing the result of the right shift operation. */ shr64(shift: number): SquareSet { @@ -257,7 +257,7 @@ export class SquareSet implements Iterable { * The left shift operation shifts the bits of the SquareSet towards the left by the given * number of positions. The vacated bits on the right side are filled with zeros. * - * @param {number} shift - The number of positions to shift the bits to the left. + * @param {number} shift The number of positions to shift the bits to the left. * @returns {SquareSet} A new SquareSet representing the result of the left shift operation. */ shl64(shift: number): SquareSet { @@ -288,7 +288,7 @@ export class SquareSet implements Iterable { /** * Subtracts another SquareSet from the current SquareSet in a 64-bit manner. * - * @param {SquareSet} other - The SquareSet to subtract. + * @param {SquareSet} other The SquareSet to subtract. * @returns {SquareSet} A new SquareSet representing the result of the subtraction. */ minus64(other: SquareSet): SquareSet { @@ -300,7 +300,7 @@ export class SquareSet implements Iterable { /** * Checks if the current SquareSet is equal to another SquareSet. * - * @param {SquareSet} other - The SquareSet to compare with. + * @param {SquareSet} other The SquareSet to compare with. * @returns {boolean} True if the SquareSets are equal, false otherwise. */ equals(other: SquareSet): boolean { @@ -337,7 +337,7 @@ export class SquareSet implements Iterable { /** * Checks if the SquareSet contains a specific square. * - * @param {Square} square - The square to check for presence. + * @param {Square} square The square to check for presence. * @returns {boolean} True if the SquareSet contains the square, false otherwise. */ has(square: Square): boolean { @@ -347,8 +347,8 @@ export class SquareSet implements Iterable { /** * Sets or unsets a square in the SquareSet. * - * @param {Square} square - The square to set or unset. - * @param {boolean} on - True to set the square, false to unset it. + * @param {Square} square The square to set or unset. + * @param {boolean} on True to set the square, false to unset it. * @returns {SquareSet} A new SquareSet with the square set or unset. */ set(square: Square, on: boolean): SquareSet { @@ -358,7 +358,7 @@ export class SquareSet implements Iterable { /** * Adds a square to the SquareSet. * - * @param {Square} square - The square to add. + * @param {Square} square The square to add. * @returns {SquareSet} A new SquareSet with the square added. */ with(square: Square): SquareSet { @@ -370,7 +370,7 @@ export class SquareSet implements Iterable { /** * Removes a square from the SquareSet. * - * @param {Square} square - The square to remove. + * @param {Square} square The square to remove. * @returns {SquareSet} A new SquareSet with the square removed. */ without(square: Square): SquareSet { @@ -382,7 +382,7 @@ export class SquareSet implements Iterable { /** * Toggles the presence of a square in the SquareSet. * - * @param {Square} square - The square to toggle. + * @param {Square} square The square to toggle. * @returns {SquareSet} A new SquareSet with the square toggled. */ toggle(square: Square): SquareSet { diff --git a/src/transform.ts b/src/transform.ts index 1c7a699c..0a2ddae9 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -7,7 +7,7 @@ import { defined } from './util.js'; /** * Flips a SquareSet vertically. * -* @param {SquareSet} s - The SquareSet to flip. +* @param {SquareSet} s The SquareSet to flip. * @returns {SquareSet} The flipped SquareSet. */ export const flipVertical = (s: SquareSet): SquareSet => s.bswap64(); @@ -15,7 +15,7 @@ export const flipVertical = (s: SquareSet): SquareSet => s.bswap64(); /** * Flips a SquareSet horizontally. * -* @param {SquareSet} s - The SquareSet to flip. +* @param {SquareSet} s The SquareSet to flip. * @returns {SquareSet} The flipped SquareSet. */ export const flipHorizontal = (s: SquareSet): SquareSet => { @@ -31,7 +31,7 @@ export const flipHorizontal = (s: SquareSet): SquareSet => { /** * Flips a SquareSet diagonally. * -* @param {SquareSet} s - The SquareSet to flip. +* @param {SquareSet} s The SquareSet to flip. * @returns {SquareSet} The flipped SquareSet. */ export const flipDiagonal = (s: SquareSet): SquareSet => { @@ -47,7 +47,7 @@ export const flipDiagonal = (s: SquareSet): SquareSet => { /** * Rotates a SquareSet by 180 degrees. * -* @param {SquareSet} s - The SquareSet to rotate. +* @param {SquareSet} s The SquareSet to rotate. * @returns {SquareSet} The rotated SquareSet. */ export const rotate180 = (s: SquareSet): SquareSet => s.rbit64(); @@ -55,8 +55,8 @@ export const rotate180 = (s: SquareSet): SquareSet => s.rbit64(); /** * Transforms a Board by applying a transformation function to each SquareSet. * -* @param {Board} board - The Board to transform. -* @param {function(SquareSet): SquareSet} f - The transformation function. +* @param {Board} board The Board to transform. +* @param {function(SquareSet): SquareSet} f The transformation function. * @returns {Board} The transformed Board. */ export const transformBoard = (board: Board, f: (s: SquareSet) => SquareSet): Board => { @@ -71,8 +71,8 @@ export const transformBoard = (board: Board, f: (s: SquareSet) => SquareSet): Bo /** * Transforms a Setup by applying a transformation function to each SquareSet. * -* @param {Setup} setup - The Setup to transform. -* @param {function(SquareSet): SquareSet} f - The transformation function. +* @param {Setup} setup The Setup to transform. +* @param {function(SquareSet): SquareSet} f The transformation function. * @returns {Setup} The transformed Setup. */ export const transformSetup = (setup: Setup, f: (s: SquareSet) => SquareSet): Setup => ({ diff --git a/src/types.ts b/src/types.ts index b5dc53e2..9ca7b265 100644 --- a/src/types.ts +++ b/src/types.ts @@ -98,9 +98,9 @@ export type ByCastlingSide = { /** * An interface representing a chess piece. * @interface Piece - * @property {Role} role - The role of the piece. - * @property {Color} color - The color of the piece. - * @property {boolean} [promoted] - Whether the piece is promoted (optional). + * @property {Role} role The role of the piece. + * @property {Color} color The color of the piece. + * @property {boolean} [promoted] Whether the piece is promoted (optional). */ export interface Piece { role: Role; @@ -111,9 +111,9 @@ export interface Piece { /** * An interface representing a normal chess move. * @interface NormalMove - * @property {Square} from - The starting square of the move. - * @property {Square} to - The destination square of the move. - * @property {Role} [promotion] - The role to promote the pawn to (optional). + * @property {Square} from The starting square of the move. + * @property {Square} to The destination square of the move. + * @property {Role} [promotion] The role to promote the pawn to (optional). */ export interface NormalMove { from: Square; @@ -124,8 +124,8 @@ export interface NormalMove { /** * An interface representing a drop move in chess variants. * @interface DropMove - * @property {Role} role - The role of the piece being dropped. - * @property {Square} to - The square where the piece is dropped. + * @property {Role} role The role of the piece being dropped. + * @property {Square} to The square where the piece is dropped. */ export interface DropMove { role: Role; @@ -140,7 +140,7 @@ export type Move = NormalMove | DropMove; /** * A type guard function to check if a move is a drop move. * @function isDrop - * @param {Move} v - The move to check. + * @param {Move} v The move to check. * @returns {v is DropMove} - Returns true if the move is a drop move. */ export const isDrop = (v: Move): v is DropMove => 'role' in v; @@ -148,7 +148,7 @@ export const isDrop = (v: Move): v is DropMove => 'role' in v; /** * A type guard function to check if a move is a normal move. * @function isNormal - * @param {Move} v - The move to check. + * @param {Move} v The move to check. * @returns {v is NormalMove} - Returns true if the move is a normal move. */ export const isNormal = (v: Move): v is NormalMove => 'from' in v; @@ -176,7 +176,7 @@ export type Rules = (typeof RULES)[number]; /** * An interface representing the outcome of a chess game. * @interface Outcome - * @property {Color | undefined} winner - The color of the winning side, or undefined if the game is a draw. + * @property {Color | undefined} winner The color of the winning side, or undefined if the game is a draw. */ export interface Outcome { winner: Color | undefined; diff --git a/src/util.ts b/src/util.ts index 43e4ad38..cf378ec3 100644 --- a/src/util.ts +++ b/src/util.ts @@ -57,8 +57,8 @@ export const squareFromCoords = (file: number, rank: number): Square | undefined /** * A function to convert a chess piece role to its corresponding character representation. * @function roleToChar - * @param {Role} role - The chess piece role. - * @returns {string} - The character representation of the role. + * @param {Role} role The chess piece role. + * @returns {string} The character representation of the role. */ export const roleToChar = (role: Role): string => { switch (role) { @@ -80,8 +80,8 @@ export const roleToChar = (role: Role): string => { /** * A function to convert a character to its corresponding chess piece role. * @function charToRole - * @param {string} ch - The character to convert. - * @returns {Role | undefined} - The corresponding chess piece role, or undefined if the character is not valid. + * @param {string} ch The character to convert. + * @returns {Role | undefined} The corresponding chess piece role, or undefined if the character is not valid. */ export function charToRole(ch: 'p' | 'n' | 'b' | 'r' | 'q' | 'k' | 'P' | 'N' | 'B' | 'R' | 'Q' | 'K'): Role; export function charToRole(ch: string): Role | undefined; @@ -107,8 +107,8 @@ export function charToRole(ch: string): Role | undefined { /** * A function to parse a square name and return the corresponding square. * @function parseSquare - * @param {string} str - The square name to parse. - * @returns {Square | undefined} - The corresponding square, or undefined if the square name is not valid. + * @param {string} str The square name to parse. + * @returns {Square | undefined} The corresponding square, or undefined if the square name is not valid. */ export function parseSquare(str: SquareName): Square; export function parseSquare(str: string): Square | undefined; @@ -120,8 +120,8 @@ export function parseSquare(str: string): Square | undefined { /** * A function to convert a square to its corresponding square name. * @function makeSquare - * @param {Square} square - The square to convert. - * @returns {SquareName} - The corresponding square name. + * @param {Square} square The square to convert. + * @returns {SquareName} The corresponding square name. */ export const makeSquare = (square: Square): SquareName => (FILE_NAMES[squareFile(square)] + RANK_NAMES[squareRank(square)]) as SquareName; @@ -129,8 +129,8 @@ export const makeSquare = (square: Square): SquareName => /** * A function to parse a UCI (Universal Chess Interface) string and return the corresponding move. * @function parseUci - * @param {string} str - The UCI string to parse. - * @returns {Move | undefined} - The corresponding move, or undefined if the UCI string is not valid. + * @param {string} str The UCI string to parse. + * @returns {Move | undefined} The corresponding move, or undefined if the UCI string is not valid. */ export const parseUci = (str: string): Move | undefined => { if (str[1] === '@' && str.length === 4) { @@ -153,9 +153,9 @@ export const parseUci = (str: string): Move | undefined => { /** * A function to check if two moves are equal. * @function moveEquals - * @param {Move} left - The first move to compare. - * @param {Move} right - The second move to compare. - * @returns {boolean} - True if the moves are equal, false otherwise. + * @param {Move} left The first move to compare. + * @param {Move} right The second move to compare. + * @returns {boolean} True if the moves are equal, false otherwise. */ export const moveEquals = (left: Move, right: Move): boolean => { if (left.to !== right.to) return false; @@ -166,8 +166,8 @@ export const moveEquals = (left: Move, right: Move): boolean => { /** * A function to convert a move to its corresponding UCI string representation. * @function makeUci - * @param {Move} move - The move to convert. - * @returns {string} - The corresponding UCI string representation of the move. + * @param {Move} move The move to convert. + * @returns {string} The corresponding UCI string representation of the move. */ export const makeUci = (move: Move): string => isDrop(move) @@ -177,9 +177,9 @@ export const makeUci = (move: Move): string => /** * A function to get the square where the king castles to for a given color and castling side. * @function kingCastlesTo - * @param {Color} color - The color of the king. - * @param {CastlingSide} side - The castling side ('a' for queenside, 'h' for kingside). - * @returns {Square} - The square where the king castles to. + * @param {Color} color The color of the king. + * @param {CastlingSide} side The castling side ('a' for queenside, 'h' for kingside). + * @returns {Square} The square where the king castles to. */ export const kingCastlesTo = (color: Color, side: CastlingSide): Square => color === 'white' ? (side === 'a' ? 2 : 6) : side === 'a' ? 58 : 62; @@ -187,9 +187,9 @@ export const kingCastlesTo = (color: Color, side: CastlingSide): Square => /** * A function to get the square where the rook castles to for a given color and castling side. * @function rookCastlesTo - * @param {Color} color - The color of the rook. - * @param {CastlingSide} side - The castling side ('a' for queenside, 'h' for kingside). - * @returns {Square} - The square where the rook castles to. + * @param {Color} color The color of the rook. + * @param {CastlingSide} side The castling side ('a' for queenside, 'h' for kingside). + * @returns {Square} The square where the rook castles to. */ export const rookCastlesTo = (color: Color, side: CastlingSide): Square => color === 'white' ? (side === 'a' ? 3 : 5) : side === 'a' ? 59 : 61; \ No newline at end of file From 52ef051eabbf2c64e5df38f07e7b44c754d1e125 Mon Sep 17 00:00:00 2001 From: Steven Stavrakis Date: Mon, 3 Jun 2024 16:14:11 -0400 Subject: [PATCH 3/6] fix some true/false jsdoc --- src/setup.ts | 79 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/src/setup.ts b/src/setup.ts index f0b4d612..3a5b72f1 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -13,20 +13,37 @@ import { ByColor, ByRole, Color, Role, ROLES, Square } from './types.js'; * @property {number} king The number of kings on the side. */ export class MaterialSide implements ByRole { - /** @type {number} */ + /** + * The number of pawns. + */ pawn: number; - /** @type {number} */ + + /** + * The number of knights. + */ knight: number; - /** @type {number} */ + + /** + * The number of bishops. + */ bishop: number; - /** @type {number} */ + + /** + * The number of rooks. + */ rook: number; - /** @type {number} */ + + /** + * The number of queens. + */ queen: number; - /** @type {number} */ + + /** + * The number of kings. + */ king: number; - private constructor() {} + private constructor() { } /** * Creates an empty MaterialSide instance. @@ -63,7 +80,7 @@ export class MaterialSide implements ByRole { /** * Checks if the MaterialSide instance is equal to another MaterialSide instance. * @param {MaterialSide} other The other MaterialSide instance to compare. - * @returns {boolean} True if the MaterialSide instances are equal, false otherwise. + * @returns {boolean} `true` if the MaterialSide instances are equal, `false` otherwise. */ equals(other: MaterialSide): boolean { return ROLES.every(role => this[role] === other[role]); @@ -93,7 +110,7 @@ export class MaterialSide implements ByRole { /** * Checks if the MaterialSide is not empty (has pieces). - * @returns {boolean} True if the MaterialSide is not empty, false otherwise. + * @returns {boolean} `true` if the MaterialSide is not empty, `false` otherwise. */ nonEmpty(): boolean { return ROLES.some(role => this[role] > 0); @@ -101,7 +118,7 @@ export class MaterialSide implements ByRole { /** * Checks if the MaterialSide is empty (no pieces). - * @returns {boolean} True if the MaterialSide is empty, false otherwise. + * @returns {boolean} `true` if the MaterialSide is empty, `false` otherwise. */ isEmpty(): boolean { return !this.nonEmpty(); @@ -109,7 +126,7 @@ export class MaterialSide implements ByRole { /** * Checks if the MaterialSide has pawns. - * @returns {boolean} True if the MaterialSide has pawns, false otherwise. + * @returns {boolean} `true` if the MaterialSide has pawns, `false` otherwise. */ hasPawns(): boolean { return this.pawn > 0; @@ -117,7 +134,7 @@ export class MaterialSide implements ByRole { /** * Checks if the MaterialSide has non-pawn pieces. - * @returns {boolean} True if the MaterialSide has non-pawn pieces, false otherwise. + * @returns {boolean} `true` if the MaterialSide has non-pawn pieces, `false` otherwise. */ hasNonPawns(): boolean { return this.knight > 0 || this.bishop > 0 || this.rook > 0 || this.queen > 0 || this.king > 0; @@ -147,7 +164,7 @@ export class Material implements ByColor { constructor( public white: MaterialSide, public black: MaterialSide, - ) {} + ) { } /** * Creates an empty Material instance. @@ -177,7 +194,7 @@ export class Material implements ByColor { /** * Checks if the Material instance is equal to another Material instance. * @param {Material} other The other Material instance to compare. - * @returns {boolean} True if the Material instances are equal, false otherwise. + * @returns {boolean} `true` if the Material instances are equal, `false` otherwise. */ equals(other: Material): boolean { return this.white.equals(other.white) && this.black.equals(other.black); @@ -220,7 +237,7 @@ export class Material implements ByColor { /** * Checks if the Material is empty (no pieces). - * @returns {boolean} True if the Material is empty, false otherwise. + * @returns {boolean} `true` if the Material is empty, `false` otherwise. */ isEmpty(): boolean { return this.white.isEmpty() && this.black.isEmpty(); @@ -228,7 +245,7 @@ export class Material implements ByColor { /** * Checks if the Material is not empty (has pieces). - * @returns {boolean} True if the Material is not empty, false otherwise. + * @returns {boolean} `true` if the Material is not empty, `false` otherwise. */ nonEmpty(): boolean { return !this.isEmpty(); @@ -236,7 +253,7 @@ export class Material implements ByColor { /** * Checks if the Material has pawns. - * @returns {boolean} True if the Material has pawns, false otherwise. + * @returns {boolean} `true` if the Material has pawns, `false` otherwise. */ hasPawns(): boolean { return this.white.hasPawns() || this.black.hasPawns(); @@ -244,27 +261,49 @@ export class Material implements ByColor { /** * Checks if the Material has non-pawn pieces. - * @returns {boolean} True if the Material has non-pawn pieces, false otherwise. + * @returns {boolean} `true` if the Material has non-pawn pieces, `false` otherwise. */ hasNonPawns(): boolean { return this.white.hasNonPawns() || this.black.hasNonPawns(); } } +/** + * Represents the remaining checks count for each color. + * @implements {ByColor} + */ export class RemainingChecks implements ByColor { + /** + * Creates a new instance of the RemainingChecks class. + * @param {number} white The remaining checks count for the white player. + * @param {number} black The remaining checks count for the black player. + */ constructor( public white: number, public black: number, - ) {} + ) { } + /** + * Returns the default remaining checks count for each color. + * @returns {RemainingChecks} The default remaining checks count. + */ static default(): RemainingChecks { return new RemainingChecks(3, 3); } + /** + * Creates a clone of the RemainingChecks instance. + * @returns {RemainingChecks} A new instance with the same remaining checks count. + */ clone(): RemainingChecks { return new RemainingChecks(this.white, this.black); } + /** + * Checks if the RemainingChecks instance is equal to another instance. + * @param {RemainingChecks} other The other RemainingChecks instance to compare. + * @returns {boolean} `true` if the instances are equal, `false` otherwise. + */ equals(other: RemainingChecks): boolean { return this.white === other.white && this.black === other.black; } @@ -328,7 +367,7 @@ export const setupClone = (setup: Setup): Setup => ({ * Checks if two setups are equal. * @param {Setup} left The first setup. * @param {Setup} right The second setup. - * @returns {boolean} True if the setups are equal, false otherwise. + * @returns {boolean} `true` if the setups are equal, `false` otherwise. */ export const setupEquals = (left: Setup, right: Setup): boolean => boardEquals(left.board, right.board) From 2ef1c2ce9e994091c6d851ad0ce9d1b2fb6815f9 Mon Sep 17 00:00:00 2001 From: Steven Stavrakis Date: Mon, 3 Jun 2024 16:23:52 -0400 Subject: [PATCH 4/6] more fixes to true/false. Improved types on some classes --- src/board.ts | 4 ++-- src/setup.ts | 37 ++++++++++++++++++++++++++++++++++--- src/squareSet.ts | 20 ++++++++++---------- src/util.ts | 2 +- 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/board.ts b/src/board.ts index 1cf5638c..122667c8 100644 --- a/src/board.ts +++ b/src/board.ts @@ -172,7 +172,7 @@ export class Board implements Iterable<[Square, Piece]>, ByRole, ByCo /** * Checks if the given square is occupied by a piece. * @param {Square} square The square to check. - * @returns {boolean} True if the square is occupied, false otherwise. + * @returns {boolean} `true` if the square is occupied, `false` otherwise. */ has(square: Square): boolean { return this.occupied.has(square); @@ -228,7 +228,7 @@ export class Board implements Iterable<[Square, Piece]>, ByRole, ByCo * Checks if two boards are equal. * @param {Board} left The first board. * @param {Board} right The second board. - * @returns {boolean} True if the boards are equal, false otherwise. + * @returns {boolean} `true` if the boards are equal, `false` otherwise. */ export const boardEquals = (left: Board, right: Board): boolean => left.white.equals(right.white) diff --git a/src/setup.ts b/src/setup.ts index 3a5b72f1..fd71de5f 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -313,22 +313,53 @@ export class RemainingChecks implements ByColor { * Represents the setup of a chess position. * @interface Setup * @property {Board} board The chess board. - * @property {Material | undefined} pockets The material in the pockets (optional). + * @property {Material | undefined} pockets The material in the pockets. * @property {Color} turn The color of the side to move. * @property {SquareSet} castlingRights The castling rights. - * @property {Square | undefined} epSquare The en passant square (optional). - * @property {RemainingChecks | undefined} remainingChecks The remaining checks (optional). + * @property {Square | undefined} epSquare A square where en passant is possible. + * @property {RemainingChecks | undefined} remainingChecks The remaining checks. * @property {number} halfmoves The number of halfmoves since the last pawn advance or capture. * @property {number} fullmoves The number of fullmoves. */ export interface Setup { + /** + * The chess board. + */ board: Board; + /** + * The material in the pockets. + */ pockets: Material | undefined; + /** + * The color of the side to move. + */ turn: Color; + /** + * The castling rights. + */ castlingRights: SquareSet; + /** + * A square where en passant is possible. + */ epSquare: Square | undefined; + + /** + * The remaining checks. Used in multi-check variants. + */ remainingChecks: RemainingChecks | undefined; + + /** + * The number of halfmoves since the last pawn advance or capture. + * + * A halfmove is when a side makes a move. + */ halfmoves: number; + + /** + * The number of fullmoves. + * + * A fullmove is when both sides make a move. + */ fullmoves: number; } diff --git a/src/squareSet.ts b/src/squareSet.ts index 9db2b793..d308abcb 100644 --- a/src/squareSet.ts +++ b/src/squareSet.ts @@ -193,7 +193,7 @@ export class SquareSet implements Iterable { * Two SquareSets are considered to intersect if they have at least one square in common. * * @param {SquareSet} other The SquareSet to check for intersection. - * @returns {boolean} True if the current set intersects with the other set, false otherwise. + * @returns {boolean} `true` if the current set intersects with the other set, `false` otherwise. */ intersects(other: SquareSet): boolean { return this.intersect(other).nonEmpty(); @@ -205,7 +205,7 @@ export class SquareSet implements Iterable { * Two SquareSets are considered to be disjoint if they have no squares in common. * * @param {SquareSet} other The SquareSet to check for disjointness. - * @returns {boolean} True if the current set is disjoint with the other set, false otherwise. + * @returns {boolean} `true` if the current set is disjoint with the other set, `false` otherwise. */ isDisjoint(other: SquareSet): boolean { return this.intersect(other).isEmpty(); @@ -217,7 +217,7 @@ export class SquareSet implements Iterable { * A SquareSet is a superset of another SquareSet if every square in the other set is also present in the current set. * * @param {SquareSet} other The SquareSet to check for supersetness. - * @returns {boolean} True if the current set is a superset of the other set, false otherwise. + * @returns {boolean} `true` if the current set is a superset of the other set, `false` otherwise. */ supersetOf(other: SquareSet): boolean { return other.diff(this).isEmpty(); @@ -229,7 +229,7 @@ export class SquareSet implements Iterable { * A SquareSet is a subset of another SquareSet if every square in the current set is also present in the other set. * * @param {SquareSet} other The SquareSet to check for subsetness. - * @returns {boolean} True if the current set is a subset of the other set, false otherwise. + * @returns {boolean} `true` if the current set is a subset of the other set, `false` otherwise. */ subsetOf(other: SquareSet): boolean { return this.diff(other).isEmpty(); @@ -301,7 +301,7 @@ export class SquareSet implements Iterable { * Checks if the current SquareSet is equal to another SquareSet. * * @param {SquareSet} other The SquareSet to compare with. - * @returns {boolean} True if the SquareSets are equal, false otherwise. + * @returns {boolean} `true` if the SquareSets are equal, `false` otherwise. */ equals(other: SquareSet): boolean { return this.lo === other.lo && this.hi === other.hi; @@ -319,7 +319,7 @@ export class SquareSet implements Iterable { /** * Checks if the SquareSet is empty. * - * @returns {boolean} True if the SquareSet is empty, false otherwise. + * @returns {boolean} `true` if the SquareSet is empty, `false` otherwise. */ isEmpty(): boolean { return this.lo === 0 && this.hi === 0; @@ -328,7 +328,7 @@ export class SquareSet implements Iterable { /** * Checks if the SquareSet is not empty. * - * @returns {boolean} True if the SquareSet is not empty, false otherwise. + * @returns {boolean} `true` if the SquareSet is not empty, `false` otherwise. */ nonEmpty(): boolean { return this.lo !== 0 || this.hi !== 0; @@ -338,7 +338,7 @@ export class SquareSet implements Iterable { * Checks if the SquareSet contains a specific square. * * @param {Square} square The square to check for presence. - * @returns {boolean} True if the SquareSet contains the square, false otherwise. + * @returns {boolean} `true` if the SquareSet contains the square, `false` otherwise. */ has(square: Square): boolean { return (square >= 32 ? this.hi & (1 << (square - 32)) : this.lo & (1 << square)) !== 0; @@ -348,7 +348,7 @@ export class SquareSet implements Iterable { * Sets or unsets a square in the SquareSet. * * @param {Square} square The square to set or unset. - * @param {boolean} on True to set the square, false to unset it. + * @param {boolean} on `true` to set the square, `false` to unset it. * @returns {SquareSet} A new SquareSet with the square set or unset. */ set(square: Square, on: boolean): SquareSet { @@ -426,7 +426,7 @@ export class SquareSet implements Iterable { /** * Checks if the SquareSet contains more than one square. * - * @returns {boolean} True if the SquareSet contains more than one square, false otherwise. + * @returns {boolean} `true` if the SquareSet contains more than one square, `false` otherwise. */ moreThanOne(): boolean { return (this.hi !== 0 && this.lo !== 0) || (this.lo & (this.lo - 1)) !== 0 || (this.hi & (this.hi - 1)) !== 0; diff --git a/src/util.ts b/src/util.ts index cf378ec3..2944406f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -155,7 +155,7 @@ export const parseUci = (str: string): Move | undefined => { * @function moveEquals * @param {Move} left The first move to compare. * @param {Move} right The second move to compare. - * @returns {boolean} True if the moves are equal, false otherwise. + * @returns {boolean} `true` if the moves are equal, `false` otherwise. */ export const moveEquals = (left: Move, right: Move): boolean => { if (left.to !== right.to) return false; From e4d7571073b035490da84063d697aa1d4ee23213 Mon Sep 17 00:00:00 2001 From: Steven Stavrakis Date: Mon, 3 Jun 2024 17:02:51 -0400 Subject: [PATCH 5/6] adjust jsdoc in chess.ts --- src/chess.ts | 186 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 116 insertions(+), 70 deletions(-) diff --git a/src/chess.ts b/src/chess.ts index abc30362..8779ea02 100644 --- a/src/chess.ts +++ b/src/chess.ts @@ -67,22 +67,38 @@ const attacksTo = (square: Square, attacker: Color, board: Board, occupied: Squa .union(pawnAttacks(opposite(attacker), square).intersect(board.pawn)), ); + /** -* TODO: Not sure what this is +* Represents the castling rights and related information for a chess position. */ export class Castles { - /** @type {SquareSet} */ + /** + * The castling rights as a set of squares. + * @type {SquareSet} + */ castlingRights: SquareSet; - /** @type {ByColor>} */ + + /** + * The rook positions for each color and castling side. + * @type {ByColor>} + */ rook: ByColor>; - /** @type {ByColor>} */ + + /** + * The castling paths for each color and castling side. + * @type {ByColor>} + */ path: ByColor>; + /** + * Creates a new instance of the Castles class. + * @private + */ private constructor() { } /** - * Creates a new Castles instance with default castling rights. - * @returns {Castles} The default Castles instance. + * Returns the default castling rights and setup. + * @returns {Castles} The default castling rights and setup. */ static default(): Castles { const castles = new Castles(); @@ -99,8 +115,8 @@ export class Castles { } /** - * Creates a new empty Castles instance. - * @returns {Castles} The empty Castles instance. + * Returns an empty castling rights setup. + * @returns {Castles} The empty castling rights setup. */ static empty(): Castles { const castles = new Castles(); @@ -117,8 +133,8 @@ export class Castles { } /** - * Creates a clone of the current Castles instance. - * @returns {Castles} The cloned Castles instance. + * Creates a clone of the Castles instance. + * @returns {Castles} A new instance with the same castling rights and setup. */ clone(): Castles { const castles = new Castles(); @@ -135,11 +151,12 @@ export class Castles { } /** - * Adds castling rights for the given color and side. - * @param {Color} color The color. - * @param {CastlingSide} side The castling side. - * @param {Square} king The king's square. - * @param {Square} rook The rook's square. + * Adds a castling right for the given color, side, king, and rook positions. + * @param {Color} color The color of the castling side. + * @param {CastlingSide} side The castling side ('a' for queenside, 'h' for kingside). + * @param {Square} king The position of the king. + * @param {Square} rook The position of the rook. + * @private */ private add(color: Color, side: CastlingSide, king: Square, rook: Square): void { const kingTo = kingCastlesTo(color, side); @@ -154,10 +171,10 @@ export class Castles { } /** - * Creates a Castles instance from the given setup. - * @param {Setup} setup The chess setup. - * @returns {Castles} The Castles instance derived from the setup. - */ + * Creates a Castles instance from the given setup. + * @param {Setup} setup The chess position setup. + * @returns {Castles} The Castles instance created from the setup. + */ static fromSetup(setup: Setup): Castles { const castles = Castles.empty(); const rooks = setup.castlingRights.intersect(setup.board.rook); @@ -175,8 +192,8 @@ export class Castles { } /** - * Discards castling rights for the rook on the given square. - * @param {Square} square The square of the rook. + * Discards the castling rights for the given rook square. + * @param {Square} square The square of the rook to discard. */ discardRook(square: Square): void { if (this.castlingRights.has(square)) { @@ -190,8 +207,8 @@ export class Castles { } /** - * Discards castling rights for the given color. - * @param {Color} color The color to discard castling rights for. + * Discards the castling rights for the given color. + * @param {Color} color The color to discard the castling rights for. */ discardColor(color: Color): void { this.castlingRights = this.castlingRights.diff(SquareSet.backrank(color)); @@ -201,20 +218,38 @@ export class Castles { } /** - * TODO: Not sure what this is * Represents the context of a chess position. - * @interface - * @property {Square | undefined} king The square of the king. - * @property {SquareSet} blockers The set of blocking squares. - * @property {SquareSet} checkers The set of checking squares. - * @property {boolean} variantEnd Whether the variant has ended. - * @property {boolean} mustCapture Whether a capture is required. + * @interface Context */ export interface Context { + /** + * The position of the king. + * @type {Square | undefined} + */ king: Square | undefined; + + /** + * The set of blocking squares. + * @type {SquareSet} + */ blockers: SquareSet; + + /** + * The set of checking squares. + * @type {SquareSet} + */ checkers: SquareSet; + + /** + * Indicates if the variant has ended. + * @type {boolean} + */ variantEnd: boolean; + + /** + * Indicates if a capture is required. + * @type {boolean} + */ mustCapture: boolean; } @@ -477,18 +512,18 @@ export abstract class Position { } /** - * TODO: Not sure what this is - * @returns {boolean} Whether the position is a variant end. + * Checks if the variant has ended. + * @returns {boolean} `false` by default. Subclasses can override this method. */ isVariantEnd(): boolean { return false; } /** - * TODO: Not sure what this is - * @param _ctx - * @returns - */ + * Determines the outcome of the variant. + * @param {Context} [_ctx] The optional context for the position. + * @returns {Outcome | undefined} `undefined` by default. Subclasses can override this method. + */ variantOutcome(_ctx?: Context): Outcome | undefined { return; } @@ -720,10 +755,9 @@ export class Chess extends Position { } /** - * Create a new, unchecked chess game from a setup. - * TODO: There is validation, but I'm not sure what it is. - * @param {Setup} setup The chess setup. - * @returns Chess or an error. + * Creates a new chess position from the given setup. + * @param {Setup} setup The chess position setup. + * @returns {Result} The result containing the chess position or an error. */ static fromSetup(setup: Setup): Result { const pos = new this(); @@ -774,11 +808,11 @@ const legalEpSquare = (pos: Position): Square | undefined => { }; /** - * TODO: Not sure what this does - * @param {Position} pos - * @param {Square} pawnFrom - * @param {Context} ctx - * @returns {boolean} `true` if can capture, `false` otherwise + * Checks if an en passant capture is possible from the given pawn square. + * @param {Position} pos The chess position. + * @param {Square} pawnFrom The square of the capturing pawn. + * @param {Context} ctx The context for the position. + * @returns {boolean} `true` if an en passant capture is possible, `false` otherwise. */ const canCaptureEp = (pos: Position, pawnFrom: Square, ctx: Context): boolean => { if (!defined(pos.epSquare)) return false; @@ -834,6 +868,19 @@ const castlingDest = (pos: Position, side: CastlingSide, ctx: Context): SquareSe return SquareSet.fromSquare(rook); }; +/** + * Calculates the pseudo-legal destination squares for a given piece on a square. + * + * Pseudo-legal destinations refer to the set of squares that a piece can potentially move to, without + * considering the legality of the move in the context of the current position. They include moves that + * may be illegal, such as leaving the king in check. Pseudo-legal moves need to be further filtered to + * determine the actual legal moves in the given position. + * + * @param {Position} pos The chess position. + * @param {Square} square The square of the piece. + * @param {Context} ctx The context for the position. + * @returns {SquareSet} The set of pseudo-legal destination squares. + */ export const pseudoDests = (pos: Position, square: Square, ctx: Context): SquareSet => { if (ctx.variantEnd) return SquareSet.empty(); const piece = pos.board.get(square); @@ -862,6 +909,12 @@ export const pseudoDests = (pos: Position, square: Square, ctx: Context): Square else return pseudo; }; +/** + * Checks if two positions are equal, ignoring the move history. + * @param {Position} left The first position. + * @param {Position} right The second position. + * @returns {boolean} `true` if the positions are equal (ignoring move history), `false` otherwise. + */ export const equalsIgnoreMoves = (left: Position, right: Position): boolean => left.rules === right.rules && boardEquals(left.board, right.board) @@ -873,12 +926,10 @@ export const equalsIgnoreMoves = (left: Position, right: Position): boolean => || (!left.remainingChecks && !right.remainingChecks)); /** - * TODO: unsure - * - * I believe this takes in a move and a position, and from that determines what side is being castled to? - * @param pos - * @param move - * @returns {CastlingSide | undefined} + * Determines the castling side for a given move in a chess position. + * @param {Position} pos The chess position. + * @param {Move} move The move to determine the castling side for. + * @returns {CastlingSide | undefined} The castling side ('a' for queenside, 'h' for kingside) or `undefined` if the move is not a castling move. */ export const castlingSide = (pos: Position, move: Move): CastlingSide | undefined => { if (isDrop(move)) return; @@ -889,10 +940,10 @@ export const castlingSide = (pos: Position, move: Move): CastlingSide | undefine }; /** - * TODO: unsure - * @param pos - * @param move - * @returns + * Normalizes a move by converting castling moves to their corresponding rook moves. + * @param {Position} pos The chess position. + * @param {Move} move The move to normalize. + * @returns {Move} The normalized move. */ export const normalizeMove = (pos: Position, move: Move): Move => { const side = castlingSide(pos, move); @@ -905,12 +956,10 @@ export const normalizeMove = (pos: Position, move: Move): Move => { }; /** - * TODO: unsure - * - * I think this determines whether a given side has more pieces than normal? - * @param board - * @param color - * @returns + * Checks if the material on a given side is in a standard configuration. + * @param {Board} board The chessboard. + * @param {Color} color The color of the side to check. + * @returns {boolean} `true` if the material on the side is in a standard configuration, `false` otherwise. */ export const isStandardMaterialSide = (board: Board, color: Color): boolean => { const promoted = Math.max(board.pieces(color, 'queen').size() - 1, 0) @@ -922,20 +971,17 @@ export const isStandardMaterialSide = (board: Board, color: Color): boolean => { }; /** - * TODO: unsure - * - * I think this returns whether the total amount of material on the board is within the bounds - * of what is expected in standard chess. - * @param pos - * @returns + * Checks if the material on both sides is in a standard configuration. + * @param {Chess} pos The chess position. + * @returns {boolean} `true` if the material on both sides is in a standard configuration, `false` otherwise. */ export const isStandardMaterial = (pos: Chess): boolean => COLORS.every(color => isStandardMaterialSide(pos.board, color)); /** - * TODO: no clue here - * @param pos - * @returns + * Checks if the current position has an impossible check configuration. + * @param {Position} pos The chess position. + * @returns {boolean} `true` if the position has an impossible check configuration, `false` otherwise. */ export const isImpossibleCheck = (pos: Position): boolean => { const ourKing = pos.board.kingOf(pos.turn); From f797aee11bfff73d636202b2e8fdfcb0c898e996 Mon Sep 17 00:00:00 2001 From: Steven Stavrakis Date: Mon, 3 Jun 2024 17:20:15 -0400 Subject: [PATCH 6/6] run formatting --- src/attacks.ts | 10 ++-- src/board.ts | 4 +- src/chess.ts | 122 +++++++++++++++++++++++------------------------ src/compat.ts | 8 ++-- src/fen.ts | 8 ++-- src/setup.ts | 18 +++---- src/squareSet.ts | 118 ++++++++++++++++++++++----------------------- src/transform.ts | 64 ++++++++++++------------- src/types.ts | 4 +- src/util.ts | 2 +- 10 files changed, 177 insertions(+), 181 deletions(-) diff --git a/src/attacks.ts b/src/attacks.ts index 281880b5..585c649f 100644 --- a/src/attacks.ts +++ b/src/attacks.ts @@ -59,9 +59,12 @@ const KNIGHT_ATTACKS: BySquare = tabulate(sq => computeRange(sq, [-17 /** * A pre-computed table of pawn attacks for each square on the chessboard, separated by color. - * @type {{ white: BySquare, black: BySquare }} + * @type {{ white: BySquare; black: BySquare }} */ -const PAWN_ATTACKS: { white: BySquare, black: BySquare } = { +const PAWN_ATTACKS: { + white: BySquare; + black: BySquare; +} = { white: tabulate(sq => computeRange(sq, [7, 9])), black: tabulate(sq => computeRange(sq, [-7, -9])), }; @@ -88,7 +91,6 @@ export const knightAttacks = (square: Square): SquareSet => KNIGHT_ATTACKS[squar */ export const pawnAttacks = (color: Color, square: Square): SquareSet => PAWN_ATTACKS[color][square]; - /** * A pre-computed table of file ranges for each square on the chessboard. */ @@ -249,4 +251,4 @@ export const ray = (a: Square, b: Square): SquareSet => { export const between = (a: Square, b: Square): SquareSet => ray(a, b) .intersect(SquareSet.full().shl64(a).xor(SquareSet.full().shl64(b))) - .withoutFirst(); \ No newline at end of file + .withoutFirst(); diff --git a/src/board.ts b/src/board.ts index 122667c8..79380619 100644 --- a/src/board.ts +++ b/src/board.ts @@ -41,7 +41,7 @@ export class Board implements Iterable<[Square, Piece]>, ByRole, ByCo /** @type {SquareSet} */ king: SquareSet; - private constructor() { } + private constructor() {} /** * Creates a new board with the default starting position for standard chess. @@ -233,4 +233,4 @@ export class Board implements Iterable<[Square, Piece]>, ByRole, ByCo export const boardEquals = (left: Board, right: Board): boolean => left.white.equals(right.white) && left.promoted.equals(right.promoted) - && ROLES.every(role => left[role].equals(right[role])); \ No newline at end of file + && ROLES.every(role => left[role].equals(right[role])); diff --git a/src/chess.ts b/src/chess.ts index 8779ea02..949db50f 100644 --- a/src/chess.ts +++ b/src/chess.ts @@ -47,7 +47,7 @@ export enum IllegalSetup { * Custom error class for position errors. * @extends Error */ -export class PositionError extends Error { } +export class PositionError extends Error {} /** * Calculates the attacking squares for a given square and attacker color. @@ -67,10 +67,9 @@ const attacksTo = (square: Square, attacker: Color, board: Board, occupied: Squa .union(pawnAttacks(opposite(attacker), square).intersect(board.pawn)), ); - /** -* Represents the castling rights and related information for a chess position. -*/ + * Represents the castling rights and related information for a chess position. + */ export class Castles { /** * The castling rights as a set of squares. @@ -94,7 +93,7 @@ export class Castles { * Creates a new instance of the Castles class. * @private */ - private constructor() { } + private constructor() {} /** * Returns the default castling rights and setup. @@ -306,7 +305,7 @@ export abstract class Position { */ fullmoves: number; - protected constructor(readonly rules: Rules) { } + protected constructor(readonly rules: Rules) {} /** * Resets the position to the starting position. @@ -445,20 +444,20 @@ export abstract class Position { } /** - * Calculates the possible destination squares for a drop move. - * @param {Context} [_ctx] The optional context for the move generation. - * @returns {SquareSet} The set of possible destination squares for a drop move. - */ + * Calculates the possible destination squares for a drop move. + * @param {Context} [_ctx] The optional context for the move generation. + * @returns {SquareSet} The set of possible destination squares for a drop move. + */ dropDests(_ctx?: Context): SquareSet { return SquareSet.empty(); } /** - * Calculates the possible destination squares for a piece on a given square. - * @param {Square} square The square of the piece. - * @param {Context} [ctx] The optional context for the move generation. - * @returns {SquareSet} The set of possible destination squares. - */ + * Calculates the possible destination squares for a piece on a given square. + * @param {Square} square The square of the piece. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {SquareSet} The set of possible destination squares. + */ dests(square: Square, ctx?: Context): SquareSet { ctx = ctx || this.ctx(); if (ctx.variantEnd) return SquareSet.empty(); @@ -520,10 +519,10 @@ export abstract class Position { } /** - * Determines the outcome of the variant. - * @param {Context} [_ctx] The optional context for the position. - * @returns {Outcome | undefined} `undefined` by default. Subclasses can override this method. - */ + * Determines the outcome of the variant. + * @param {Context} [_ctx] The optional context for the position. + * @returns {Outcome | undefined} `undefined` by default. Subclasses can override this method. + */ variantOutcome(_ctx?: Context): Outcome | undefined { return; } @@ -577,10 +576,10 @@ export abstract class Position { } /** - * Checks if there are any possible destination squares for the current player's moves. - * @param {Context} [ctx] The optional context for the move generation. - * @returns {boolean} `true` if there are possible destination squares, `false` otherwise. - */ + * Checks if there are any possible destination squares for the current player's moves. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {boolean} `true` if there are possible destination squares, `false` otherwise. + */ hasDests(ctx?: Context): boolean { ctx = ctx || this.ctx(); for (const square of this.board[this.turn]) { @@ -589,11 +588,11 @@ export abstract class Position { return this.dropDests(ctx).nonEmpty(); } /** - * Checks if a given move is legal in the current position. - * @param {Move} move The move to check for legality. - * @param {Context} [ctx] The optional context for the move generation. - * @returns {boolean} `true` if the move is legal, `false` otherwise. - */ + * Checks if a given move is legal in the current position. + * @param {Move} move The move to check for legality. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {boolean} `true` if the move is legal, `false` otherwise. + */ isLegal(move: Move, ctx?: Context): boolean { if (isDrop(move)) { if (!this.pockets || this.pockets[this.turn][move.role] <= 0) return false; @@ -609,49 +608,49 @@ export abstract class Position { } /** - * Checks if the current position is a check. - * @returns {boolean} `true` if the current position is a check, `false` otherwise. - */ + * Checks if the current position is a check. + * @returns {boolean} `true` if the current position is a check, `false` otherwise. + */ isCheck(): boolean { const king = this.board.kingOf(this.turn); return defined(king) && this.kingAttackers(king, opposite(this.turn), this.board.occupied).nonEmpty(); } /** - * Checks if the current position is an end position. - * @param {Context} [ctx] The optional context for the move generation. - * @returns {boolean} `true` if the current position is an end position, `false` otherwise. - */ + * Checks if the current position is an end position. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {boolean} `true` if the current position is an end position, `false` otherwise. + */ isEnd(ctx?: Context): boolean { if (ctx ? ctx.variantEnd : this.isVariantEnd()) return true; return this.isInsufficientMaterial() || !this.hasDests(ctx); } /** - * Checks if the current position is a checkmate. - * @param {Context} [ctx] The optional context for the move generation. - * @returns {boolean} `true` if the current position is a checkmate, `false` otherwise. - */ + * Checks if the current position is a checkmate. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {boolean} `true` if the current position is a checkmate, `false` otherwise. + */ isCheckmate(ctx?: Context): boolean { ctx = ctx || this.ctx(); return !ctx.variantEnd && ctx.checkers.nonEmpty() && !this.hasDests(ctx); } /** - * Checks if the current position is a stalemate. - * @param {Context} [ctx] The optional context for the move generation. - * @returns {boolean} `true` if the current position is a stalemate, `false` otherwise. - */ + * Checks if the current position is a stalemate. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {boolean} `true` if the current position is a stalemate, `false` otherwise. + */ isStalemate(ctx?: Context): boolean { ctx = ctx || this.ctx(); return !ctx.variantEnd && ctx.checkers.isEmpty() && !this.hasDests(ctx); } /** - * Determines the outcome of the current position. - * @param {Context} [ctx] The optional context for the move generation. - * @returns {Outcome | undefined} The outcome of the current position, or undefined if the position is not an end position. - */ + * Determines the outcome of the current position. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {Outcome | undefined} The outcome of the current position, or undefined if the position is not an end position. + */ outcome(ctx?: Context): Outcome | undefined { const variantOutcome = this.variantOutcome(ctx); if (variantOutcome) return variantOutcome; @@ -662,10 +661,10 @@ export abstract class Position { } /** - * Calculates all possible destination squares for each piece of the current player. - * @param {Context} [ctx] The optional context for the move generation. - * @returns {Map} A map of source squares to their corresponding sets of possible destination squares. - */ + * Calculates all possible destination squares for each piece of the current player. + * @param {Context} [ctx] The optional context for the move generation. + * @returns {Map} A map of source squares to their corresponding sets of possible destination squares. + */ allDests(ctx?: Context): Map { ctx = ctx || this.ctx(); const d = new Map(); @@ -793,7 +792,7 @@ const validEpSquare = (pos: Position, square: Square | undefined): Square | unde /** * Finds and returns all legal en passant squares in the position. - * @param {Position} pos + * @param {Position} pos * @returns {Square | undefined} */ const legalEpSquare = (pos: Position): Square | undefined => { @@ -830,12 +829,11 @@ const canCaptureEp = (pos: Position, pawnFrom: Square, ctx: Context): boolean => .isEmpty(); }; - /** * Checks various castling conditions and returns a set of squares that can be castled to. - * @param {Position} pos - * @param {CastlingSide} side - * @param {Context} ctx + * @param {Position} pos + * @param {CastlingSide} side + * @param {Context} ctx * @returns {SquareSet} A set of squares that can be castled to. Can be empty. */ const castlingDest = (pos: Position, side: CastlingSide, ctx: Context): SquareSet => { @@ -855,12 +853,10 @@ const castlingDest = (pos: Position, side: CastlingSide, ctx: Context): SquareSe // Remove the king position const occ = pos.board.occupied.without(ctx.king); - for (const sq of kingPath) { if (pos.kingAttackers(sq, opposite(pos.turn), occ).nonEmpty()) return SquareSet.empty(); } - const rookTo = rookCastlesTo(pos.turn, side); const after = pos.board.occupied.toggle(ctx.king).toggle(rook).toggle(rookTo); if (pos.kingAttackers(kingTo, opposite(pos.turn), after).nonEmpty()) return SquareSet.empty(); @@ -870,12 +866,12 @@ const castlingDest = (pos: Position, side: CastlingSide, ctx: Context): SquareSe /** * Calculates the pseudo-legal destination squares for a given piece on a square. - * - * Pseudo-legal destinations refer to the set of squares that a piece can potentially move to, without - * considering the legality of the move in the context of the current position. They include moves that - * may be illegal, such as leaving the king in check. Pseudo-legal moves need to be further filtered to + * + * Pseudo-legal destinations refer to the set of squares that a piece can potentially move to, without + * considering the legality of the move in the context of the current position. They include moves that + * may be illegal, such as leaving the king in check. Pseudo-legal moves need to be further filtered to * determine the actual legal moves in the given position. - * + * * @param {Position} pos The chess position. * @param {Square} square The square of the piece. * @param {Context} ctx The context for the position. diff --git a/src/compat.ts b/src/compat.ts index a00ce5d5..35cfe89a 100644 --- a/src/compat.ts +++ b/src/compat.ts @@ -22,7 +22,7 @@ export interface ChessgroundDestsOpts { * Includes both possible representations of castling moves (unless * `chess960` mode is enabled), so that the `rookCastles` option will work * correctly. - * @param {Position} pos + * @param {Position} pos * @param {ChessgroundDestsOpts} [opts] */ export const chessgroundDests = (pos: Position, opts?: ChessgroundDestsOpts): Map => { @@ -68,7 +68,7 @@ export const scalachessCharPair = (move: Move): string => /** * Converts chessops chess variant names to lichess chess rule names - * @param variant + * @param variant * @returns {Rules} */ export const lichessRules = ( @@ -102,8 +102,8 @@ export const lichessRules = ( /** * Conversts chessops rule name to lichess variant name. - * @param rules - * @returns + * @param rules + * @returns */ export const lichessVariant = ( rules: Rules, diff --git a/src/fen.ts b/src/fen.ts index 79e956db..8aacc70c 100644 --- a/src/fen.ts +++ b/src/fen.ts @@ -27,7 +27,7 @@ export enum InvalidFen { Fullmoves = 'ERR_FULLMOVES', } -export class FenError extends Error { } +export class FenError extends Error {} const nthIndexOf = (haystack: string, needle: string, n: number): number => { let index = haystack.indexOf(needle); @@ -48,7 +48,7 @@ const charToPiece = (ch: string): Piece | undefined => { /** * TODO: what is a "boardPart"? * Takes a FEN and produces a Board object representing it - * @param boardPart + * @param boardPart * @returns {Result} */ export const parseBoardFen = (boardPart: string): Result => { @@ -149,7 +149,7 @@ export const parseCastlingFen = (board: Board, castlingPart: string): Result { return fen; }; - /** * Converts a MaterialSide object to its string representation. * @@ -433,7 +432,6 @@ export const makeCastlingFen = (board: Board, castlingRights: SquareSet): string */ export const makeRemainingChecks = (checks: RemainingChecks): string => `${checks.white}+${checks.black}`; - /** * Converts a Setup object to its FEN string representation. * diff --git a/src/setup.ts b/src/setup.ts index fd71de5f..cb7c75f2 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -13,9 +13,9 @@ import { ByColor, ByRole, Color, Role, ROLES, Square } from './types.js'; * @property {number} king The number of kings on the side. */ export class MaterialSide implements ByRole { - /** - * The number of pawns. - */ + /** + * The number of pawns. + */ pawn: number; /** @@ -43,7 +43,7 @@ export class MaterialSide implements ByRole { */ king: number; - private constructor() { } + private constructor() {} /** * Creates an empty MaterialSide instance. @@ -164,7 +164,7 @@ export class Material implements ByColor { constructor( public white: MaterialSide, public black: MaterialSide, - ) { } + ) {} /** * Creates an empty Material instance. @@ -281,7 +281,7 @@ export class RemainingChecks implements ByColor { constructor( public white: number, public black: number, - ) { } + ) {} /** * Returns the default remaining checks count for each color. @@ -350,14 +350,14 @@ export interface Setup { /** * The number of halfmoves since the last pawn advance or capture. - * + * * A halfmove is when a side makes a move. */ halfmoves: number; /** * The number of fullmoves. - * + * * A fullmove is when both sides make a move. */ fullmoves: number; @@ -409,4 +409,4 @@ export const setupEquals = (left: Setup, right: Setup): boolean => && ((right.remainingChecks && left.remainingChecks?.equals(right.remainingChecks)) || (!left.remainingChecks && !right.remainingChecks)) && left.halfmoves === right.halfmoves - && left.fullmoves === right.fullmoves; \ No newline at end of file + && left.fullmoves === right.fullmoves; diff --git a/src/squareSet.ts b/src/squareSet.ts index d308abcb..f1e444f8 100644 --- a/src/squareSet.ts +++ b/src/squareSet.ts @@ -32,8 +32,8 @@ export class SquareSet implements Iterable { /** * Returns a square set containing the given square. - * @param square - * @returns + * @param square + * @returns */ static fromSquare(square: Square): SquareSet { return square >= 32 ? new SquareSet(0, 1 << (square - 32)) : new SquareSet(1 << square, 0); @@ -124,26 +124,26 @@ export class SquareSet implements Iterable { } /** - * Returns the complement of the current SquareSet. - * - * The complement of a SquareSet is a new SquareSet that contains all the squares - * that are not present in the original set. - * - * @returns {SquareSet} A new SquareSet representing the complement of the current set. - */ + * Returns the complement of the current SquareSet. + * + * The complement of a SquareSet is a new SquareSet that contains all the squares + * that are not present in the original set. + * + * @returns {SquareSet} A new SquareSet representing the complement of the current set. + */ complement(): SquareSet { return new SquareSet(~this.lo, ~this.hi); } /** - * Performs a bitwise XOR operation between the current SquareSet and another SquareSet. - * - * The XOR operation returns a new SquareSet that contains the squares that are present - * in either the current set or the other set, but not both. - * - * @param {SquareSet} other The SquareSet to perform the XOR operation with. - * @returns {SquareSet} A new SquareSet representing the result of the XOR operation. - */ + * Performs a bitwise XOR operation between the current SquareSet and another SquareSet. + * + * The XOR operation returns a new SquareSet that contains the squares that are present + * in either the current set or the other set, but not both. + * + * @param {SquareSet} other The SquareSet to perform the XOR operation with. + * @returns {SquareSet} A new SquareSet representing the result of the XOR operation. + */ xor(other: SquareSet): SquareSet { return new SquareSet(this.lo ^ other.lo, this.hi ^ other.hi); } @@ -213,7 +213,7 @@ export class SquareSet implements Iterable { /** * Checks if the current SquareSet is a superset of another SquareSet. - * + * * A SquareSet is a superset of another SquareSet if every square in the other set is also present in the current set. * * @param {SquareSet} other The SquareSet to check for supersetness. @@ -268,29 +268,29 @@ export class SquareSet implements Iterable { } /** - * Swaps the bytes of the SquareSet in a 64-bit manner. - * - * @returns {SquareSet} A new SquareSet with the bytes swapped. - */ + * Swaps the bytes of the SquareSet in a 64-bit manner. + * + * @returns {SquareSet} A new SquareSet with the bytes swapped. + */ bswap64(): SquareSet { return new SquareSet(bswap32(this.hi), bswap32(this.lo)); } /** - * Reverses the bits of the SquareSet in a 64-bit manner. - * - * @returns {SquareSet} A new SquareSet with the bits reversed. - */ + * Reverses the bits of the SquareSet in a 64-bit manner. + * + * @returns {SquareSet} A new SquareSet with the bits reversed. + */ rbit64(): SquareSet { return new SquareSet(rbit32(this.hi), rbit32(this.lo)); } /** - * Subtracts another SquareSet from the current SquareSet in a 64-bit manner. - * - * @param {SquareSet} other The SquareSet to subtract. - * @returns {SquareSet} A new SquareSet representing the result of the subtraction. - */ + * Subtracts another SquareSet from the current SquareSet in a 64-bit manner. + * + * @param {SquareSet} other The SquareSet to subtract. + * @returns {SquareSet} A new SquareSet representing the result of the subtraction. + */ minus64(other: SquareSet): SquareSet { const lo = this.lo - other.lo; const c = ((lo & other.lo & 1) + (other.lo >>> 1) + (lo >>> 1)) >>> 31; @@ -298,59 +298,59 @@ export class SquareSet implements Iterable { } /** - * Checks if the current SquareSet is equal to another SquareSet. - * - * @param {SquareSet} other The SquareSet to compare with. - * @returns {boolean} `true` if the SquareSets are equal, `false` otherwise. - */ + * Checks if the current SquareSet is equal to another SquareSet. + * + * @param {SquareSet} other The SquareSet to compare with. + * @returns {boolean} `true` if the SquareSets are equal, `false` otherwise. + */ equals(other: SquareSet): boolean { return this.lo === other.lo && this.hi === other.hi; } /** - * Returns the number of squares in the SquareSet. - * - * @returns {number} The count of squares in the SquareSet. - */ + * Returns the number of squares in the SquareSet. + * + * @returns {number} The count of squares in the SquareSet. + */ size(): number { return popcnt32(this.lo) + popcnt32(this.hi); } /** - * Checks if the SquareSet is empty. - * - * @returns {boolean} `true` if the SquareSet is empty, `false` otherwise. - */ + * Checks if the SquareSet is empty. + * + * @returns {boolean} `true` if the SquareSet is empty, `false` otherwise. + */ isEmpty(): boolean { return this.lo === 0 && this.hi === 0; } /** - * Checks if the SquareSet is not empty. - * - * @returns {boolean} `true` if the SquareSet is not empty, `false` otherwise. - */ + * Checks if the SquareSet is not empty. + * + * @returns {boolean} `true` if the SquareSet is not empty, `false` otherwise. + */ nonEmpty(): boolean { return this.lo !== 0 || this.hi !== 0; } /** - * Checks if the SquareSet contains a specific square. - * - * @param {Square} square The square to check for presence. - * @returns {boolean} `true` if the SquareSet contains the square, `false` otherwise. - */ + * Checks if the SquareSet contains a specific square. + * + * @param {Square} square The square to check for presence. + * @returns {boolean} `true` if the SquareSet contains the square, `false` otherwise. + */ has(square: Square): boolean { return (square >= 32 ? this.hi & (1 << (square - 32)) : this.lo & (1 << square)) !== 0; } /** - * Sets or unsets a square in the SquareSet. - * - * @param {Square} square The square to set or unset. - * @param {boolean} on `true` to set the square, `false` to unset it. - * @returns {SquareSet} A new SquareSet with the square set or unset. - */ + * Sets or unsets a square in the SquareSet. + * + * @param {Square} square The square to set or unset. + * @param {boolean} on `true` to set the square, `false` to unset it. + * @returns {SquareSet} A new SquareSet with the square set or unset. + */ set(square: Square, on: boolean): SquareSet { return on ? this.with(square) : this.without(square); } diff --git a/src/transform.ts b/src/transform.ts index 0a2ddae9..45a7e82f 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -5,19 +5,19 @@ import { COLORS, ROLES } from './types.js'; import { defined } from './util.js'; /** -* Flips a SquareSet vertically. -* -* @param {SquareSet} s The SquareSet to flip. -* @returns {SquareSet} The flipped SquareSet. -*/ + * Flips a SquareSet vertically. + * + * @param {SquareSet} s The SquareSet to flip. + * @returns {SquareSet} The flipped SquareSet. + */ export const flipVertical = (s: SquareSet): SquareSet => s.bswap64(); /** -* Flips a SquareSet horizontally. -* -* @param {SquareSet} s The SquareSet to flip. -* @returns {SquareSet} The flipped SquareSet. -*/ + * Flips a SquareSet horizontally. + * + * @param {SquareSet} s The SquareSet to flip. + * @returns {SquareSet} The flipped SquareSet. + */ export const flipHorizontal = (s: SquareSet): SquareSet => { const k1 = new SquareSet(0x55555555, 0x55555555); const k2 = new SquareSet(0x33333333, 0x33333333); @@ -29,11 +29,11 @@ export const flipHorizontal = (s: SquareSet): SquareSet => { }; /** -* Flips a SquareSet diagonally. -* -* @param {SquareSet} s The SquareSet to flip. -* @returns {SquareSet} The flipped SquareSet. -*/ + * Flips a SquareSet diagonally. + * + * @param {SquareSet} s The SquareSet to flip. + * @returns {SquareSet} The flipped SquareSet. + */ export const flipDiagonal = (s: SquareSet): SquareSet => { let t = s.xor(s.shl64(28)).intersect(new SquareSet(0, 0x0f0f0f0f)); s = s.xor(t.xor(t.shr64(28))); @@ -45,20 +45,20 @@ export const flipDiagonal = (s: SquareSet): SquareSet => { }; /** -* Rotates a SquareSet by 180 degrees. -* -* @param {SquareSet} s The SquareSet to rotate. -* @returns {SquareSet} The rotated SquareSet. -*/ + * Rotates a SquareSet by 180 degrees. + * + * @param {SquareSet} s The SquareSet to rotate. + * @returns {SquareSet} The rotated SquareSet. + */ export const rotate180 = (s: SquareSet): SquareSet => s.rbit64(); /** -* Transforms a Board by applying a transformation function to each SquareSet. -* -* @param {Board} board The Board to transform. -* @param {function(SquareSet): SquareSet} f The transformation function. -* @returns {Board} The transformed Board. -*/ + * Transforms a Board by applying a transformation function to each SquareSet. + * + * @param {Board} board The Board to transform. + * @param {function(SquareSet): SquareSet} f The transformation function. + * @returns {Board} The transformed Board. + */ export const transformBoard = (board: Board, f: (s: SquareSet) => SquareSet): Board => { const b = Board.empty(); b.occupied = f(board.occupied); @@ -69,12 +69,12 @@ export const transformBoard = (board: Board, f: (s: SquareSet) => SquareSet): Bo }; /** -* Transforms a Setup by applying a transformation function to each SquareSet. -* -* @param {Setup} setup The Setup to transform. -* @param {function(SquareSet): SquareSet} f The transformation function. -* @returns {Setup} The transformed Setup. -*/ + * Transforms a Setup by applying a transformation function to each SquareSet. + * + * @param {Setup} setup The Setup to transform. + * @param {function(SquareSet): SquareSet} f The transformation function. + * @returns {Setup} The transformed Setup. + */ export const transformSetup = (setup: Setup, f: (s: SquareSet) => SquareSet): Setup => ({ board: transformBoard(setup.board, f), pockets: setup.pockets?.clone(), diff --git a/src/types.ts b/src/types.ts index 9ca7b265..b815e885 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,7 +22,7 @@ export type RankName = (typeof RANK_NAMES)[number]; /** * The type representing a square on the chess board. - * + * * A number between 0 and 63, inclusive. */ export type Square = number; @@ -180,4 +180,4 @@ export type Rules = (typeof RULES)[number]; */ export interface Outcome { winner: Color | undefined; -} \ No newline at end of file +} diff --git a/src/util.ts b/src/util.ts index 2944406f..dbe85704 100644 --- a/src/util.ts +++ b/src/util.ts @@ -192,4 +192,4 @@ export const kingCastlesTo = (color: Color, side: CastlingSide): Square => * @returns {Square} The square where the rook castles to. */ export const rookCastlesTo = (color: Color, side: CastlingSide): Square => - color === 'white' ? (side === 'a' ? 3 : 5) : side === 'a' ? 59 : 61; \ No newline at end of file + color === 'white' ? (side === 'a' ? 3 : 5) : side === 'a' ? 59 : 61;