chessops
Version:
Chess and chess variant rules and operations
112 lines (97 loc) • 3.41 kB
text/typescript
import {
CastlingSide,
Color,
FILE_NAMES,
isDrop,
isNormal,
Move,
RANK_NAMES,
Role,
type RoleChar,
Square,
SquareName,
} from './types.js';
export const defined = <A>(v: A | undefined): v is A => v !== undefined;
export const opposite = (color: Color): Color => (color === 'white' ? 'black' : 'white');
export const squareRank = (square: Square): number => square >> 3;
export const squareFile = (square: Square): number => square & 0x7;
export const squareFromCoords = (file: number, rank: number): Square | undefined =>
0 <= file && file < 8 && 0 <= rank && rank < 8 ? file + 8 * rank : undefined;
export const roleToChar = (role: Role): RoleChar => {
switch (role) {
case 'pawn':
return 'p';
case 'knight':
return 'n';
case 'bishop':
return 'b';
case 'rook':
return 'r';
case 'queen':
return 'q';
case 'king':
return 'k';
}
};
export function charToRole(ch: RoleChar | Uppercase<RoleChar>): Role;
export function charToRole(ch: string): Role | undefined;
export function charToRole(ch: string): Role | undefined {
switch (ch.toLowerCase()) {
case 'p':
return 'pawn';
case 'n':
return 'knight';
case 'b':
return 'bishop';
case 'r':
return 'rook';
case 'q':
return 'queen';
case 'k':
return 'king';
default:
return;
}
}
export function parseSquare(str: SquareName): Square;
export function parseSquare(str: string): Square | undefined;
export function parseSquare(str: string): Square | undefined {
if (str.length !== 2) return;
return squareFromCoords(str.charCodeAt(0) - 'a'.charCodeAt(0), str.charCodeAt(1) - '1'.charCodeAt(0));
}
export const makeSquare = (square: Square): SquareName =>
(FILE_NAMES[squareFile(square)] + RANK_NAMES[squareRank(square)]) as SquareName;
export const parseUci = (str: string): Move | undefined => {
if (str[1] === '@' && str.length === 4) {
const role = charToRole(str[0]);
const to = parseSquare(str.slice(2));
if (role && defined(to)) return { role, to };
} else if (str.length === 4 || str.length === 5) {
const from = parseSquare(str.slice(0, 2));
const to = parseSquare(str.slice(2, 4));
let promotion: Role | undefined;
if (str.length === 5) {
promotion = charToRole(str[4]);
if (!promotion) return;
}
if (defined(from) && defined(to)) return { from, to, promotion };
}
return;
};
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;
else return isNormal(right) && left.from === right.from && left.promotion === right.promotion;
};
/**
* 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.
*/
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) : '');
export const kingCastlesTo = (color: Color, side: CastlingSide): Square =>
color === 'white' ? (side === 'a' ? 2 : 6) : side === 'a' ? 58 : 62;
export const rookCastlesTo = (color: Color, side: CastlingSide): Square =>
color === 'white' ? (side === 'a' ? 3 : 5) : side === 'a' ? 59 : 61;