@connectorvol/chessops 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE.txt +674 -0
  2. package/README.md +61 -0
  3. package/dist/cjs/attacks.js +152 -0
  4. package/dist/cjs/attacks.js.map +1 -0
  5. package/dist/cjs/board.js +143 -0
  6. package/dist/cjs/board.js.map +1 -0
  7. package/dist/cjs/chess.js +638 -0
  8. package/dist/cjs/chess.js.map +1 -0
  9. package/dist/cjs/compat.js +89 -0
  10. package/dist/cjs/compat.js.map +1 -0
  11. package/dist/cjs/debug.js +103 -0
  12. package/dist/cjs/debug.js.map +1 -0
  13. package/dist/cjs/fen.js +325 -0
  14. package/dist/cjs/fen.js.map +1 -0
  15. package/dist/cjs/index.js +94 -0
  16. package/dist/cjs/index.js.map +1 -0
  17. package/dist/cjs/pgn.js +796 -0
  18. package/dist/cjs/pgn.js.map +1 -0
  19. package/dist/cjs/san.js +174 -0
  20. package/dist/cjs/san.js.map +1 -0
  21. package/dist/cjs/setup.js +167 -0
  22. package/dist/cjs/setup.js.map +1 -0
  23. package/dist/cjs/squareSet.js +206 -0
  24. package/dist/cjs/squareSet.js.map +1 -0
  25. package/dist/cjs/transform.js +57 -0
  26. package/dist/cjs/transform.js.map +1 -0
  27. package/dist/cjs/types.js +24 -0
  28. package/dist/cjs/types.js.map +1 -0
  29. package/dist/cjs/util.js +104 -0
  30. package/dist/cjs/util.js.map +1 -0
  31. package/dist/cjs/variant.js +833 -0
  32. package/dist/cjs/variant.js.map +1 -0
  33. package/dist/esm/attacks.js +140 -0
  34. package/dist/esm/attacks.js.map +1 -0
  35. package/dist/esm/board.js +138 -0
  36. package/dist/esm/board.js.map +1 -0
  37. package/dist/esm/chess.js +624 -0
  38. package/dist/esm/chess.js.map +1 -0
  39. package/dist/esm/compat.js +81 -0
  40. package/dist/esm/compat.js.map +1 -0
  41. package/dist/esm/debug.js +94 -0
  42. package/dist/esm/debug.js.map +1 -0
  43. package/dist/esm/fen.js +308 -0
  44. package/dist/esm/fen.js.map +1 -0
  45. package/dist/esm/index.js +15 -0
  46. package/dist/esm/index.js.map +1 -0
  47. package/dist/esm/pgn.js +769 -0
  48. package/dist/esm/pgn.js.map +1 -0
  49. package/dist/esm/san.js +167 -0
  50. package/dist/esm/san.js.map +1 -0
  51. package/dist/esm/setup.js +157 -0
  52. package/dist/esm/setup.js.map +1 -0
  53. package/dist/esm/squareSet.js +202 -0
  54. package/dist/esm/squareSet.js.map +1 -0
  55. package/dist/esm/transform.js +48 -0
  56. package/dist/esm/transform.js.map +1 -0
  57. package/dist/esm/types.js +19 -0
  58. package/dist/esm/types.js.map +1 -0
  59. package/dist/esm/util.js +87 -0
  60. package/dist/esm/util.js.map +1 -0
  61. package/dist/esm/variant.js +812 -0
  62. package/dist/esm/variant.js.map +1 -0
  63. package/dist/types/attacks.d.ts +58 -0
  64. package/dist/types/board.d.ts +62 -0
  65. package/dist/types/chess.d.ts +82 -0
  66. package/dist/types/compat.d.ts +26 -0
  67. package/dist/types/debug.d.ts +10 -0
  68. package/dist/types/fen.d.ts +40 -0
  69. package/dist/types/index.d.ts +14 -0
  70. package/dist/types/pgn.d.ts +203 -0
  71. package/dist/types/san.d.ts +6 -0
  72. package/dist/types/setup.d.ts +65 -0
  73. package/dist/types/squareSet.d.ts +50 -0
  74. package/dist/types/transform.d.ts +9 -0
  75. package/dist/types/types.d.ts +58 -0
  76. package/dist/types/util.d.ts +21 -0
  77. package/dist/types/variant.d.ts +92 -0
  78. package/package.json +86 -0
  79. package/src/attacks.ts +160 -0
  80. package/src/board.ts +168 -0
  81. package/src/chess.ts +687 -0
  82. package/src/compat.ts +120 -0
  83. package/src/debug.ts +100 -0
  84. package/src/fen.ts +328 -0
  85. package/src/index.ts +85 -0
  86. package/src/pgn.ts +876 -0
  87. package/src/san.ts +190 -0
  88. package/src/setup.ts +203 -0
  89. package/src/squareSet.ts +243 -0
  90. package/src/transform.ts +49 -0
  91. package/src/types.ts +93 -0
  92. package/src/util.ts +116 -0
  93. package/src/variant.ts +939 -0
