kokopu
Version:
A JavaScript/TypeScript library implementing the chess game rules and providing tools to read/write the standard chess file formats.
259 lines (212 loc) • 9.58 kB
text/typescript
/*!
* -------------------------------------------------------------------------- *
* *
* Kokopu - A JavaScript/TypeScript chess library. *
* <https://www.npmjs.com/package/kokopu> *
* Copyright (C) 2018-2026 Yoann Le Montagner <yo35 -at- melix.net> *
* *
* Kokopu is free software: you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public License *
* as published by the Free Software Foundation, either version 3 of *
* the License, or (at your option) any later version. *
* *
* Kokopu is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General *
* Public License along with this program. If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* -------------------------------------------------------------------------- */
import { Color, Piece, ColoredPiece, File, Rank, Square, SquareCouple, Castle, Castle960, Coordinates, GameResult, GameVariant } from './base_types';
import { IllegalArgument } from './exception';
import { colorFromString, pieceFromString, coloredPieceFromString, fileFromString, rankFromString, squareFromString, resultFromString, variantFromString,
colorToString, fileToString, rankToString, squareToString, squareColorImpl } from './private_position/base_types_impl';
import { hasCanonicalStartPosition } from './private_position/impl';
import { isPositiveInteger } from './private_game/common';
/**
* Whether the given value represents a valid {@link Color} or not.
*/
export function isColor(value: unknown): value is Color {
return colorFromString(value) >= 0;
}
/**
* Whether the given value represents a valid {@link Piece} or not.
*/
export function isPiece(value: unknown): value is Piece {
return pieceFromString(value) >= 0;
}
/**
* Whether the given value represents a valid {@link ColoredPiece} or not.
*/
export function isColoredPiece(value: unknown): value is ColoredPiece {
return coloredPieceFromString(value) >= 0;
}
/**
* Whether the given value represents a valid {@link File} or not.
*/
export function isFile(value: unknown): value is File {
return fileFromString(value) >= 0;
}
/**
* Whether the given value represents a valid {@link Rank} or not.
*/
export function isRank(value: unknown): value is Rank {
return rankFromString(value) >= 0;
}
/**
* Whether the given value represents a valid {@link Square} or not.
*/
export function isSquare(value: unknown): value is Square {
return squareFromString(value) >= 0;
}
/**
* Whether the given value represents a valid {@link SquareCouple} or not.
*/
export function isSquareCouple(value: unknown): value is SquareCouple {
return typeof value === 'string' && /^[a-h][1-8][a-h][1-8]$/.test(value);
}
/**
* Whether the given value represents a valid {@link Castle} or not.
*/
export function isCastle(value: unknown): value is Castle {
return typeof value === 'string' && /^[wb][kq]$/.test(value);
}
/**
* Whether the given value represents a valid {@link Castle960} or not.
*/
export function isCastle960(value: unknown): value is Castle960 {
return typeof value === 'string' && /^[wb][a-h]$/.test(value);
}
/**
* Whether the given value represents a valid {@link GameResult} or not.
*/
export function isGameResult(value: unknown): value is GameResult {
return resultFromString(value) >= 0;
}
/**
* Whether the given value represents a valid {@link GameVariant} or not.
*/
export function isGameVariant(value: unknown): value is GameVariant {
return variantFromString(value) >= 0;
}
/**
* Execute the given callback on each of the 64 squares.
*/
export function forEachSquare(callback: (square: Square) => void): void {
for (let rank = 0; rank < 8; ++rank) {
for (let file = 0; file < 8; ++file) {
callback(squareToString(rank * 16 + file));
}
}
}
/**
* Return the color of a square.
*/
export function squareColor(square: Square): Color {
const squareCode = squareFromString(square);
if (squareCode < 0) {
throw new IllegalArgument('squareColor()');
}
return colorToString(squareColorImpl(squareCode));
}
/**
* Return the coordinates of a square.
*/
export function squareToCoordinates(square: Square): Coordinates {
const squareCode = squareFromString(square);
if (squareCode < 0) {
throw new IllegalArgument('squareToCoordinates()');
}
return { rank: Math.trunc(squareCode / 16), file: squareCode % 16 };
}
/**
* Return the square corresponding to the given coordinates.
*/
export function coordinatesToSquare(file: number, rank: number): Square;
/**
* Return the square corresponding to the given coordinates.
*/
export function coordinatesToSquare(coordinates: Coordinates): Square;
export function coordinatesToSquare(fileOrCoordinates: number | Coordinates, rankOrUndefined?: number): Square {
const file = typeof fileOrCoordinates === 'number' ? fileOrCoordinates : fileOrCoordinates.file;
const rank = typeof fileOrCoordinates === 'number' ? rankOrUndefined! : fileOrCoordinates.rank;
if (!Number.isInteger(file) || !Number.isInteger(rank) || file < 0 || file >= 8 || rank < 0 || rank >= 8) {
throw new IllegalArgument('coordinatesToSquare()');
}
return fileToString(file) + rankToString(rank) as Square;
}
/**
* Change white to black, and black to white.
*/
export function oppositeColor(color: Color): Color {
const colorCode = colorFromString(color);
if (colorCode < 0) {
throw new IllegalArgument('oppositeColor()');
}
return colorToString(1 - colorCode);
}
/**
* Whether the given variant has a canonical start position or not.
*/
export function variantWithCanonicalStartPosition(variant: GameVariant): boolean {
const variantCode = variantFromString(variant);
if (variantCode < 0) {
throw new IllegalArgument('variantWithCanonicalStartPosition()');
}
return hasCanonicalStartPosition(variantCode);
}
const NAG_SYMBOLS = new Map<number, string>();
/* eslint-disable @stylistic/no-multi-spaces, @stylistic/space-in-parens */
NAG_SYMBOLS.set( 1, '!' ); // good move
NAG_SYMBOLS.set( 2, '?' ); // bad move
NAG_SYMBOLS.set( 3, '!!' ); // very good move
NAG_SYMBOLS.set( 4, '??' ); // very bad move
NAG_SYMBOLS.set( 5, '!?' ); // interesting move
NAG_SYMBOLS.set( 6, '?!' ); // questionable move
NAG_SYMBOLS.set( 7, '\u25a1' ); // Only move
NAG_SYMBOLS.set( 8, '\u25a1' ); // Only move (ChessBase)
NAG_SYMBOLS.set( 9, '\u2612' ); // Worst move (Chess.com)
NAG_SYMBOLS.set( 10, '=' ); // equal position
NAG_SYMBOLS.set( 11, '=' ); // equal position (ChessBase)
NAG_SYMBOLS.set( 13, '\u221e' ); // unclear position
NAG_SYMBOLS.set( 14, '\u2a72' ); // White has a slight advantage
NAG_SYMBOLS.set( 15, '\u2a71' ); // Black has a slight advantage
NAG_SYMBOLS.set( 16, '\u00b1' ); // White has a moderate advantage
NAG_SYMBOLS.set( 17, '\u2213' ); // Black has a moderate advantage
NAG_SYMBOLS.set( 18, '+\u2212' ); // White has a decisive advantage
NAG_SYMBOLS.set( 19, '\u2212+' ); // Black has a decisive advantage
NAG_SYMBOLS.set( 20, '+\u2212\u2212'); // White has a crushing advantage
NAG_SYMBOLS.set( 21, '\u2212\u2212+'); // Black has a crushing advantage
NAG_SYMBOLS.set( 22, '\u2a00' ); // Zugzwang
NAG_SYMBOLS.set( 32, '\u27f3' ); // Development advantage
NAG_SYMBOLS.set( 36, '\u2191' ); // Initiative
NAG_SYMBOLS.set( 40, '\u2192' ); // Attack
NAG_SYMBOLS.set( 44, '\u2bf9' ); // Compensation
NAG_SYMBOLS.set(132, '\u21c6' ); // Counterplay
NAG_SYMBOLS.set(138, '\u2a01' ); // Zeitnot
NAG_SYMBOLS.set(140, '\u2206' ); // With idea...
NAG_SYMBOLS.set(141, '\u2207' ); // Aimed against...
NAG_SYMBOLS.set(142, '\u2313' ); // Better is...
NAG_SYMBOLS.set(143, '\u2264' ); // Worse is...
NAG_SYMBOLS.set(145, 'RR' ); // Editorial comment
NAG_SYMBOLS.set(146, 'N' ); // Novelty
/* eslint-enable */
/**
* Return the human-readable symbol for the given [NAG](https://en.wikipedia.org/wiki/Numeric_Annotation_Glyphs).
*/
export function nagSymbol(nag: number): string {
if (!isPositiveInteger(nag)) {
throw new IllegalArgument('nagSymbol()');
}
const result = NAG_SYMBOLS.get(nag);
return result ?? '$' + nag;
}
/**
* Whether the given string represents a valid [ECO code](https://en.wikipedia.org/wiki/List_of_chess_openings) or not.
*/
export function isValidECO(eco: string): boolean {
return typeof eco === 'string' && /^[A-E][0-9][0-9]$/.test(eco);
}