@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,812 @@
1
+ import { Result } from "@badrap/result";
2
+ import { between, kingAttacks, pawnAttacks } from "./attacks.js";
3
+ import { Board } from "./board.js";
4
+ import { Castles, castlingSide, Chess, equalsIgnoreMoves, IllegalSetup, isImpossibleCheck, isStandardMaterialSide, normalizeMove, Position, PositionError, pseudoDests, } from "./chess.js";
5
+ import { Material, MaterialSide, RemainingChecks } from "./setup.js";
6
+ import { SquareSet } from "./squareSet.js";
7
+ import { COLORS } from "./types.js";
8
+ import { defined, opposite } from "./util.js";
9
+ export { Castles, castlingSide, Chess, equalsIgnoreMoves, IllegalSetup, isImpossibleCheck, normalizeMove, Position, PositionError, };
10
+ export class Crazyhouse extends Position {
11
+ constructor() {
12
+ super("crazyhouse");
13
+ }
14
+ reset() {
15
+ super.reset();
16
+ this.pockets = Material.empty();
17
+ }
18
+ setupUnchecked(setup) {
19
+ super.setupUnchecked(setup);
20
+ this.board.promoted = setup.board.promoted
21
+ .intersect(setup.board.occupied)
22
+ .diff(setup.board.king)
23
+ .diff(setup.board.pawn);
24
+ this.pockets = setup.pockets ? setup.pockets.clone() : Material.empty();
25
+ }
26
+ static default() {
27
+ const pos = new this();
28
+ pos.reset();
29
+ return pos;
30
+ }
31
+ static fromSetup(setup) {
32
+ const pos = new this();
33
+ pos.setupUnchecked(setup);
34
+ return pos.validate().map((_) => pos);
35
+ }
36
+ clone() {
37
+ return super.clone();
38
+ }
39
+ validate() {
40
+ return super.validate().chain((_) => {
41
+ var _a, _b;
42
+ if ((_a = this.pockets) === null || _a === void 0 ? void 0 : _a.count("king")) {
43
+ return Result.err(new PositionError(IllegalSetup.Kings));
44
+ }
45
+ if ((((_b = this.pockets) === null || _b === void 0 ? void 0 : _b.size()) || 0) + this.board.occupied.size() > 64) {
46
+ return Result.err(new PositionError(IllegalSetup.Variant));
47
+ }
48
+ return Result.ok(undefined);
49
+ });
50
+ }
51
+ hasInsufficientMaterial(color) {
52
+ // No material can leave the game, but we can easily check this for
53
+ // custom positions.
54
+ if (!this.pockets)
55
+ return super.hasInsufficientMaterial(color);
56
+ return (this.board.occupied.size() + this.pockets.size() <= 3 &&
57
+ this.board.pawn.isEmpty() &&
58
+ this.board.promoted.isEmpty() &&
59
+ this.board.rooksAndQueens().isEmpty() &&
60
+ this.pockets.count("pawn") <= 0 &&
61
+ this.pockets.count("rook") <= 0 &&
62
+ this.pockets.count("queen") <= 0);
63
+ }
64
+ dropDests(ctx) {
65
+ var _a, _b;
66
+ const mask = this.board.occupied
67
+ .complement()
68
+ .intersect(((_a = this.pockets) === null || _a === void 0 ? void 0 : _a[this.turn].hasNonPawns())
69
+ ? SquareSet.full()
70
+ : ((_b = this.pockets) === null || _b === void 0 ? void 0 : _b[this.turn].hasPawns())
71
+ ? SquareSet.backranks().complement()
72
+ : SquareSet.empty());
73
+ ctx = ctx || this.ctx();
74
+ if (defined(ctx.king) && ctx.checkers.nonEmpty()) {
75
+ const checker = ctx.checkers.singleSquare();
76
+ if (!defined(checker))
77
+ return SquareSet.empty();
78
+ return mask.intersect(between(checker, ctx.king));
79
+ }
80
+ else
81
+ return mask;
82
+ }
83
+ }
84
+ export class Atomic extends Position {
85
+ constructor() {
86
+ super("atomic");
87
+ }
88
+ static default() {
89
+ const pos = new this();
90
+ pos.reset();
91
+ return pos;
92
+ }
93
+ static fromSetup(setup) {
94
+ const pos = new this();
95
+ pos.setupUnchecked(setup);
96
+ return pos.validate().map((_) => pos);
97
+ }
98
+ clone() {
99
+ return super.clone();
100
+ }
101
+ validate() {
102
+ // Like chess, but allow our king to be missing.
103
+ if (this.board.occupied.isEmpty())
104
+ return Result.err(new PositionError(IllegalSetup.Empty));
105
+ if (this.board.king.size() > 2)
106
+ return Result.err(new PositionError(IllegalSetup.Kings));
107
+ const otherKing = this.board.kingOf(opposite(this.turn));
108
+ if (!defined(otherKing))
109
+ return Result.err(new PositionError(IllegalSetup.Kings));
110
+ if (this.kingAttackers(otherKing, this.turn, this.board.occupied).nonEmpty()) {
111
+ return Result.err(new PositionError(IllegalSetup.OppositeCheck));
112
+ }
113
+ if (SquareSet.backranks().intersects(this.board.pawn)) {
114
+ return Result.err(new PositionError(IllegalSetup.PawnsOnBackrank));
115
+ }
116
+ return Result.ok(undefined);
117
+ }
118
+ kingAttackers(square, attacker, occupied) {
119
+ const attackerKings = this.board.pieces(attacker, "king");
120
+ if (attackerKings.isEmpty() || kingAttacks(square).intersects(attackerKings)) {
121
+ return SquareSet.empty();
122
+ }
123
+ return super.kingAttackers(square, attacker, occupied);
124
+ }
125
+ playCaptureAt(square, captured) {
126
+ super.playCaptureAt(square, captured);
127
+ this.board.take(square);
128
+ for (const explode of kingAttacks(square)
129
+ .intersect(this.board.occupied)
130
+ .diff(this.board.pawn)) {
131
+ const piece = this.board.take(explode);
132
+ if ((piece === null || piece === void 0 ? void 0 : piece.role) === "rook")
133
+ this.castles.discardRook(explode);
134
+ if ((piece === null || piece === void 0 ? void 0 : piece.role) === "king")
135
+ this.castles.discardColor(piece.color);
136
+ }
137
+ }
138
+ hasInsufficientMaterial(color) {
139
+ // Remaining material does not matter if the enemy king is already
140
+ // exploded.
141
+ if (this.board.pieces(opposite(color), "king").isEmpty())
142
+ return false;
143
+ // Bare king cannot mate.
144
+ if (this.board[color].diff(this.board.king).isEmpty())
145
+ return true;
146
+ // As long as the enemy king is not alone, there is always a chance their
147
+ // own pieces explode next to it.
148
+ if (this.board[opposite(color)].diff(this.board.king).nonEmpty()) {
149
+ // Unless there are only bishops that cannot explode each other.
150
+ if (this.board.occupied.equals(this.board.bishop.union(this.board.king))) {
151
+ if (!this.board.bishop.intersect(this.board.white).intersects(SquareSet.darkSquares())) {
152
+ return !this.board.bishop
153
+ .intersect(this.board.black)
154
+ .intersects(SquareSet.lightSquares());
155
+ }
156
+ if (!this.board.bishop.intersect(this.board.white).intersects(SquareSet.lightSquares())) {
157
+ return !this.board.bishop.intersect(this.board.black).intersects(SquareSet.darkSquares());
158
+ }
159
+ }
160
+ return false;
161
+ }
162
+ // Queen or pawn (future queen) can give mate against bare king.
163
+ if (this.board.queen.nonEmpty() || this.board.pawn.nonEmpty())
164
+ return false;
165
+ // Single knight, bishop or rook cannot mate against bare king.
166
+ if (this.board.knight.union(this.board.bishop).union(this.board.rook).size() === 1)
167
+ return true;
168
+ // If only knights, more than two are required to mate bare king.
169
+ if (this.board.occupied.equals(this.board.knight.union(this.board.king))) {
170
+ return this.board.knight.size() <= 2;
171
+ }
172
+ return false;
173
+ }
174
+ dests(square, ctx) {
175
+ ctx = ctx || this.ctx();
176
+ let dests = SquareSet.empty();
177
+ for (const to of pseudoDests(this, square, ctx)) {
178
+ const after = this.clone();
179
+ after.play({ from: square, to });
180
+ const ourKing = after.board.kingOf(this.turn);
181
+ if (defined(ourKing) &&
182
+ (!defined(after.board.kingOf(after.turn)) ||
183
+ after.kingAttackers(ourKing, after.turn, after.board.occupied).isEmpty())) {
184
+ dests = dests.with(to);
185
+ }
186
+ }
187
+ return dests;
188
+ }
189
+ isVariantEnd() {
190
+ return !!this.variantOutcome();
191
+ }
192
+ variantOutcome(_ctx) {
193
+ for (const color of COLORS) {
194
+ if (this.board.pieces(color, "king").isEmpty())
195
+ return { winner: opposite(color) };
196
+ }
197
+ return;
198
+ }
199
+ }
200
+ export class Antichess extends Position {
201
+ constructor() {
202
+ super("antichess");
203
+ }
204
+ reset() {
205
+ super.reset();
206
+ this.castles = Castles.empty();
207
+ }
208
+ setupUnchecked(setup) {
209
+ super.setupUnchecked(setup);
210
+ this.castles = Castles.empty();
211
+ }
212
+ static default() {
213
+ const pos = new this();
214
+ pos.reset();
215
+ return pos;
216
+ }
217
+ static fromSetup(setup) {
218
+ const pos = new this();
219
+ pos.setupUnchecked(setup);
220
+ return pos.validate().map((_) => pos);
221
+ }
222
+ clone() {
223
+ return super.clone();
224
+ }
225
+ validate() {
226
+ if (this.board.occupied.isEmpty())
227
+ return Result.err(new PositionError(IllegalSetup.Empty));
228
+ if (SquareSet.backranks().intersects(this.board.pawn)) {
229
+ return Result.err(new PositionError(IllegalSetup.PawnsOnBackrank));
230
+ }
231
+ return Result.ok(undefined);
232
+ }
233
+ kingAttackers(_square, _attacker, _occupied) {
234
+ return SquareSet.empty();
235
+ }
236
+ ctx() {
237
+ const ctx = super.ctx();
238
+ if (defined(this.epSquare) &&
239
+ pawnAttacks(opposite(this.turn), this.epSquare).intersects(this.board.pieces(this.turn, "pawn"))) {
240
+ ctx.mustCapture = true;
241
+ return ctx;
242
+ }
243
+ const enemy = this.board[opposite(this.turn)];
244
+ for (const from of this.board[this.turn]) {
245
+ if (pseudoDests(this, from, ctx).intersects(enemy)) {
246
+ ctx.mustCapture = true;
247
+ return ctx;
248
+ }
249
+ }
250
+ return ctx;
251
+ }
252
+ dests(square, ctx) {
253
+ ctx = ctx || this.ctx();
254
+ const dests = pseudoDests(this, square, ctx);
255
+ const enemy = this.board[opposite(this.turn)];
256
+ return dests.intersect(ctx.mustCapture
257
+ ? defined(this.epSquare) && this.board.getRole(square) === "pawn"
258
+ ? enemy.with(this.epSquare)
259
+ : enemy
260
+ : SquareSet.full());
261
+ }
262
+ hasInsufficientMaterial(color) {
263
+ if (this.board[color].isEmpty())
264
+ return false;
265
+ if (this.board[opposite(color)].isEmpty())
266
+ return true;
267
+ if (this.board.occupied.equals(this.board.bishop)) {
268
+ const weSomeOnLight = this.board[color].intersects(SquareSet.lightSquares());
269
+ const weSomeOnDark = this.board[color].intersects(SquareSet.darkSquares());
270
+ const theyAllOnDark = this.board[opposite(color)].isDisjoint(SquareSet.lightSquares());
271
+ const theyAllOnLight = this.board[opposite(color)].isDisjoint(SquareSet.darkSquares());
272
+ return (weSomeOnLight && theyAllOnDark) || (weSomeOnDark && theyAllOnLight);
273
+ }
274
+ if (this.board.occupied.equals(this.board.knight) && this.board.occupied.size() === 2) {
275
+ return ((this.board.white.intersects(SquareSet.lightSquares()) !==
276
+ this.board.black.intersects(SquareSet.darkSquares())) !==
277
+ (this.turn === color));
278
+ }
279
+ return false;
280
+ }
281
+ isVariantEnd() {
282
+ return this.board[this.turn].isEmpty();
283
+ }
284
+ variantOutcome(ctx) {
285
+ ctx = ctx || this.ctx();
286
+ if (ctx.variantEnd || this.isStalemate(ctx)) {
287
+ return { winner: this.turn };
288
+ }
289
+ return;
290
+ }
291
+ }
292
+ export class KingOfTheHill extends Position {
293
+ constructor() {
294
+ super("kingofthehill");
295
+ }
296
+ static default() {
297
+ const pos = new this();
298
+ pos.reset();
299
+ return pos;
300
+ }
301
+ static fromSetup(setup) {
302
+ const pos = new this();
303
+ pos.setupUnchecked(setup);
304
+ return pos.validate().map((_) => pos);
305
+ }
306
+ clone() {
307
+ return super.clone();
308
+ }
309
+ hasInsufficientMaterial(_color) {
310
+ return false;
311
+ }
312
+ isVariantEnd() {
313
+ return this.board.king.intersects(SquareSet.center());
314
+ }
315
+ variantOutcome(_ctx) {
316
+ for (const color of COLORS) {
317
+ if (this.board.pieces(color, "king").intersects(SquareSet.center()))
318
+ return { winner: color };
319
+ }
320
+ return;
321
+ }
322
+ }
323
+ export class ThreeCheck extends Position {
324
+ constructor() {
325
+ super("3check");
326
+ }
327
+ reset() {
328
+ super.reset();
329
+ this.remainingChecks = RemainingChecks.default();
330
+ }
331
+ setupUnchecked(setup) {
332
+ var _a;
333
+ super.setupUnchecked(setup);
334
+ this.remainingChecks = ((_a = setup.remainingChecks) === null || _a === void 0 ? void 0 : _a.clone()) || RemainingChecks.default();
335
+ }
336
+ static default() {
337
+ const pos = new this();
338
+ pos.reset();
339
+ return pos;
340
+ }
341
+ static fromSetup(setup) {
342
+ const pos = new this();
343
+ pos.setupUnchecked(setup);
344
+ return pos.validate().map((_) => pos);
345
+ }
346
+ clone() {
347
+ return super.clone();
348
+ }
349
+ hasInsufficientMaterial(color) {
350
+ return this.board.pieces(color, "king").equals(this.board[color]);
351
+ }
352
+ isVariantEnd() {
353
+ return (!!this.remainingChecks && (this.remainingChecks.white <= 0 || this.remainingChecks.black <= 0));
354
+ }
355
+ variantOutcome(_ctx) {
356
+ if (this.remainingChecks) {
357
+ for (const color of COLORS) {
358
+ if (this.remainingChecks[color] <= 0)
359
+ return { winner: color };
360
+ }
361
+ }
362
+ return;
363
+ }
364
+ }
365
+ const racingKingsBoard = () => {
366
+ const board = Board.empty();
367
+ board.occupied = new SquareSet(0xffff, 0);
368
+ board.promoted = SquareSet.empty();
369
+ board.white = new SquareSet(0xf0f0, 0);
370
+ board.black = new SquareSet(0x0f0f, 0);
371
+ board.pawn = SquareSet.empty();
372
+ board.knight = new SquareSet(0x1818, 0);
373
+ board.bishop = new SquareSet(0x2424, 0);
374
+ board.rook = new SquareSet(0x4242, 0);
375
+ board.queen = new SquareSet(0x0081, 0);
376
+ board.king = new SquareSet(0x8100, 0);
377
+ return board;
378
+ };
379
+ export class RacingKings extends Position {
380
+ constructor() {
381
+ super("racingkings");
382
+ }
383
+ reset() {
384
+ this.board = racingKingsBoard();
385
+ this.pockets = undefined;
386
+ this.turn = "white";
387
+ this.castles = Castles.empty();
388
+ this.epSquare = undefined;
389
+ this.remainingChecks = undefined;
390
+ this.halfmoves = 0;
391
+ this.fullmoves = 1;
392
+ }
393
+ setupUnchecked(setup) {
394
+ super.setupUnchecked(setup);
395
+ this.castles = Castles.empty();
396
+ }
397
+ static default() {
398
+ const pos = new this();
399
+ pos.reset();
400
+ return pos;
401
+ }
402
+ static fromSetup(setup) {
403
+ const pos = new this();
404
+ pos.setupUnchecked(setup);
405
+ return pos.validate().map((_) => pos);
406
+ }
407
+ clone() {
408
+ return super.clone();
409
+ }
410
+ validate() {
411
+ if (this.isCheck() || this.board.pawn.nonEmpty())
412
+ return Result.err(new PositionError(IllegalSetup.Variant));
413
+ return super.validate();
414
+ }
415
+ dests(square, ctx) {
416
+ ctx = ctx || this.ctx();
417
+ // Kings cannot give check.
418
+ if (square === ctx.king)
419
+ return super.dests(square, ctx);
420
+ // Do not allow giving check.
421
+ let dests = SquareSet.empty();
422
+ for (const to of super.dests(square, ctx)) {
423
+ // Valid, because there are no promotions (or even pawns).
424
+ const move = { from: square, to };
425
+ const after = this.clone();
426
+ after.play(move);
427
+ if (!after.isCheck())
428
+ dests = dests.with(to);
429
+ }
430
+ return dests;
431
+ }
432
+ hasInsufficientMaterial(_color) {
433
+ return false;
434
+ }
435
+ isVariantEnd() {
436
+ const goal = SquareSet.fromRank(7);
437
+ const inGoal = this.board.king.intersect(goal);
438
+ if (inGoal.isEmpty())
439
+ return false;
440
+ if (this.turn === "white" || inGoal.intersects(this.board.black))
441
+ return true;
442
+ // White has reached the backrank. Check if black can catch up.
443
+ const blackKing = this.board.kingOf("black");
444
+ if (defined(blackKing)) {
445
+ const occ = this.board.occupied.without(blackKing);
446
+ for (const target of kingAttacks(blackKing).intersect(goal).diff(this.board.black)) {
447
+ if (this.kingAttackers(target, "white", occ).isEmpty())
448
+ return false;
449
+ }
450
+ }
451
+ return true;
452
+ }
453
+ variantOutcome(ctx) {
454
+ if (ctx ? !ctx.variantEnd : !this.isVariantEnd())
455
+ return;
456
+ const goal = SquareSet.fromRank(7);
457
+ const blackInGoal = this.board.pieces("black", "king").intersects(goal);
458
+ const whiteInGoal = this.board.pieces("white", "king").intersects(goal);
459
+ if (blackInGoal && !whiteInGoal)
460
+ return { winner: "black" };
461
+ if (whiteInGoal && !blackInGoal)
462
+ return { winner: "white" };
463
+ return { winner: undefined };
464
+ }
465
+ }
466
+ const hordeBoard = () => {
467
+ const board = Board.empty();
468
+ board.occupied = new SquareSet(4294967295, 4294901862);
469
+ board.promoted = SquareSet.empty();
470
+ board.white = new SquareSet(4294967295, 102);
471
+ board.black = new SquareSet(0, 4294901760);
472
+ board.pawn = new SquareSet(4294967295, 16711782);
473
+ board.knight = new SquareSet(0, 1107296256);
474
+ board.bishop = new SquareSet(0, 603979776);
475
+ board.rook = new SquareSet(0, 2164260864);
476
+ board.queen = new SquareSet(0, 134217728);
477
+ board.king = new SquareSet(0, 268435456);
478
+ return board;
479
+ };
480
+ export class Horde extends Position {
481
+ constructor() {
482
+ super("horde");
483
+ }
484
+ reset() {
485
+ this.board = hordeBoard();
486
+ this.pockets = undefined;
487
+ this.turn = "white";
488
+ this.castles = Castles.default();
489
+ this.castles.discardColor("white");
490
+ this.epSquare = undefined;
491
+ this.remainingChecks = undefined;
492
+ this.halfmoves = 0;
493
+ this.fullmoves = 1;
494
+ }
495
+ static default() {
496
+ const pos = new this();
497
+ pos.reset();
498
+ return pos;
499
+ }
500
+ static fromSetup(setup) {
501
+ const pos = new this();
502
+ pos.setupUnchecked(setup);
503
+ return pos.validate().map((_) => pos);
504
+ }
505
+ clone() {
506
+ return super.clone();
507
+ }
508
+ validate() {
509
+ if (this.board.occupied.isEmpty())
510
+ return Result.err(new PositionError(IllegalSetup.Empty));
511
+ if (this.board.king.size() !== 1)
512
+ return Result.err(new PositionError(IllegalSetup.Kings));
513
+ const otherKing = this.board.kingOf(opposite(this.turn));
514
+ if (defined(otherKing) &&
515
+ this.kingAttackers(otherKing, this.turn, this.board.occupied).nonEmpty()) {
516
+ return Result.err(new PositionError(IllegalSetup.OppositeCheck));
517
+ }
518
+ for (const color of COLORS) {
519
+ const backranks = this.board.pieces(color, "king").isEmpty()
520
+ ? SquareSet.backrank(opposite(color))
521
+ : SquareSet.backranks();
522
+ if (this.board.pieces(color, "pawn").intersects(backranks)) {
523
+ return Result.err(new PositionError(IllegalSetup.PawnsOnBackrank));
524
+ }
525
+ }
526
+ return Result.ok(undefined);
527
+ }
528
+ hasInsufficientMaterial(color) {
529
+ // The side with the king can always win by capturing the horde.
530
+ if (this.board.pieces(color, "king").nonEmpty())
531
+ return false;
532
+ const oppositeSquareColor = (squareColor) => squareColor === "light" ? "dark" : "light";
533
+ const coloredSquares = (squareColor) => squareColor === "light" ? SquareSet.lightSquares() : SquareSet.darkSquares();
534
+ const hasBishopPair = (side) => {
535
+ const bishops = this.board.pieces(side, "bishop");
536
+ return (bishops.intersects(SquareSet.darkSquares()) && bishops.intersects(SquareSet.lightSquares()));
537
+ };
538
+ // By this point: color is the horde.
539
+ // Based on
540
+ // https://github.com/stevepapazis/horde-insufficient-material-tests.
541
+ const horde = MaterialSide.fromBoard(this.board, color);
542
+ const hordeBishops = (squareColor) => coloredSquares(squareColor).intersect(this.board.pieces(color, "bishop")).size();
543
+ const hordeBishopColor = hordeBishops("light") >= 1 ? "light" : "dark";
544
+ const hordeNum = horde.pawn +
545
+ horde.knight +
546
+ horde.rook +
547
+ horde.queen +
548
+ Math.min(hordeBishops("dark"), 2) +
549
+ Math.min(hordeBishops("light"), 2);
550
+ const pieces = MaterialSide.fromBoard(this.board, opposite(color));
551
+ const piecesBishops = (squareColor) => coloredSquares(squareColor)
552
+ .intersect(this.board.pieces(opposite(color), "bishop"))
553
+ .size();
554
+ const piecesNum = pieces.size();
555
+ const piecesOfRoleNot = (piece) => piecesNum - piece;
556
+ if (hordeNum === 0)
557
+ return true;
558
+ if (hordeNum >= 4) {
559
+ // Four or more pieces can always deliver mate.
560
+ return false;
561
+ }
562
+ if ((horde.pawn >= 1 || horde.queen >= 1) && hordeNum >= 2) {
563
+ // Pawns/queens are never insufficient material when paired with any other
564
+ // piece (a pawn promotes to a queen and delivers mate).
565
+ return false;
566
+ }
567
+ if (horde.rook >= 1 && hordeNum >= 2) {
568
+ // A rook is insufficient material only when it is paired with a bishop
569
+ // against a lone king. The horde can mate in any other case.
570
+ // A rook on A1 and a bishop on C3 mate a king on B1 when there is a
571
+ // friendly pawn/opposite-color-bishop/rook/queen on C2.
572
+ // A rook on B8 and a bishop C3 mate a king on A1 when there is a friendly
573
+ // knight on A2.
574
+ if (!(hordeNum === 2 &&
575
+ horde.rook === 1 &&
576
+ horde.bishop === 1 &&
577
+ piecesOfRoleNot(piecesBishops(hordeBishopColor)) === 1)) {
578
+ return false;
579
+ }
580
+ }
581
+ if (hordeNum === 1) {
582
+ if (piecesNum === 1) {
583
+ // A lone piece cannot mate a lone king.
584
+ return true;
585
+ }
586
+ else if (horde.queen === 1) {
587
+ // The horde has a lone queen.
588
+ // A lone queen mates a king on A1 bounded by:
589
+ // -- a pawn/rook on A2
590
+ // -- two same color bishops on A2, B1
591
+ // We ignore every other mating case, since it can be reduced to
592
+ // the two previous cases (e.g. a black pawn on A2 and a black
593
+ // bishop on B1).
594
+ return !(pieces.pawn >= 1 ||
595
+ pieces.rook >= 1 ||
596
+ piecesBishops("light") >= 2 ||
597
+ piecesBishops("dark") >= 2);
598
+ }
599
+ else if (horde.pawn === 1) {
600
+ // Promote the pawn to a queen or a knight and check whether white
601
+ // can mate.
602
+ const pawnSquare = this.board.pieces(color, "pawn").last();
603
+ const promoteToQueen = this.clone();
604
+ promoteToQueen.board.set(pawnSquare, { color, role: "queen" });
605
+ const promoteToKnight = this.clone();
606
+ promoteToKnight.board.set(pawnSquare, { color, role: "knight" });
607
+ return (promoteToQueen.hasInsufficientMaterial(color) &&
608
+ promoteToKnight.hasInsufficientMaterial(color));
609
+ }
610
+ else if (horde.rook === 1) {
611
+ // A lone rook mates a king on A8 bounded by a pawn/rook on A7 and a
612
+ // pawn/knight on B7. We ignore every other case, since it can be
613
+ // reduced to the two previous cases.
614
+ // (e.g. three pawns on A7, B7, C7)
615
+ return !(pieces.pawn >= 2 ||
616
+ (pieces.rook >= 1 && pieces.pawn >= 1) ||
617
+ (pieces.rook >= 1 && pieces.knight >= 1) ||
618
+ (pieces.pawn >= 1 && pieces.knight >= 1));
619
+ }
620
+ else if (horde.bishop === 1) {
621
+ // The horde has a lone bishop.
622
+ return !(
623
+ // The king can be mated on A1 if there is a pawn/opposite-color-bishop
624
+ // on A2 and an opposite-color-bishop on B1.
625
+ // If black has two or more pawns, white gets the benefit of the doubt;
626
+ // there is an outside chance that white promotes its pawns to
627
+ // opposite-color-bishops and selfmates theirself.
628
+ // Every other case that the king is mated by the bishop requires that
629
+ // black has two pawns or two opposite-color-bishop or a pawn and an
630
+ // opposite-color-bishop.
631
+ // For example a king on A3 can be mated if there is
632
+ // a pawn/opposite-color-bishop on A4, a pawn/opposite-color-bishop on
633
+ // B3, a pawn/bishop/rook/queen on A2 and any other piece on B2.
634
+ (piecesBishops(oppositeSquareColor(hordeBishopColor)) >= 2 ||
635
+ (piecesBishops(oppositeSquareColor(hordeBishopColor)) >= 1 && pieces.pawn >= 1) ||
636
+ pieces.pawn >= 2));
637
+ }
638
+ else if (horde.knight === 1) {
639
+ // The horde has a lone knight.
640
+ return !(
641
+ // The king on A1 can be smother mated by a knight on C2 if there is
642
+ // a pawn/knight/bishop on B2, a knight/rook on B1 and any other piece
643
+ // on A2.
644
+ // Moreover, when black has four or more pieces and two of them are
645
+ // pawns, black can promote their pawns and selfmate theirself.
646
+ (piecesNum >= 4 &&
647
+ (pieces.knight >= 2 ||
648
+ pieces.pawn >= 2 ||
649
+ (pieces.rook >= 1 && pieces.knight >= 1) ||
650
+ (pieces.rook >= 1 && pieces.bishop >= 1) ||
651
+ (pieces.knight >= 1 && pieces.bishop >= 1) ||
652
+ (pieces.rook >= 1 && pieces.pawn >= 1) ||
653
+ (pieces.knight >= 1 && pieces.pawn >= 1) ||
654
+ (pieces.bishop >= 1 && pieces.pawn >= 1) ||
655
+ (hasBishopPair(opposite(color)) && pieces.pawn >= 1)) &&
656
+ (piecesBishops("dark") < 2 || piecesOfRoleNot(piecesBishops("dark")) >= 3) &&
657
+ (piecesBishops("light") < 2 || piecesOfRoleNot(piecesBishops("light")) >= 3)));
658
+ }
659
+ // By this point, we only need to deal with white's minor pieces.
660
+ }
661
+ else if (hordeNum === 2) {
662
+ if (piecesNum === 1) {
663
+ // Two minor pieces cannot mate a lone king.
664
+ return true;
665
+ }
666
+ else if (horde.knight === 2) {
667
+ // A king on A1 is mated by two knights, if it is obstructed by a
668
+ // pawn/bishop/knight on B2. On the other hand, if black only has
669
+ // major pieces it is a draw.
670
+ return pieces.pawn + pieces.bishop + pieces.knight < 1;
671
+ }
672
+ else if (hasBishopPair(color)) {
673
+ return !(
674
+ // A king on A1 obstructed by a pawn/bishop on A2 is mated
675
+ // by the bishop pair.
676
+ (pieces.pawn >= 1 ||
677
+ pieces.bishop >= 1 ||
678
+ // A pawn/bishop/knight on B4, a pawn/bishop/rook/queen on
679
+ // A4 and the king on A3 enable Boden's mate by the bishop
680
+ // pair. In every other case white cannot win.
681
+ (pieces.knight >= 1 && pieces.rook + pieces.queen >= 1)));
682
+ }
683
+ else if (horde.bishop >= 1 && horde.knight >= 1) {
684
+ // The horde has a bishop and a knight.
685
+ return !(
686
+ // A king on A1 obstructed by a pawn/opposite-color-bishop on
687
+ // A2 is mated by a knight on D2 and a bishop on C3.
688
+ (pieces.pawn >= 1 ||
689
+ piecesBishops(oppositeSquareColor(hordeBishopColor)) >= 1 ||
690
+ // A king on A1 bounded by two friendly pieces on A2 and B1 is
691
+ // mated when the knight moves from D4 to C2 so that both the
692
+ // knight and the bishop deliver check.
693
+ piecesOfRoleNot(piecesBishops(hordeBishopColor)) >= 3));
694
+ }
695
+ else {
696
+ // The horde has two or more bishops on the same color.
697
+ // White can only win if black has enough material to obstruct
698
+ // the squares of the opposite color around the king.
699
+ return !(
700
+ // A king on A1 obstructed by a pawn/opposite-bishop/knight
701
+ // on A2 and a opposite-bishop/knight on B1 is mated by two
702
+ // bishops on B2 and C3. This position is theoretically
703
+ // achievable even when black has two pawns or when they
704
+ // have a pawn and an opposite color bishop.
705
+ ((pieces.pawn >= 1 && piecesBishops(oppositeSquareColor(hordeBishopColor)) >= 1) ||
706
+ (pieces.pawn >= 1 && pieces.knight >= 1) ||
707
+ (piecesBishops(oppositeSquareColor(hordeBishopColor)) >= 1 && pieces.knight >= 1) ||
708
+ piecesBishops(oppositeSquareColor(hordeBishopColor)) >= 2 ||
709
+ pieces.knight >= 2 ||
710
+ pieces.pawn >= 2)
711
+ // In every other case, white can only draw.
712
+ );
713
+ }
714
+ }
715
+ else if (hordeNum === 3) {
716
+ // A king in the corner is mated by two knights and a bishop or three
717
+ // knights or the bishop pair and a knight/bishop.
718
+ if ((horde.knight === 2 && horde.bishop === 1) ||
719
+ horde.knight === 3 ||
720
+ hasBishopPair(color)) {
721
+ return false;
722
+ }
723
+ else {
724
+ // White has two same color bishops and a knight.
725
+ // A king on A1 is mated by a bishop on B2, a bishop on C1 and a
726
+ // knight on C3, as long as there is another black piece to waste
727
+ // a tempo.
728
+ return piecesNum === 1;
729
+ }
730
+ }
731
+ return true;
732
+ }
733
+ isVariantEnd() {
734
+ return this.board.white.isEmpty() || this.board.black.isEmpty();
735
+ }
736
+ variantOutcome(_ctx) {
737
+ if (this.board.white.isEmpty())
738
+ return { winner: "black" };
739
+ if (this.board.black.isEmpty())
740
+ return { winner: "white" };
741
+ return;
742
+ }
743
+ }
744
+ export const defaultPosition = (rules) => {
745
+ switch (rules) {
746
+ case "chess":
747
+ return Chess.default();
748
+ case "antichess":
749
+ return Antichess.default();
750
+ case "atomic":
751
+ return Atomic.default();
752
+ case "horde":
753
+ return Horde.default();
754
+ case "racingkings":
755
+ return RacingKings.default();
756
+ case "kingofthehill":
757
+ return KingOfTheHill.default();
758
+ case "3check":
759
+ return ThreeCheck.default();
760
+ case "crazyhouse":
761
+ return Crazyhouse.default();
762
+ }
763
+ };
764
+ export const setupPosition = (rules, setup) => {
765
+ switch (rules) {
766
+ case "chess":
767
+ return Chess.fromSetup(setup);
768
+ case "antichess":
769
+ return Antichess.fromSetup(setup);
770
+ case "atomic":
771
+ return Atomic.fromSetup(setup);
772
+ case "horde":
773
+ return Horde.fromSetup(setup);
774
+ case "racingkings":
775
+ return RacingKings.fromSetup(setup);
776
+ case "kingofthehill":
777
+ return KingOfTheHill.fromSetup(setup);
778
+ case "3check":
779
+ return ThreeCheck.fromSetup(setup);
780
+ case "crazyhouse":
781
+ return Crazyhouse.fromSetup(setup);
782
+ }
783
+ };
784
+ export const isStandardMaterial = (pos) => {
785
+ var _a, _b, _c, _d, _e;
786
+ switch (pos.rules) {
787
+ case "chess":
788
+ case "antichess":
789
+ case "atomic":
790
+ case "kingofthehill":
791
+ case "3check":
792
+ return COLORS.every((color) => isStandardMaterialSide(pos.board, color));
793
+ case "crazyhouse": {
794
+ const promoted = pos.board.promoted;
795
+ return (promoted.size() + pos.board.pawn.size() + (((_a = pos.pockets) === null || _a === void 0 ? void 0 : _a.count("pawn")) || 0) <= 16 &&
796
+ pos.board.knight.diff(promoted).size() + (((_b = pos.pockets) === null || _b === void 0 ? void 0 : _b.count("knight")) || 0) <= 4 &&
797
+ pos.board.bishop.diff(promoted).size() + (((_c = pos.pockets) === null || _c === void 0 ? void 0 : _c.count("bishop")) || 0) <= 4 &&
798
+ pos.board.rook.diff(promoted).size() + (((_d = pos.pockets) === null || _d === void 0 ? void 0 : _d.count("rook")) || 0) <= 4 &&
799
+ pos.board.queen.diff(promoted).size() + (((_e = pos.pockets) === null || _e === void 0 ? void 0 : _e.count("queen")) || 0) <= 2);
800
+ }
801
+ case "horde":
802
+ return COLORS.every((color) => pos.board.pieces(color, "king").nonEmpty()
803
+ ? isStandardMaterialSide(pos.board, color)
804
+ : pos.board[color].size() <= 36);
805
+ case "racingkings":
806
+ return COLORS.every((color) => pos.board.pieces(color, "knight").size() <= 2 &&
807
+ pos.board.pieces(color, "bishop").size() <= 2 &&
808
+ pos.board.pieces(color, "rook").size() <= 2 &&
809
+ pos.board.pieces(color, "queen").size() <= 1);
810
+ }
811
+ };
812
+ //# sourceMappingURL=variant.js.map