@@ -0,0 +1,50 @@
1
+ import { Color, Square } from "./types.js";
2
+ /**
3
+ * An immutable set of squares, implemented as a bitboard.
4
+ */
5
+ export declare class SquareSet implements Iterable<Square> {
6
+ readonly lo: number;
7
+ readonly hi: number;
8
+ constructor(lo: number, hi: number);
9
+ static fromSquare(square: Square): SquareSet;
10
+ static fromRank(rank: number): SquareSet;
11
+ static fromFile(file: number): SquareSet;
12
+ static empty(): SquareSet;
13
+ static full(): SquareSet;
14
+ static corners(): SquareSet;
15
+ static center(): SquareSet;
16
+ static backranks(): SquareSet;
17
+ static backrank(color: Color): SquareSet;
18
+ static lightSquares(): SquareSet;
19
+ static darkSquares(): SquareSet;
20
+ complement(): SquareSet;
21
+ xor(other: SquareSet): SquareSet;
22
+ union(other: SquareSet): SquareSet;
23
+ intersect(other: SquareSet): SquareSet;
24
+ diff(other: SquareSet): SquareSet;
25
+ intersects(other: SquareSet): boolean;
26
+ isDisjoint(other: SquareSet): boolean;
27
+ supersetOf(other: SquareSet): boolean;
28
+ subsetOf(other: SquareSet): boolean;
29
+ shr64(shift: number): SquareSet;
30
+ shl64(shift: number): SquareSet;
31
+ bswap64(): SquareSet;
32
+ rbit64(): SquareSet;
33
+ minus64(other: SquareSet): SquareSet;
34
+ equals(other: SquareSet): boolean;
35
+ size(): number;
36
+ isEmpty(): boolean;
37
+ nonEmpty(): boolean;
38
+ has(square: Square): boolean;
39
+ set(square: Square, on: boolean): SquareSet;
40
+ with(square: Square): SquareSet;
41
+ without(square: Square): SquareSet;
42
+ toggle(square: Square): SquareSet;
43
+ last(): Square | undefined;
44
+ first(): Square | undefined;
45
+ withoutFirst(): SquareSet;
46
+ moreThanOne(): boolean;
47
+ singleSquare(): Square | undefined;
48
+ [Symbol.iterator](): Iterator<Square>;
49
+ reversed(): Iterable<Square>;
50
+ }
@@ -0,0 +1,9 @@
1
+ import { Board } from "./board.js";
2
+ import { Setup } from "./setup.js";
3
+ import { SquareSet } from "./squareSet.js";
4
+ export declare const flipVertical: (s: SquareSet) => SquareSet;
5
+ export declare const flipHorizontal: (s: SquareSet) => SquareSet;
6
+ export declare const flipDiagonal: (s: SquareSet) => SquareSet;
7
+ export declare const rotate180: (s: SquareSet) => SquareSet;
8
+ export declare const transformBoard: (board: Board, f: (s: SquareSet) => SquareSet) => Board;
9
+ export declare const transformSetup: (setup: Setup, f: (s: SquareSet) => SquareSet) => Setup;
@@ -0,0 +1,58 @@
1
+ export declare const FILE_NAMES: readonly ["a", "b", "c", "d", "e", "f", "g", "h"];
2
+ export type FileName = (typeof FILE_NAMES)[number];
3
+ export declare const RANK_NAMES: readonly ["1", "2", "3", "4", "5", "6", "7", "8"];
4
+ export type RankName = (typeof RANK_NAMES)[number];
5
+ export type Square = number;
6
+ export type SquareName = `${FileName}${RankName}`;
7
+ export declare const ROLE_CHARS: readonly ["q", "n", "r", "b", "p", "k"];
8
+ export type RoleChar = (typeof ROLE_CHARS)[number];
9
+ /**
10
+ * Indexable by square indices.
11
+ */
12
+ export type BySquare<T> = T[];
13
+ export declare const COLORS: readonly ["white", "black"];
14
+ export type Color = (typeof COLORS)[number];
15
+ /**
16
+ * Indexable by `white` and `black`.
17
+ */
18
+ export type ByColor<T> = {
19
+ [color in Color]: T;
20
+ };
21
+ export declare const ROLES: readonly ["pawn", "knight", "bishop", "rook", "queen", "king"];
22
+ export type Role = (typeof ROLES)[number];
23
+ /**
24
+ * Indexable by `pawn`, `knight`, `bishop`, `rook`, `queen`, and `king`.
25
+ */
26
+ export type ByRole<T> = {
27
+ [role in Role]: T;
28
+ };
29
+ export declare const CASTLING_SIDES: readonly ["a", "h"];
30
+ export type CastlingSide = (typeof CASTLING_SIDES)[number];
31
+ /**
32
+ * Indexable by `a` and `h`.
33
+ */
34
+ export type ByCastlingSide<T> = {
35
+ [side in CastlingSide]: T;
36
+ };
37
+ export interface Piece {
38
+ role: Role;
39
+ color: Color;
40
+ promoted?: boolean;
41
+ }
42
+ export interface NormalMove {
43
+ from: Square;
44
+ to: Square;
45
+ promotion?: Role;
46
+ }
47
+ export interface DropMove {
48
+ role: Role;
49
+ to: Square;
50
+ }
51
+ export type Move = NormalMove | DropMove;
52
+ export declare const isDrop: (v: Move) => v is DropMove;
53
+ export declare const isNormal: (v: Move) => v is NormalMove;
54
+ export declare const RULES: readonly ["chess", "antichess", "kingofthehill", "3check", "atomic", "horde", "racingkings", "crazyhouse"];
55
+ export type Rules = (typeof RULES)[number];
56
+ export interface Outcome {
57
+ winner: Color | undefined;
58
+ }
@@ -0,0 +1,21 @@
1
+ import { CastlingSide, Color, Move, Role, type RoleChar, Square, SquareName } from "./types.js";
2
+ export declare const defined: <A>(v: A | undefined) => v is A;
3
+ export declare const opposite: (color: Color) => Color;
4
+ export declare const squareRank: (square: Square) => number;
5
+ export declare const squareFile: (square: Square) => number;
6
+ export declare const squareFromCoords: (file: number, rank: number) => Square | undefined;
7
+ export declare const roleToChar: (role: Role) => RoleChar;
8
+ export declare function charToRole(ch: RoleChar | Uppercase<RoleChar>): Role;
9
+ export declare function charToRole(ch: string): Role | undefined;
10
+ export declare function parseSquare(str: SquareName): Square;
11
+ export declare function parseSquare(str: string): Square | undefined;
12
+ export declare const makeSquare: (square: Square) => SquareName;
13
+ export declare const parseUci: (str: string) => Move | undefined;
14
+ export declare const moveEquals: (left: Move, right: Move) => boolean;
15
+ /**
16
+ * Converts a move to UCI notation, like `g1f3` for a normal move,
17
+ * `a7a8q` for promotion to a queen, and `Q@f7` for a Crazyhouse drop.
18
+ */
19
+ export declare const makeUci: (move: Move) => string;
20
+ export declare const kingCastlesTo: (color: Color, side: CastlingSide) => Square;
21
+ export declare const rookCastlesTo: (color: Color, side: CastlingSide) => Square;
@@ -0,0 +1,92 @@
1
+ import { Result } from "@badrap/result";
2
+ import { Castles, castlingSide, Chess, Context, equalsIgnoreMoves, IllegalSetup, isImpossibleCheck, normalizeMove, Position, PositionError } from "./chess.js";
3
+ import { Setup } from "./setup.js";
4
+ import { SquareSet } from "./squareSet.js";
5
+ import { Color, Outcome, Piece, Rules, Square } from "./types.js";
6
+ export { Castles, castlingSide, Chess, Context, equalsIgnoreMoves, IllegalSetup, isImpossibleCheck, normalizeMove, Position, PositionError, };
7
+ export declare class Crazyhouse extends Position {
8
+ private constructor();
9
+ reset(): void;
10
+ protected setupUnchecked(setup: Setup): void;
11
+ static default(): Crazyhouse;
12
+ static fromSetup(setup: Setup): Result<Crazyhouse, PositionError>;
13
+ clone(): Crazyhouse;
14
+ protected validate(): Result<undefined, PositionError>;
15
+ hasInsufficientMaterial(color: Color): boolean;
16
+ dropDests(ctx?: Context): SquareSet;
17
+ }
18
+ export declare class Atomic extends Position {
19
+ private constructor();
20
+ static default(): Atomic;
21
+ static fromSetup(setup: Setup): Result<Atomic, PositionError>;
22
+ clone(): Atomic;
23
+ protected validate(): Result<undefined, PositionError>;
24
+ kingAttackers(square: Square, attacker: Color, occupied: SquareSet): SquareSet;
25
+ protected playCaptureAt(square: Square, captured: Piece): void;
26
+ hasInsufficientMaterial(color: Color): boolean;
27
+ dests(square: Square, ctx?: Context): SquareSet;
28
+ isVariantEnd(): boolean;
29
+ variantOutcome(_ctx?: Context): Outcome | undefined;
30
+ }
31
+ export declare class Antichess extends Position {
32
+ private constructor();
33
+ reset(): void;
34
+ protected setupUnchecked(setup: Setup): void;
35
+ static default(): Antichess;
36
+ static fromSetup(setup: Setup): Result<Antichess, PositionError>;
37
+ clone(): Antichess;
38
+ protected validate(): Result<undefined, PositionError>;
39
+ kingAttackers(_square: Square, _attacker: Color, _occupied: SquareSet): SquareSet;
40
+ ctx(): Context;
41
+ dests(square: Square, ctx?: Context): SquareSet;
42
+ hasInsufficientMaterial(color: Color): boolean;
43
+ isVariantEnd(): boolean;
44
+ variantOutcome(ctx?: Context): Outcome | undefined;
45
+ }
46
+ export declare class KingOfTheHill extends Position {
47
+ private constructor();
48
+ static default(): KingOfTheHill;
49
+ static fromSetup(setup: Setup): Result<KingOfTheHill, PositionError>;
50
+ clone(): KingOfTheHill;
51
+ hasInsufficientMaterial(_color: Color): boolean;
52
+ isVariantEnd(): boolean;
53
+ variantOutcome(_ctx?: Context): Outcome | undefined;
54
+ }
55
+ export declare class ThreeCheck extends Position {
56
+ private constructor();
57
+ reset(): void;
58
+ protected setupUnchecked(setup: Setup): void;
59
+ static default(): ThreeCheck;
60
+ static fromSetup(setup: Setup): Result<ThreeCheck, PositionError>;
61
+ clone(): ThreeCheck;
62
+ hasInsufficientMaterial(color: Color): boolean;
63
+ isVariantEnd(): boolean;
64
+ variantOutcome(_ctx?: Context): Outcome | undefined;
65
+ }
66
+ export declare class RacingKings extends Position {
67
+ private constructor();
68
+ reset(): void;
69
+ setupUnchecked(setup: Setup): void;
70
+ static default(): RacingKings;
71
+ static fromSetup(setup: Setup): Result<RacingKings, PositionError>;
72
+ clone(): RacingKings;
73
+ protected validate(): Result<undefined, PositionError>;
74
+ dests(square: Square, ctx?: Context): SquareSet;
75
+ hasInsufficientMaterial(_color: Color): boolean;
76
+ isVariantEnd(): boolean;
77
+ variantOutcome(ctx?: Context): Outcome | undefined;
78
+ }
79
+ export declare class Horde extends Position {
80
+ private constructor();
81
+ reset(): void;
82
+ static default(): Horde;
83
+ static fromSetup(setup: Setup): Result<Horde, PositionError>;
84
+ clone(): Horde;
85
+ protected validate(): Result<undefined, PositionError>;
86
+ hasInsufficientMaterial(color: Color): boolean;
87
+ isVariantEnd(): boolean;
88
+ variantOutcome(_ctx?: Context): Outcome | undefined;
89
+ }
90
+ export declare const defaultPosition: (rules: Rules) => Position;
91
+ export declare const setupPosition: (rules: Rules, setup: Setup) => Result<Position, PositionError>;
92
+ export declare const isStandardMaterial: (pos: Position) => boolean;
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@connectorvol/chessops",
3
+ "version": "0.15.1",
4
+ "description": "Chess and chess variant rules and operations",
5
+ "keywords": [
6
+ "chess",
7
+ "fen",
8
+ "lichess",
9
+ "pgn",
10
+ "typescript",
11
+ "uci"
12
+ ],
13
+ "license": "GPL-3.0-or-later",
14
+ "author": "Niklas Fiekas <niklas.fiekas@backscattering.de>",
15
+ "repository": "github:niklasf/chessops",
16
+ "funding": "https://github.com/sponsors/niklasf",
17
+ "files": [
18
+ "/src",
19
+ "/dist",
20
+ "!/**/*.test.*"
21
+ ],
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "main": "dist/cjs/index.js",
25
+ "module": "dist/esm/index.js",
26
+ "types": "index.d.ts",
27
+ "typesVersions": {
28
+ "*": {
29
+ "*": [
30
+ "dist/types/*"
31
+ ]
32
+ }
33
+ },
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/types/index.d.ts",
37
+ "import": "./dist/esm/index.js",
38
+ "require": "./dist/cjs/index.js"
39
+ },
40
+ "./*": {
41
+ "types": "./dist/types/*.d.ts",
42
+ "import": "./dist/esm/*.js",
43
+ "require": "./dist/cjs/*.js"
44
+ }
45
+ },
46
+ "scripts": {
47
+ "package": "tsc --declarationDir dist/types && tsc --outDir dist/cjs --module commonjs --declaration false",
48
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
49
+ "doc": "typedoc src/types.ts src/attacks.ts src/util.ts src/squareSet.ts src/board.ts src/setup.ts src/chess.ts src/compat.ts src/debug.ts src/fen.ts src/san.ts src/transform.ts src/variant.ts src/pgn.ts",
50
+ "lint": "eslint",
51
+ "format": "dprint fmt",
52
+ "check-format": "dprint check"
53
+ },
54
+ "dependencies": {
55
+ "@badrap/result": "^0.3"
56
+ },
57
+ "devDependencies": {
58
+ "@jest/globals": "^30",
59
+ "@types/node": "^24.9.1",
60
+ "@typescript-eslint/eslint-plugin": "^8",
61
+ "@typescript-eslint/parser": "^8",
62
+ "dprint": "^0.50",
63
+ "eslint": "^9",
64
+ "jest": "^30",
65
+ "ts-jest": "^29",
66
+ "typedoc": "^0.28",
67
+ "typescript": "^5"
68
+ },
69
+ "jest": {
70
+ "extensionsToTreatAsEsm": [
71
+ ".ts"
72
+ ],
73
+ "moduleNameMapper": {
74
+ "^(.*)\\.js$": "$1"
75
+ },
76
+ "testRegex": ".*\\.test\\.ts$",
77
+ "transform": {
78
+ "\\.ts$": [
79
+ "ts-jest",
80
+ {
81
+ "useESM": true
82
+ }
83
+ ]
84
+ }
85
+ }
86
+ }
package/src/attacks.ts ADDED
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Compute attacks and rays.
3
+ *
4
+ * These are low-level functions that can be used to implement chess rules.
5
+ *
6
+ * Implementation notes: Sliding attacks are computed using
7
+ * [Hyperbola Quintessence](https://www.chessprogramming.org/Hyperbola_Quintessence).
8
+ * Magic Bitboards would deliver slightly faster lookups, but also require
9
+ * initializing considerably larger attack tables. On the web, initialization
10
+ * time is important, so the chosen method may strike a better balance.
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+
15
+ import { SquareSet } from "./squareSet.js";
16
+ import { BySquare, Color, Piece, Square } from "./types.js";
17
+ import { squareFile, squareRank } from "./util.js";
18
+
19
+ const computeRange = (square: Square, deltas: number[]): SquareSet => {
20
+ let range = SquareSet.empty();
21
+ for (const delta of deltas) {
22
+ const sq = square + delta;
23
+ if (0 <= sq && sq < 64 && Math.abs(squareFile(square) - squareFile(sq)) <= 2) {
24
+ range = range.with(sq);
25
+ }
26
+ }
27
+ return range;
28
+ };
29
+
30
+ const tabulate = <T>(f: (square: Square) => T): BySquare<T> => {
31
+ const table = [];
32
+ for (let square = 0; square < 64; square++) table[square] = f(square);
33
+ return table;
34
+ };
35
+
36
+ const KING_ATTACKS = tabulate((sq) => computeRange(sq, [-9, -8, -7, -1, 1, 7, 8, 9]));
37
+ const KNIGHT_ATTACKS = tabulate((sq) => computeRange(sq, [-17, -15, -10, -6, 6, 10, 15, 17]));
38
+ const PAWN_ATTACKS = {
39
+ white: tabulate((sq) => computeRange(sq, [7, 9])),
40
+ black: tabulate((sq) => computeRange(sq, [-7, -9])),
41
+ };
42
+
43
+ /**
44
+ * Gets squares attacked or defended by a king on `square`.
45
+ */
46
+ export const kingAttacks = (square: Square): SquareSet => KING_ATTACKS[square];
47
+
48
+ /**
49
+ * Gets squares attacked or defended by a knight on `square`.
50
+ */
51
+ export const knightAttacks = (square: Square): SquareSet => KNIGHT_ATTACKS[square];
52
+
53
+ /**
54
+ * Gets squares attacked or defended by a pawn of the given `color`
55
+ * on `square`.
56
+ */
57
+ export const pawnAttacks = (color: Color, square: Square): SquareSet => PAWN_ATTACKS[color][square];
58
+
59
+ const FILE_RANGE = tabulate((sq) => SquareSet.fromFile(squareFile(sq)).without(sq));
60
+ const RANK_RANGE = tabulate((sq) => SquareSet.fromRank(squareRank(sq)).without(sq));
61
+
62
+ const DIAG_RANGE = tabulate((sq) => {
63
+ const diag = new SquareSet(0x0804_0201, 0x8040_2010);
64
+ const shift = 8 * (squareRank(sq) - squareFile(sq));
65
+ return (shift >= 0 ? diag.shl64(shift) : diag.shr64(-shift)).without(sq);
66
+ });
67
+
68
+ const ANTI_DIAG_RANGE = tabulate((sq) => {
69
+ const diag = new SquareSet(0x1020_4080, 0x0102_0408);
70
+ const shift = 8 * (squareRank(sq) + squareFile(sq) - 7);
71
+ return (shift >= 0 ? diag.shl64(shift) : diag.shr64(-shift)).without(sq);
72
+ });
73
+
74
+ const hyperbola = (bit: SquareSet, range: SquareSet, occupied: SquareSet): SquareSet => {
75
+ let forward = occupied.intersect(range);
76
+ let reverse = forward.bswap64(); // Assumes no more than 1 bit per rank
77
+ forward = forward.minus64(bit);
78
+ reverse = reverse.minus64(bit.bswap64());
79
+ return forward.xor(reverse.bswap64()).intersect(range);
80
+ };
81
+
82
+ const fileAttacks = (square: Square, occupied: SquareSet): SquareSet =>
83
+ hyperbola(SquareSet.fromSquare(square), FILE_RANGE[square], occupied);
84
+
85
+ const rankAttacks = (square: Square, occupied: SquareSet): SquareSet => {
86
+ const range = RANK_RANGE[square];
87
+ let forward = occupied.intersect(range);
88
+ let reverse = forward.rbit64();
89
+ forward = forward.minus64(SquareSet.fromSquare(square));
90
+ reverse = reverse.minus64(SquareSet.fromSquare(63 - square));
91
+ return forward.xor(reverse.rbit64()).intersect(range);
92
+ };
93
+
94
+ /**
95
+ * Gets squares attacked or defended by a bishop on `square`, given `occupied`
96
+ * squares.
97
+ */
98
+ export const bishopAttacks = (square: Square, occupied: SquareSet): SquareSet => {
99
+ const bit = SquareSet.fromSquare(square);
100
+ return hyperbola(bit, DIAG_RANGE[square], occupied).xor(
101
+ hyperbola(bit, ANTI_DIAG_RANGE[square], occupied),
102
+ );
103
+ };
104
+
105
+ /**
106
+ * Gets squares attacked or defended by a rook on `square`, given `occupied`
107
+ * squares.
108
+ */
109
+ export const rookAttacks = (square: Square, occupied: SquareSet): SquareSet =>
110
+ fileAttacks(square, occupied).xor(rankAttacks(square, occupied));
111
+
112
+ /**
113
+ * Gets squares attacked or defended by a queen on `square`, given `occupied`
114
+ * squares.
115
+ */
116
+ export const queenAttacks = (square: Square, occupied: SquareSet): SquareSet =>
117
+ bishopAttacks(square, occupied).xor(rookAttacks(square, occupied));
118
+
119
+ /**
120
+ * Gets squares attacked or defended by a `piece` on `square`, given
121
+ * `occupied` squares.
122
+ */
123
+ export const attacks = (piece: Piece, square: Square, occupied: SquareSet): SquareSet => {
124
+ switch (piece.role) {
125
+ case "pawn":
126
+ return pawnAttacks(piece.color, square);
127
+ case "knight":
128
+ return knightAttacks(square);
129
+ case "bishop":
130
+ return bishopAttacks(square, occupied);
131
+ case "rook":
132
+ return rookAttacks(square, occupied);
133
+ case "queen":
134
+ return queenAttacks(square, occupied);
135
+ case "king":
136
+ return kingAttacks(square);
137
+ }
138
+ };
139
+
140
+ /**
141
+ * Gets all squares of the rank, file or diagonal with the two squares
142
+ * `a` and `b`, or an empty set if they are not aligned.
143
+ */
144
+ export const ray = (a: Square, b: Square): SquareSet => {
145
+ const other = SquareSet.fromSquare(b);
146
+ if (RANK_RANGE[a].intersects(other)) return RANK_RANGE[a].with(a);
147
+ if (ANTI_DIAG_RANGE[a].intersects(other)) return ANTI_DIAG_RANGE[a].with(a);
148
+ if (DIAG_RANGE[a].intersects(other)) return DIAG_RANGE[a].with(a);
149
+ if (FILE_RANGE[a].intersects(other)) return FILE_RANGE[a].with(a);
150
+ return SquareSet.empty();
151
+ };
152
+
153
+ /**
154
+ * Gets all squares between `a` and `b` (bounds not included), or an empty set
155
+ * if they are not on the same rank, file or diagonal.
156
+ */
157
+ export const between = (a: Square, b: Square): SquareSet =>
158
+ ray(a, b)
159
+ .intersect(SquareSet.full().shl64(a).xor(SquareSet.full().shl64(b)))
160
+ .withoutFirst();
package/src/board.ts ADDED
@@ -0,0 +1,168 @@
1
+ import { SquareSet } from "./squareSet.js";
2
+ import { ByColor, ByRole, Color, COLORS, Piece, Role, ROLES, Square } from "./types.js";
3
+
4
+ /**
5
+ * Piece positions on a board.
6
+ *
7
+ * Properties are sets of squares, like `board.occupied` for all occupied
8
+ * squares, `board[color]` for all pieces of that color, and `board[role]`
9
+ * for all pieces of that role. When modifying the properties directly, take
10
+ * care to keep them consistent.
11
+ */
12
+ export class Board implements Iterable<[Square, Piece]>, ByRole<SquareSet>, ByColor<SquareSet> {
13
+ /**
14
+ * All occupied squares.
15
+ */
16
+ occupied: SquareSet;
17
+ /**
18
+ * All squares occupied by pieces known to be promoted. This information is
19
+ * relevant in chess variants like Crazyhouse.
20
+ */
21
+ promoted: SquareSet;
22
+
23
+ white: SquareSet;
24
+ black: SquareSet;
25
+
26
+ pawn: SquareSet;
27
+ knight: SquareSet;
28
+ bishop: SquareSet;
29
+ rook: SquareSet;
30
+ queen: SquareSet;
31
+ king: SquareSet;
32
+
33
+ private constructor() {}
34
+
35
+ static default(): Board {
36
+ const board = new Board();
37
+ board.reset();
38
+ return board;
39
+ }
40
+
41
+ /**
42
+ * Resets all pieces to the default starting position for standard chess.
43
+ */
44
+ reset(): void {
45
+ this.occupied = new SquareSet(0xffff, 0xffff_0000);
46
+ this.promoted = SquareSet.empty();
47
+ this.white = new SquareSet(0xffff, 0);
48
+ this.black = new SquareSet(0, 0xffff_0000);
49
+ this.pawn = new SquareSet(0xff00, 0x00ff_0000);
50
+ this.knight = new SquareSet(0x42, 0x4200_0000);
51
+ this.bishop = new SquareSet(0x24, 0x2400_0000);
52
+ this.rook = new SquareSet(0x81, 0x8100_0000);
53
+ this.queen = new SquareSet(0x8, 0x0800_0000);
54
+ this.king = new SquareSet(0x10, 0x1000_0000);
55
+ }
56
+
57
+ static empty(): Board {
58
+ const board = new Board();
59
+ board.clear();
60
+ return board;
61
+ }
62
+
63
+ clear(): void {
64
+ this.occupied = SquareSet.empty();
65
+ this.promoted = SquareSet.empty();
66
+ for (const color of COLORS) this[color] = SquareSet.empty();
67
+ for (const role of ROLES) this[role] = SquareSet.empty();
68
+ }
69
+
70
+ clone(): Board {
71
+ const board = new Board();
72
+ board.occupied = this.occupied;
73
+ board.promoted = this.promoted;
74
+ for (const color of COLORS) board[color] = this[color];
75
+ for (const role of ROLES) board[role] = this[role];
76
+ return board;
77
+ }
78
+
79
+ getColor(square: Square): Color | undefined {
80
+ if (this.white.has(square)) return "white";
81
+ if (this.black.has(square)) return "black";
82
+ return;
83
+ }
84
+
85
+ getRole(square: Square): Role | undefined {
86
+ for (const role of ROLES) {
87
+ if (this[role].has(square)) return role;
88
+ }
89
+ return;
90
+ }
91
+
92
+ get(square: Square): Piece | undefined {
93
+ const color = this.getColor(square);
94
+ if (!color) return;
95
+ const role = this.getRole(square)!;
96
+ const promoted = this.promoted.has(square);
97
+ return { color, role, promoted };
98
+ }
99
+
100
+ /**
101
+ * Removes and returns the piece from the given `square`, if any.
102
+ */
103
+ take(square: Square): Piece | undefined {
104
+ const piece = this.get(square);
105
+ if (piece) {
106
+ this.occupied = this.occupied.without(square);
107
+ this[piece.color] = this[piece.color].without(square);
108
+ this[piece.role] = this[piece.role].without(square);
109
+ if (piece.promoted) this.promoted = this.promoted.without(square);
110
+ }
111
+ return piece;
112
+ }
113
+
114
+ /**
115
+ * Put `piece` onto `square`, potentially replacing an existing piece.
116
+ * Returns the existing piece, if any.
117
+ */
118
+ set(square: Square, piece: Piece): Piece | undefined {
119
+ const old = this.take(square);
120
+ this.occupied = this.occupied.with(square);
121
+ this[piece.color] = this[piece.color].with(square);
122
+ this[piece.role] = this[piece.role].with(square);
123
+ if (piece.promoted) this.promoted = this.promoted.with(square);
124
+ return old;
125
+ }
126
+
127
+ has(square: Square): boolean {
128
+ return this.occupied.has(square);
129
+ }
130
+
131
+ *[Symbol.iterator](): Iterator<[Square, Piece]> {
132
+ for (const square of this.occupied) {
133
+ yield [square, this.get(square)!];
134
+ }
135
+ }
136
+
137
+ pieces(color: Color, role: Role): SquareSet {
138
+ return this[color].intersect(this[role]);
139
+ }
140
+
141
+ rooksAndQueens(): SquareSet {
142
+ return this.rook.union(this.queen);
143
+ }
144
+
145
+ bishopsAndQueens(): SquareSet {
146
+ return this.bishop.union(this.queen);
147
+ }
148
+
149
+ steppers(): SquareSet {
150
+ return this.knight.union(this.pawn).union(this.king);
151
+ }
152
+
153
+ sliders(): SquareSet {
154
+ return this.bishop.union(this.rook).union(this.queen);
155
+ }
156
+
157
+ /**
158
+ * Finds the unique king of the given `color`, if any.
159
+ */
160
+ kingOf(color: Color): Square | undefined {
161
+ return this.pieces(color, "king").singleSquare();
162
+ }
163
+ }
164
+
165
+ export const boardEquals = (left: Board, right: Board): boolean =>
166
+ left.white.equals(right.white) &&
167
+ left.promoted.equals(right.promoted) &&
168
+ ROLES.every((role) => left[role].equals(right[role]));