@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
package/src/san.ts ADDED
@@ -0,0 +1,190 @@
1
+ import {
2
+ attacks,
3
+ bishopAttacks,
4
+ kingAttacks,
5
+ knightAttacks,
6
+ queenAttacks,
7
+ rookAttacks,
8
+ } from "./attacks.js";
9
+ import { Position } from "./chess.js";
10
+ import { SquareSet } from "./squareSet.js";
11
+ import { CastlingSide, FILE_NAMES, isDrop, Move, RANK_NAMES, SquareName } from "./types.js";
12
+ import {
13
+ charToRole,
14
+ defined,
15
+ makeSquare,
16
+ opposite,
17
+ parseSquare,
18
+ roleToChar,
19
+ squareFile,
20
+ squareRank,
21
+ } from "./util.js";
22
+
23
+ const makeSanWithoutSuffix = (pos: Position, move: Move): string => {
24
+ let san = "";
25
+ if (isDrop(move)) {
26
+ if (move.role !== "pawn") san = roleToChar(move.role).toUpperCase();
27
+ san += "@" + makeSquare(move.to);
28
+ } else {
29
+ const role = pos.board.getRole(move.from);
30
+ if (!role) return "--";
31
+ if (
32
+ role === "king" &&
33
+ (pos.board[pos.turn].has(move.to) || Math.abs(move.to - move.from) === 2)
34
+ ) {
35
+ san = move.to > move.from ? "O-O" : "O-O-O";
36
+ } else {
37
+ const capture =
38
+ pos.board.occupied.has(move.to) ||
39
+ (role === "pawn" && squareFile(move.from) !== squareFile(move.to));
40
+ if (role !== "pawn") {
41
+ san = roleToChar(role).toUpperCase();
42
+
43
+ // Disambiguation
44
+ let others;
45
+ if (role === "king") others = kingAttacks(move.to).intersect(pos.board.king);
46
+ else if (role === "queen")
47
+ others = queenAttacks(move.to, pos.board.occupied).intersect(pos.board.queen);
48
+ else if (role === "rook")
49
+ others = rookAttacks(move.to, pos.board.occupied).intersect(pos.board.rook);
50
+ else if (role === "bishop")
51
+ others = bishopAttacks(move.to, pos.board.occupied).intersect(pos.board.bishop);
52
+ else others = knightAttacks(move.to).intersect(pos.board.knight);
53
+ others = others.intersect(pos.board[pos.turn]).without(move.from);
54
+ if (others.nonEmpty()) {
55
+ const ctx = pos.ctx();
56
+ for (const from of others) {
57
+ if (!pos.dests(from, ctx).has(move.to)) others = others.without(from);
58
+ }
59
+ if (others.nonEmpty()) {
60
+ let row = false;
61
+ let column = others.intersects(SquareSet.fromRank(squareRank(move.from)));
62
+ if (others.intersects(SquareSet.fromFile(squareFile(move.from)))) row = true;
63
+ else column = true;
64
+ if (column) san += FILE_NAMES[squareFile(move.from)];
65
+ if (row) san += RANK_NAMES[squareRank(move.from)];
66
+ }
67
+ }
68
+ } else if (capture) san = FILE_NAMES[squareFile(move.from)];
69
+
70
+ if (capture) san += "x";
71
+ san += makeSquare(move.to);
72
+ if (move.promotion) san += "=" + roleToChar(move.promotion).toUpperCase();
73
+ }
74
+ }
75
+ return san;
76
+ };
77
+
78
+ export const makeSanAndPlay = (pos: Position, move: Move): string => {
79
+ const san = makeSanWithoutSuffix(pos, move);
80
+ pos.play(move);
81
+ if (pos.outcome()?.winner) return san + "#";
82
+ if (pos.isCheck()) return san + "+";
83
+ return san;
84
+ };
85
+
86
+ export const makeSanVariation = (pos: Position, variation: Move[]): string => {
87
+ pos = pos.clone();
88
+ const line = [];
89
+ for (let i = 0; i < variation.length; i++) {
90
+ if (i !== 0) line.push(" ");
91
+ if (pos.turn === "white") line.push(pos.fullmoves, ". ");
92
+ else if (i === 0) line.push(pos.fullmoves, "... ");
93
+ const san = makeSanWithoutSuffix(pos, variation[i]);
94
+ pos.play(variation[i]);
95
+ line.push(san);
96
+ if (san === "--") return line.join("");
97
+ if (i === variation.length - 1 && pos.outcome()?.winner) line.push("#");
98
+ else if (pos.isCheck()) line.push("+");
99
+ }
100
+ return line.join("");
101
+ };
102
+
103
+ export const makeSan = (pos: Position, move: Move): string => makeSanAndPlay(pos.clone(), move);
104
+
105
+ export const parseSan = (pos: Position, san: string): Move | undefined => {
106
+ const ctx = pos.ctx();
107
+
108
+ // Normal move
109
+ const match = san.match(
110
+ /^([NBRQK])?([a-h])?([1-8])?[-x]?([a-h][1-8])(?:=?([nbrqkNBRQK]))?[+#]?$/,
111
+ ) as
112
+ | [
113
+ string,
114
+ "N" | "B" | "R" | "Q" | "K" | undefined,
115
+ string | undefined,
116
+ string | undefined,
117
+ SquareName,
118
+ "n" | "b" | "r" | "q" | "k" | "N" | "B" | "R" | "Q" | "K" | undefined,
119
+ ]
120
+ | null;
121
+ if (!match) {
122
+ // Castling
123
+ let castlingSide: CastlingSide | undefined;
124
+ if (san === "O-O" || san === "O-O+" || san === "O-O#") castlingSide = "h";
125
+ else if (san === "O-O-O" || san === "O-O-O+" || san === "O-O-O#") castlingSide = "a";
126
+ if (castlingSide) {
127
+ const rook = pos.castles.rook[pos.turn][castlingSide];
128
+ if (!defined(ctx.king) || !defined(rook) || !pos.dests(ctx.king, ctx).has(rook)) return;
129
+ return {
130
+ from: ctx.king,
131
+ to: rook,
132
+ };
133
+ }
134
+
135
+ // Drop
136
+ const match = san.match(/^([pnbrqkPNBRQK])?@([a-h][1-8])[+#]?$/) as
137
+ | [
138
+ string,
139
+ "p" | "n" | "b" | "r" | "q" | "k" | "P" | "N" | "B" | "R" | "Q" | "K" | undefined,
140
+ SquareName,
141
+ ]
142
+ | null;
143
+ if (!match) return;
144
+ const move = {
145
+ role: match[1] ? charToRole(match[1]) : "pawn",
146
+ to: parseSquare(match[2]),
147
+ };
148
+ return pos.isLegal(move, ctx) ? move : undefined;
149
+ }
150
+ const role = match[1] ? charToRole(match[1]) : "pawn";
151
+ const to = parseSquare(match[4]);
152
+
153
+ const promotion = match[5] ? charToRole(match[5]) : undefined;
154
+ if (!!promotion !== (role === "pawn" && SquareSet.backranks().has(to))) return;
155
+ if (promotion === "king" && pos.rules !== "antichess") return;
156
+
157
+ let candidates = pos.board.pieces(pos.turn, role);
158
+ if (role === "pawn" && !match[2])
159
+ candidates = candidates.intersect(SquareSet.fromFile(squareFile(to)));
160
+ else if (match[2])
161
+ candidates = candidates.intersect(
162
+ SquareSet.fromFile(match[2].charCodeAt(0) - "a".charCodeAt(0)),
163
+ );
164
+ if (match[3])
165
+ candidates = candidates.intersect(
166
+ SquareSet.fromRank(match[3].charCodeAt(0) - "1".charCodeAt(0)),
167
+ );
168
+
169
+ // Optimization: Reduce set of candidates
170
+ const pawnAdvance = role === "pawn" ? SquareSet.fromFile(squareFile(to)) : SquareSet.empty();
171
+ candidates = candidates.intersect(
172
+ pawnAdvance.union(attacks({ color: opposite(pos.turn), role }, to, pos.board.occupied)),
173
+ );
174
+
175
+ // Check uniqueness and legality
176
+ let from;
177
+ for (const candidate of candidates) {
178
+ if (pos.dests(candidate, ctx).has(to)) {
179
+ if (defined(from)) return; // Ambiguous
180
+ from = candidate;
181
+ }
182
+ }
183
+ if (!defined(from)) return; // Illegal
184
+
185
+ return {
186
+ from,
187
+ to,
188
+ promotion,
189
+ };
190
+ };
package/src/setup.ts ADDED
@@ -0,0 +1,203 @@
1
+ import { Board, boardEquals } from "./board.js";
2
+ import { SquareSet } from "./squareSet.js";
3
+ import { ByColor, ByRole, Color, Role, ROLES, Square } from "./types.js";
4
+
5
+ export class MaterialSide implements ByRole<number> {
6
+ pawn: number;
7
+ knight: number;
8
+ bishop: number;
9
+ rook: number;
10
+ queen: number;
11
+ king: number;
12
+
13
+ private constructor() {}
14
+
15
+ static empty(): MaterialSide {
16
+ const m = new MaterialSide();
17
+ for (const role of ROLES) m[role] = 0;
18
+ return m;
19
+ }
20
+
21
+ static fromBoard(board: Board, color: Color): MaterialSide {
22
+ const m = new MaterialSide();
23
+ for (const role of ROLES) m[role] = board.pieces(color, role).size();
24
+ return m;
25
+ }
26
+
27
+ clone(): MaterialSide {
28
+ const m = new MaterialSide();
29
+ for (const role of ROLES) m[role] = this[role];
30
+ return m;
31
+ }
32
+
33
+ equals(other: MaterialSide): boolean {
34
+ return ROLES.every((role) => this[role] === other[role]);
35
+ }
36
+
37
+ add(other: MaterialSide): MaterialSide {
38
+ const m = new MaterialSide();
39
+ for (const role of ROLES) m[role] = this[role] + other[role];
40
+ return m;
41
+ }
42
+
43
+ subtract(other: MaterialSide): MaterialSide {
44
+ const m = new MaterialSide();
45
+ for (const role of ROLES) m[role] = this[role] - other[role];
46
+ return m;
47
+ }
48
+
49
+ nonEmpty(): boolean {
50
+ return ROLES.some((role) => this[role] > 0);
51
+ }
52
+
53
+ isEmpty(): boolean {
54
+ return !this.nonEmpty();
55
+ }
56
+
57
+ hasPawns(): boolean {
58
+ return this.pawn > 0;
59
+ }
60
+
61
+ hasNonPawns(): boolean {
62
+ return this.knight > 0 || this.bishop > 0 || this.rook > 0 || this.queen > 0 || this.king > 0;
63
+ }
64
+
65
+ size(): number {
66
+ return this.pawn + this.knight + this.bishop + this.rook + this.queen + this.king;
67
+ }
68
+ }
69
+
70
+ export class Material implements ByColor<MaterialSide> {
71
+ constructor(
72
+ public white: MaterialSide,
73
+ public black: MaterialSide,
74
+ ) {}
75
+
76
+ static empty(): Material {
77
+ return new Material(MaterialSide.empty(), MaterialSide.empty());
78
+ }
79
+
80
+ static fromBoard(board: Board): Material {
81
+ return new Material(
82
+ MaterialSide.fromBoard(board, "white"),
83
+ MaterialSide.fromBoard(board, "black"),
84
+ );
85
+ }
86
+
87
+ clone(): Material {
88
+ return new Material(this.white.clone(), this.black.clone());
89
+ }
90
+
91
+ equals(other: Material): boolean {
92
+ return this.white.equals(other.white) && this.black.equals(other.black);
93
+ }
94
+
95
+ add(other: Material): Material {
96
+ return new Material(this.white.add(other.white), this.black.add(other.black));
97
+ }
98
+
99
+ subtract(other: Material): Material {
100
+ return new Material(this.white.subtract(other.white), this.black.subtract(other.black));
101
+ }
102
+
103
+ count(role: Role): number {
104
+ return this.white[role] + this.black[role];
105
+ }
106
+
107
+ size(): number {
108
+ return this.white.size() + this.black.size();
109
+ }
110
+
111
+ isEmpty(): boolean {
112
+ return this.white.isEmpty() && this.black.isEmpty();
113
+ }
114
+
115
+ nonEmpty(): boolean {
116
+ return !this.isEmpty();
117
+ }
118
+
119
+ hasPawns(): boolean {
120
+ return this.white.hasPawns() || this.black.hasPawns();
121
+ }
122
+
123
+ hasNonPawns(): boolean {
124
+ return this.white.hasNonPawns() || this.black.hasNonPawns();
125
+ }
126
+ }
127
+
128
+ export class RemainingChecks implements ByColor<number> {
129
+ constructor(
130
+ public white: number,
131
+ public black: number,
132
+ ) {}
133
+
134
+ static default(): RemainingChecks {
135
+ return new RemainingChecks(3, 3);
136
+ }
137
+
138
+ clone(): RemainingChecks {
139
+ return new RemainingChecks(this.white, this.black);
140
+ }
141
+
142
+ equals(other: RemainingChecks): boolean {
143
+ return this.white === other.white && this.black === other.black;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * A not necessarily legal chess or chess variant position.
149
+ */
150
+ export interface Setup {
151
+ board: Board;
152
+ pockets: Material | undefined;
153
+ turn: Color;
154
+ castlingRights: SquareSet;
155
+ epSquare: Square | undefined;
156
+ remainingChecks: RemainingChecks | undefined;
157
+ halfmoves: number;
158
+ fullmoves: number;
159
+ }
160
+
161
+ export const defaultSetup = (): Setup => ({
162
+ board: Board.default(),
163
+ pockets: undefined,
164
+ turn: "white",
165
+ castlingRights: SquareSet.corners(),
166
+ epSquare: undefined,
167
+ remainingChecks: undefined,
168
+ halfmoves: 0,
169
+ fullmoves: 1,
170
+ });
171
+
172
+ export const emptySetup = (): Setup => ({
173
+ board: Board.empty(),
174
+ pockets: undefined,
175
+ turn: "white",
176
+ castlingRights: SquareSet.empty(),
177
+ epSquare: undefined,
178
+ remainingChecks: undefined,
179
+ halfmoves: 0,
180
+ fullmoves: 1,
181
+ });
182
+
183
+ export const setupClone = (setup: Setup): Setup => ({
184
+ board: setup.board.clone(),
185
+ pockets: setup.pockets?.clone(),
186
+ turn: setup.turn,
187
+ castlingRights: setup.castlingRights,
188
+ epSquare: setup.epSquare,
189
+ remainingChecks: setup.remainingChecks?.clone(),
190
+ halfmoves: setup.halfmoves,
191
+ fullmoves: setup.fullmoves,
192
+ });
193
+
194
+ export const setupEquals = (left: Setup, right: Setup): boolean =>
195
+ boardEquals(left.board, right.board) &&
196
+ ((right.pockets && left.pockets?.equals(right.pockets)) || (!left.pockets && !right.pockets)) &&
197
+ left.turn === right.turn &&
198
+ left.castlingRights.equals(right.castlingRights) &&
199
+ left.epSquare === right.epSquare &&
200
+ ((right.remainingChecks && left.remainingChecks?.equals(right.remainingChecks)) ||
201
+ (!left.remainingChecks && !right.remainingChecks)) &&
202
+ left.halfmoves === right.halfmoves &&
203
+ left.fullmoves === right.fullmoves;
@@ -0,0 +1,243 @@
1
+ import { Color, Square } from "./types.js";
2
+
3
+ const popcnt32 = (n: number): number => {
4
+ n = n - ((n >>> 1) & 0x5555_5555);
5
+ n = (n & 0x3333_3333) + ((n >>> 2) & 0x3333_3333);
6
+ return Math.imul((n + (n >>> 4)) & 0x0f0f_0f0f, 0x0101_0101) >> 24;
7
+ };
8
+
9
+ const bswap32 = (n: number): number => {
10
+ n = ((n >>> 8) & 0x00ff_00ff) | ((n & 0x00ff_00ff) << 8);
11
+ return ((n >>> 16) & 0xffff) | ((n & 0xffff) << 16);
12
+ };
13
+
14
+ const rbit32 = (n: number): number => {
15
+ n = ((n >>> 1) & 0x5555_5555) | ((n & 0x5555_5555) << 1);
16
+ n = ((n >>> 2) & 0x3333_3333) | ((n & 0x3333_3333) << 2);
17
+ n = ((n >>> 4) & 0x0f0f_0f0f) | ((n & 0x0f0f_0f0f) << 4);
18
+ return bswap32(n);
19
+ };
20
+
21
+ /**
22
+ * An immutable set of squares, implemented as a bitboard.
23
+ */
24
+ export class SquareSet implements Iterable<Square> {
25
+ readonly lo: number;
26
+ readonly hi: number;
27
+
28
+ constructor(lo: number, hi: number) {
29
+ this.lo = lo | 0;
30
+ this.hi = hi | 0;
31
+ }
32
+
33
+ static fromSquare(square: Square): SquareSet {
34
+ return square >= 32 ? new SquareSet(0, 1 << (square - 32)) : new SquareSet(1 << square, 0);
35
+ }
36
+
37
+ static fromRank(rank: number): SquareSet {
38
+ return new SquareSet(0xff, 0).shl64(8 * rank);
39
+ }
40
+
41
+ static fromFile(file: number): SquareSet {
42
+ return new SquareSet(0x0101_0101 << file, 0x0101_0101 << file);
43
+ }
44
+
45
+ static empty(): SquareSet {
46
+ return new SquareSet(0, 0);
47
+ }
48
+
49
+ static full(): SquareSet {
50
+ return new SquareSet(0xffff_ffff, 0xffff_ffff);
51
+ }
52
+
53
+ static corners(): SquareSet {
54
+ return new SquareSet(0x81, 0x8100_0000);
55
+ }
56
+
57
+ static center(): SquareSet {
58
+ return new SquareSet(0x1800_0000, 0x18);
59
+ }
60
+
61
+ static backranks(): SquareSet {
62
+ return new SquareSet(0xff, 0xff00_0000);
63
+ }
64
+
65
+ static backrank(color: Color): SquareSet {
66
+ return color === "white" ? new SquareSet(0xff, 0) : new SquareSet(0, 0xff00_0000);
67
+ }
68
+
69
+ static lightSquares(): SquareSet {
70
+ return new SquareSet(0x55aa_55aa, 0x55aa_55aa);
71
+ }
72
+
73
+ static darkSquares(): SquareSet {
74
+ return new SquareSet(0xaa55_aa55, 0xaa55_aa55);
75
+ }
76
+
77
+ complement(): SquareSet {
78
+ return new SquareSet(~this.lo, ~this.hi);
79
+ }
80
+
81
+ xor(other: SquareSet): SquareSet {
82
+ return new SquareSet(this.lo ^ other.lo, this.hi ^ other.hi);
83
+ }
84
+
85
+ union(other: SquareSet): SquareSet {
86
+ return new SquareSet(this.lo | other.lo, this.hi | other.hi);
87
+ }
88
+
89
+ intersect(other: SquareSet): SquareSet {
90
+ return new SquareSet(this.lo & other.lo, this.hi & other.hi);
91
+ }
92
+
93
+ diff(other: SquareSet): SquareSet {
94
+ return new SquareSet(this.lo & ~other.lo, this.hi & ~other.hi);
95
+ }
96
+
97
+ intersects(other: SquareSet): boolean {
98
+ return this.intersect(other).nonEmpty();
99
+ }
100
+
101
+ isDisjoint(other: SquareSet): boolean {
102
+ return this.intersect(other).isEmpty();
103
+ }
104
+
105
+ supersetOf(other: SquareSet): boolean {
106
+ return other.diff(this).isEmpty();
107
+ }
108
+
109
+ subsetOf(other: SquareSet): boolean {
110
+ return this.diff(other).isEmpty();
111
+ }
112
+
113
+ shr64(shift: number): SquareSet {
114
+ if (shift >= 64) return SquareSet.empty();
115
+ if (shift >= 32) return new SquareSet(this.hi >>> (shift - 32), 0);
116
+ if (shift > 0)
117
+ return new SquareSet((this.lo >>> shift) ^ (this.hi << (32 - shift)), this.hi >>> shift);
118
+ return this;
119
+ }
120
+
121
+ shl64(shift: number): SquareSet {
122
+ if (shift >= 64) return SquareSet.empty();
123
+ if (shift >= 32) return new SquareSet(0, this.lo << (shift - 32));
124
+ if (shift > 0)
125
+ return new SquareSet(this.lo << shift, (this.hi << shift) ^ (this.lo >>> (32 - shift)));
126
+ return this;
127
+ }
128
+
129
+ bswap64(): SquareSet {
130
+ return new SquareSet(bswap32(this.hi), bswap32(this.lo));
131
+ }
132
+
133
+ rbit64(): SquareSet {
134
+ return new SquareSet(rbit32(this.hi), rbit32(this.lo));
135
+ }
136
+
137
+ minus64(other: SquareSet): SquareSet {
138
+ const lo = this.lo - other.lo;
139
+ const c = ((lo & other.lo & 1) + (other.lo >>> 1) + (lo >>> 1)) >>> 31;
140
+ return new SquareSet(lo, this.hi - (other.hi + c));
141
+ }
142
+
143
+ equals(other: SquareSet): boolean {
144
+ return this.lo === other.lo && this.hi === other.hi;
145
+ }
146
+
147
+ size(): number {
148
+ return popcnt32(this.lo) + popcnt32(this.hi);
149
+ }
150
+
151
+ isEmpty(): boolean {
152
+ return this.lo === 0 && this.hi === 0;
153
+ }
154
+
155
+ nonEmpty(): boolean {
156
+ return this.lo !== 0 || this.hi !== 0;
157
+ }
158
+
159
+ has(square: Square): boolean {
160
+ return (square >= 32 ? this.hi & (1 << (square - 32)) : this.lo & (1 << square)) !== 0;
161
+ }
162
+
163
+ set(square: Square, on: boolean): SquareSet {
164
+ return on ? this.with(square) : this.without(square);
165
+ }
166
+
167
+ with(square: Square): SquareSet {
168
+ return square >= 32
169
+ ? new SquareSet(this.lo, this.hi | (1 << (square - 32)))
170
+ : new SquareSet(this.lo | (1 << square), this.hi);
171
+ }
172
+
173
+ without(square: Square): SquareSet {
174
+ return square >= 32
175
+ ? new SquareSet(this.lo, this.hi & ~(1 << (square - 32)))
176
+ : new SquareSet(this.lo & ~(1 << square), this.hi);
177
+ }
178
+
179
+ toggle(square: Square): SquareSet {
180
+ return square >= 32
181
+ ? new SquareSet(this.lo, this.hi ^ (1 << (square - 32)))
182
+ : new SquareSet(this.lo ^ (1 << square), this.hi);
183
+ }
184
+
185
+ last(): Square | undefined {
186
+ if (this.hi !== 0) return 63 - Math.clz32(this.hi);
187
+ if (this.lo !== 0) return 31 - Math.clz32(this.lo);
188
+ return;
189
+ }
190
+
191
+ first(): Square | undefined {
192
+ if (this.lo !== 0) return 31 - Math.clz32(this.lo & -this.lo);
193
+ if (this.hi !== 0) return 63 - Math.clz32(this.hi & -this.hi);
194
+ return;
195
+ }
196
+
197
+ withoutFirst(): SquareSet {
198
+ if (this.lo !== 0) return new SquareSet(this.lo & (this.lo - 1), this.hi);
199
+ return new SquareSet(0, this.hi & (this.hi - 1));
200
+ }
201
+
202
+ moreThanOne(): boolean {
203
+ return (
204
+ (this.hi !== 0 && this.lo !== 0) ||
205
+ (this.lo & (this.lo - 1)) !== 0 ||
206
+ (this.hi & (this.hi - 1)) !== 0
207
+ );
208
+ }
209
+
210
+ singleSquare(): Square | undefined {
211
+ return this.moreThanOne() ? undefined : this.last();
212
+ }
213
+
214
+ *[Symbol.iterator](): Iterator<Square> {
215
+ let lo = this.lo;
216
+ let hi = this.hi;
217
+ while (lo !== 0) {
218
+ const idx = 31 - Math.clz32(lo & -lo);
219
+ lo ^= 1 << idx;
220
+ yield idx;
221
+ }
222
+ while (hi !== 0) {
223
+ const idx = 31 - Math.clz32(hi & -hi);
224
+ hi ^= 1 << idx;
225
+ yield 32 + idx;
226
+ }
227
+ }
228
+
229
+ *reversed(): Iterable<Square> {
230
+ let lo = this.lo;
231
+ let hi = this.hi;
232
+ while (hi !== 0) {
233
+ const idx = 31 - Math.clz32(hi);
234
+ hi ^= 1 << idx;
235
+ yield 32 + idx;
236
+ }
237
+ while (lo !== 0) {
238
+ const idx = 31 - Math.clz32(lo);
239
+ lo ^= 1 << idx;
240
+ yield idx;
241
+ }
242
+ }
243
+ }
@@ -0,0 +1,49 @@
1
+ import { Board } from "./board.js";
2
+ import { Setup } from "./setup.js";
3
+ import { SquareSet } from "./squareSet.js";
4
+ import { COLORS, ROLES } from "./types.js";
5
+ import { defined } from "./util.js";
6
+
7
+ export const flipVertical = (s: SquareSet): SquareSet => s.bswap64();
8
+
9
+ export const flipHorizontal = (s: SquareSet): SquareSet => {
10
+ const k1 = new SquareSet(0x55555555, 0x55555555);
11
+ const k2 = new SquareSet(0x33333333, 0x33333333);
12
+ const k4 = new SquareSet(0x0f0f0f0f, 0x0f0f0f0f);
13
+ s = s.shr64(1).intersect(k1).union(s.intersect(k1).shl64(1));
14
+ s = s.shr64(2).intersect(k2).union(s.intersect(k2).shl64(2));
15
+ s = s.shr64(4).intersect(k4).union(s.intersect(k4).shl64(4));
16
+ return s;
17
+ };
18
+
19
+ export const flipDiagonal = (s: SquareSet): SquareSet => {
20
+ let t = s.xor(s.shl64(28)).intersect(new SquareSet(0, 0x0f0f0f0f));
21
+ s = s.xor(t.xor(t.shr64(28)));
22
+ t = s.xor(s.shl64(14)).intersect(new SquareSet(0x33330000, 0x33330000));
23
+ s = s.xor(t.xor(t.shr64(14)));
24
+ t = s.xor(s.shl64(7)).intersect(new SquareSet(0x55005500, 0x55005500));
25
+ s = s.xor(t.xor(t.shr64(7)));
26
+ return s;
27
+ };
28
+
29
+ export const rotate180 = (s: SquareSet): SquareSet => s.rbit64();
30
+
31
+ export const transformBoard = (board: Board, f: (s: SquareSet) => SquareSet): Board => {
32
+ const b = Board.empty();
33
+ b.occupied = f(board.occupied);
34
+ b.promoted = f(board.promoted);
35
+ for (const color of COLORS) b[color] = f(board[color]);
36
+ for (const role of ROLES) b[role] = f(board[role]);
37
+ return b;
38
+ };
39
+
40
+ export const transformSetup = (setup: Setup, f: (s: SquareSet) => SquareSet): Setup => ({
41
+ board: transformBoard(setup.board, f),
42
+ pockets: setup.pockets?.clone(),
43
+ turn: setup.turn,
44
+ castlingRights: f(setup.castlingRights),
45
+ epSquare: defined(setup.epSquare) ? f(SquareSet.fromSquare(setup.epSquare)).first() : undefined,
46
+ remainingChecks: setup.remainingChecks?.clone(),
47
+ halfmoves: setup.halfmoves,
48
+ fullmoves: setup.fullmoves,
49
+ });