@datamoc/mw_games 0.19.0 → 0.22.0

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 (141) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/dist/ai/index.d.ts +2 -2
  3. package/dist/ai/index.d.ts.map +1 -1
  4. package/dist/ai/index.js +1 -1
  5. package/dist/ai/index.js.map +1 -1
  6. package/dist/ai/search.d.ts +49 -0
  7. package/dist/ai/search.d.ts.map +1 -1
  8. package/dist/ai/search.js +48 -0
  9. package/dist/ai/search.js.map +1 -1
  10. package/dist/audio/Channels.d.ts +19 -0
  11. package/dist/audio/Channels.d.ts.map +1 -1
  12. package/dist/audio/Channels.js +33 -4
  13. package/dist/audio/Channels.js.map +1 -1
  14. package/dist/audio/LoopTags.d.ts +37 -0
  15. package/dist/audio/LoopTags.d.ts.map +1 -0
  16. package/dist/audio/LoopTags.js +116 -0
  17. package/dist/audio/LoopTags.js.map +1 -0
  18. package/dist/audio/Midi.d.ts +5 -0
  19. package/dist/audio/Midi.d.ts.map +1 -1
  20. package/dist/audio/Midi.js +9 -1
  21. package/dist/audio/Midi.js.map +1 -1
  22. package/dist/audio/MidiRender.d.ts +42 -4
  23. package/dist/audio/MidiRender.d.ts.map +1 -1
  24. package/dist/audio/MidiRender.js +286 -199
  25. package/dist/audio/MidiRender.js.map +1 -1
  26. package/dist/audio/index.d.ts +5 -3
  27. package/dist/audio/index.d.ts.map +1 -1
  28. package/dist/audio/index.js +2 -1
  29. package/dist/audio/index.js.map +1 -1
  30. package/dist/board/Engine.d.ts +33 -0
  31. package/dist/board/Engine.d.ts.map +1 -1
  32. package/dist/board/Engine.js +448 -1
  33. package/dist/board/Engine.js.map +1 -1
  34. package/dist/board/bitboard.d.ts +132 -0
  35. package/dist/board/bitboard.d.ts.map +1 -0
  36. package/dist/board/bitboard.js +495 -0
  37. package/dist/board/bitboard.js.map +1 -0
  38. package/dist/board/chess.d.ts +16 -0
  39. package/dist/board/chess.d.ts.map +1 -1
  40. package/dist/board/chess.js +43 -0
  41. package/dist/board/chess.js.map +1 -1
  42. package/dist/board/index.d.ts +11 -3
  43. package/dist/board/index.d.ts.map +1 -1
  44. package/dist/board/index.js +6 -2
  45. package/dist/board/index.js.map +1 -1
  46. package/dist/board/openings.d.ts +58 -0
  47. package/dist/board/openings.d.ts.map +1 -0
  48. package/dist/board/openings.js +45 -0
  49. package/dist/board/openings.js.map +1 -0
  50. package/dist/board/tablebase.d.ts +118 -0
  51. package/dist/board/tablebase.d.ts.map +1 -0
  52. package/dist/board/tablebase.js +414 -0
  53. package/dist/board/tablebase.js.map +1 -0
  54. package/dist/board/tourney.d.ts +90 -0
  55. package/dist/board/tourney.d.ts.map +1 -0
  56. package/dist/board/tourney.js +1013 -0
  57. package/dist/board/tourney.js.map +1 -0
  58. package/dist/core/JavaRandom.d.ts +55 -0
  59. package/dist/core/JavaRandom.d.ts.map +1 -0
  60. package/dist/core/JavaRandom.js +81 -0
  61. package/dist/core/JavaRandom.js.map +1 -0
  62. package/dist/core/index.d.ts +2 -0
  63. package/dist/core/index.d.ts.map +1 -1
  64. package/dist/core/index.js +1 -0
  65. package/dist/core/index.js.map +1 -1
  66. package/dist/mw_games.global.js +123 -116
  67. package/dist/rpg/EventRunner.d.ts +7 -0
  68. package/dist/rpg/EventRunner.d.ts.map +1 -1
  69. package/dist/rpg/EventRunner.js +6 -0
  70. package/dist/rpg/EventRunner.js.map +1 -1
  71. package/dist/threads/index.d.ts +3 -0
  72. package/dist/threads/index.d.ts.map +1 -0
  73. package/dist/threads/index.js +2 -0
  74. package/dist/threads/index.js.map +1 -0
  75. package/dist/threads/spawn.d.ts +41 -0
  76. package/dist/threads/spawn.d.ts.map +1 -0
  77. package/dist/threads/spawn.js +143 -0
  78. package/dist/threads/spawn.js.map +1 -0
  79. package/dist/threads/worker.d.ts +51 -0
  80. package/dist/threads/worker.d.ts.map +1 -0
  81. package/dist/threads/worker.js +84 -0
  82. package/dist/threads/worker.js.map +1 -0
  83. package/dist/two-d/render/FogLayer.d.ts +68 -0
  84. package/dist/two-d/render/FogLayer.d.ts.map +1 -0
  85. package/dist/two-d/render/FogLayer.js +83 -0
  86. package/dist/two-d/render/FogLayer.js.map +1 -0
  87. package/dist/two-d/render/LiquidLayer.d.ts +62 -0
  88. package/dist/two-d/render/LiquidLayer.d.ts.map +1 -0
  89. package/dist/two-d/render/LiquidLayer.js +102 -0
  90. package/dist/two-d/render/LiquidLayer.js.map +1 -0
  91. package/dist/two-d/render/RpgmAutotile.d.ts +5 -0
  92. package/dist/two-d/render/RpgmAutotile.d.ts.map +1 -1
  93. package/dist/two-d/render/RpgmAutotile.js +4 -3
  94. package/dist/two-d/render/RpgmAutotile.js.map +1 -1
  95. package/dist/two-d/render/TileMap.d.ts +14 -2
  96. package/dist/two-d/render/TileMap.d.ts.map +1 -1
  97. package/dist/two-d/render/TileMap.js +32 -2
  98. package/dist/two-d/render/TileMap.js.map +1 -1
  99. package/dist/two-d/render/index.d.ts +4 -0
  100. package/dist/two-d/render/index.d.ts.map +1 -1
  101. package/dist/two-d/render/index.js +2 -0
  102. package/dist/two-d/render/index.js.map +1 -1
  103. package/dist/two-d/ui/Label.d.ts.map +1 -1
  104. package/dist/two-d/ui/Label.js +11 -2
  105. package/dist/two-d/ui/Label.js.map +1 -1
  106. package/dist/two-d/ui/ListView.d.ts +24 -0
  107. package/dist/two-d/ui/ListView.d.ts.map +1 -1
  108. package/dist/two-d/ui/ListView.js +97 -4
  109. package/dist/two-d/ui/ListView.js.map +1 -1
  110. package/dist/two-d/ui/MessageLog.d.ts +62 -0
  111. package/dist/two-d/ui/MessageLog.d.ts.map +1 -0
  112. package/dist/two-d/ui/MessageLog.js +114 -0
  113. package/dist/two-d/ui/MessageLog.js.map +1 -0
  114. package/dist/two-d/ui/RadioGroup.d.ts +68 -0
  115. package/dist/two-d/ui/RadioGroup.d.ts.map +1 -0
  116. package/dist/two-d/ui/RadioGroup.js +177 -0
  117. package/dist/two-d/ui/RadioGroup.js.map +1 -0
  118. package/dist/two-d/ui/TextModel.d.ts +26 -1
  119. package/dist/two-d/ui/TextModel.d.ts.map +1 -1
  120. package/dist/two-d/ui/TextModel.js +56 -7
  121. package/dist/two-d/ui/TextModel.js.map +1 -1
  122. package/dist/two-d/ui/TextSharpness.d.ts +33 -0
  123. package/dist/two-d/ui/TextSharpness.d.ts.map +1 -0
  124. package/dist/two-d/ui/TextSharpness.js +56 -0
  125. package/dist/two-d/ui/TextSharpness.js.map +1 -0
  126. package/dist/two-d/ui/index.d.ts +5 -0
  127. package/dist/two-d/ui/index.d.ts.map +1 -1
  128. package/dist/two-d/ui/index.js +3 -0
  129. package/dist/two-d/ui/index.js.map +1 -1
  130. package/dist/version.d.ts +1 -1
  131. package/dist/version.js +1 -1
  132. package/package.json +19 -2
  133. package/sbom.cdx.json +54 -28
  134. package/tools/build-tablebases.d.mts +22 -0
  135. package/tools/build-tablebases.mjs +549 -0
  136. package/tools/compile-openings.d.mts +20 -0
  137. package/tools/compile-openings.mjs +130 -0
  138. package/tools/compress-dist.d.mts +8 -0
  139. package/tools/compress-dist.mjs +76 -30
  140. package/tools/play-uci.d.mts +26 -0
  141. package/tools/play-uci.mjs +194 -0
@@ -0,0 +1,1013 @@
1
+ import { bitboardFromChess, bitboardMoves, makeMove } from './bitboard.js';
2
+ import { spawn } from '../threads/index.js';
3
+ /**
4
+ * The tournament engine's serializable task: a full iterative-deepening search over a
5
+ * bitboard position, with nothing outside its own body.
6
+ *
7
+ * `threads.spawn` rehydrates this function in a worker with `new Function`, so it may
8
+ * not reference imports, module scope, or any outer binding: every rule it touches is
9
+ * nested inside it. The main-thread wrappers call this same function directly, so the
10
+ * two sides cannot diverge the way a copied worker body could.
11
+ *
12
+ * What it does, per Roadmap item 390 phase 1: negamax alpha-beta with principal
13
+ * variation search, null-move pruning, late-move reductions, iterative deepening with
14
+ * aspiration windows, a bounded transposition table over Zobrist keys, quiescence over
15
+ * captures and promotions, ordering by hash move then MVV-LVA captures then killers
16
+ * then history, and a classical material plus in-house piece-square evaluation.
17
+ * Positions are plain bitboard data, so they structured-clone into a worker untouched.
18
+ *
19
+ * Draws: stalemate, fifty moves, KvK/KBvK/KNvK dead material, and repetition along the
20
+ * search path all score 0. Repetition only sees the path from the searched position,
21
+ * not the game before it; mate scores prefer the faster mate.
22
+ */
23
+ export function tourneyThink(position, options = {}) {
24
+ const MATE = 100_000;
25
+ const INFINITY = 1_000_000;
26
+ const VALUES = [100, 320, 330, 500, 900, 0];
27
+ const ASPIRATION = 24;
28
+ const MAX_PLY = 64;
29
+ const target = Math.max(1, Math.floor(options.depth ?? 3));
30
+ const maxNodes = options.maxNodes === undefined ? 500_000 : Math.max(1, Math.floor(options.maxNodes));
31
+ const deadline = options.timeMs === undefined ? -1 : Date.now() + Math.max(1, options.timeMs);
32
+ const board = {
33
+ pieces: position.pieces.slice(),
34
+ turn: position.turn,
35
+ castling: position.castling,
36
+ enPassant: position.enPassant,
37
+ halfmove: position.halfmove,
38
+ kings: [position.kings[0], position.kings[1]],
39
+ };
40
+ function bit(square) {
41
+ return 1n << BigInt(square);
42
+ }
43
+ function bitIndex(single) {
44
+ return Math.log2(Number(single));
45
+ }
46
+ const KNIGHT_STEPS = [
47
+ [1, 2],
48
+ [2, 1],
49
+ [2, -1],
50
+ [1, -2],
51
+ [-1, -2],
52
+ [-2, -1],
53
+ [-2, 1],
54
+ [-1, 2],
55
+ ];
56
+ const KING_STEPS = [
57
+ [1, 0],
58
+ [1, 1],
59
+ [0, 1],
60
+ [-1, 1],
61
+ [-1, 0],
62
+ [-1, -1],
63
+ [0, -1],
64
+ [1, -1],
65
+ ];
66
+ const DIAGONALS = [
67
+ [1, 1],
68
+ [-1, 1],
69
+ [-1, -1],
70
+ [1, -1],
71
+ ];
72
+ const STRAIGHTS = [
73
+ [1, 0],
74
+ [0, 1],
75
+ [-1, 0],
76
+ [0, -1],
77
+ ];
78
+ function leaperTable(steps) {
79
+ return Array.from({ length: 64 }, (_, square) => {
80
+ const file = square & 7;
81
+ const rank = square >> 3;
82
+ let attacks = 0n;
83
+ for (const [df, dr] of steps) {
84
+ const f = file + df;
85
+ const r = rank + dr;
86
+ if (f >= 0 && f < 8 && r >= 0 && r < 8)
87
+ attacks |= bit(r * 8 + f);
88
+ }
89
+ return attacks;
90
+ });
91
+ }
92
+ const KNIGHT_TABLE = leaperTable(KNIGHT_STEPS);
93
+ const KING_TABLE = leaperTable(KING_STEPS);
94
+ function occupancy(colorBase) {
95
+ return (board.pieces[colorBase] |
96
+ board.pieces[colorBase + 1] |
97
+ board.pieces[colorBase + 2] |
98
+ board.pieces[colorBase + 3] |
99
+ board.pieces[colorBase + 4] |
100
+ board.pieces[colorBase + 5]);
101
+ }
102
+ function squareKind(square) {
103
+ const mask = bit(square);
104
+ for (let i = 0; i < 12; i++) {
105
+ if (board.pieces[i] & mask)
106
+ return i % 6;
107
+ }
108
+ return -1;
109
+ }
110
+ function isAttacked(square, byWhite) {
111
+ const by = byWhite ? 0 : 6;
112
+ const file = square & 7;
113
+ const rank = square >> 3;
114
+ const pawnRank = byWhite ? rank - 1 : rank + 1;
115
+ for (const df of [-1, 1]) {
116
+ const f = file + df;
117
+ if (pawnRank >= 0 && pawnRank < 8 && f >= 0 && f < 8 && board.pieces[by] & bit(pawnRank * 8 + f))
118
+ return true;
119
+ }
120
+ if (KNIGHT_TABLE[square] & board.pieces[by + 1])
121
+ return true;
122
+ if (KING_TABLE[square] & board.pieces[by + 5])
123
+ return true;
124
+ const rays = [
125
+ { steps: DIAGONALS, kinds: [2, 4] },
126
+ { steps: STRAIGHTS, kinds: [3, 4] },
127
+ ];
128
+ for (const { steps, kinds } of rays) {
129
+ for (const [df, dr] of steps) {
130
+ let f = file + df;
131
+ let r = rank + dr;
132
+ while (f >= 0 && f < 8 && r >= 0 && r < 8) {
133
+ const found = squareKind(r * 8 + f);
134
+ if (found !== -1) {
135
+ const owner = (() => {
136
+ const mask = bit(r * 8 + f);
137
+ for (let i = 0; i < 12; i++) {
138
+ if (board.pieces[i] & mask)
139
+ return i < 6 ? 0 : 1;
140
+ }
141
+ return -1;
142
+ })();
143
+ if (owner === (byWhite ? 0 : 1) && kinds.includes(found))
144
+ return true;
145
+ break;
146
+ }
147
+ f += df;
148
+ r += dr;
149
+ }
150
+ }
151
+ }
152
+ return false;
153
+ }
154
+ let key = 0n;
155
+ let zstate = 0x9e3779b97f4a7c15n;
156
+ function znext() {
157
+ zstate = (zstate + 0x9e3779b97f4a7c15n) & 0xffffffffffffffffn;
158
+ let z = zstate;
159
+ z = ((z ^ (z >> 30n)) * 0xbf58476d1ce4e5b9n) & 0xffffffffffffffffn;
160
+ z = ((z ^ (z >> 27n)) * 0x94d049bb133111ebn) & 0xffffffffffffffffn;
161
+ return z ^ (z >> 31n);
162
+ }
163
+ const PIECE_KEYS = Array.from({ length: 12 }, () => Array.from({ length: 64 }, () => znext()));
164
+ const SIDE_KEY = znext();
165
+ const CASTLE_KEYS = Array.from({ length: 16 }, () => znext());
166
+ const EP_KEYS = Array.from({ length: 8 }, () => znext());
167
+ function fullKey() {
168
+ let k = 0n;
169
+ for (let i = 0; i < 12; i++) {
170
+ let bb = board.pieces[i];
171
+ while (bb) {
172
+ const single = bb & -bb;
173
+ k ^= PIECE_KEYS[i][bitIndex(single)];
174
+ bb ^= single;
175
+ }
176
+ }
177
+ if (board.turn === 'black')
178
+ k ^= SIDE_KEY;
179
+ k ^= CASTLE_KEYS[board.castling];
180
+ if (board.enPassant !== null)
181
+ k ^= EP_KEYS[board.enPassant & 7];
182
+ return k;
183
+ }
184
+ key = fullKey();
185
+ function rightBit(side, kingside) {
186
+ return side === 'white' ? (kingside ? 1 : 2) : kingside ? 4 : 8;
187
+ }
188
+ function doMove(move) {
189
+ const white = board.turn === 'white';
190
+ const us = white ? 0 : 1;
191
+ const colorBase = us * 6;
192
+ const fromBit = bit(move.from);
193
+ const toBit = bit(move.to);
194
+ let kind = -1;
195
+ for (let k = 0; k < 6; k++) {
196
+ if (board.pieces[colorBase + k] & fromBit) {
197
+ kind = k;
198
+ break;
199
+ }
200
+ }
201
+ const undo = {
202
+ captured: null,
203
+ castling: board.castling,
204
+ enPassant: board.enPassant,
205
+ halfmove: board.halfmove,
206
+ kingSquare: board.kings[us],
207
+ key,
208
+ nullMove: false,
209
+ };
210
+ const themBase = (1 - us) * 6;
211
+ const dir = white ? 8 : -8;
212
+ let capturedSquare = move.to;
213
+ if (kind === 0 && move.to === board.enPassant)
214
+ capturedSquare = move.to - dir;
215
+ const capturedBit = bit(capturedSquare);
216
+ for (let k = 0; k < 6; k++) {
217
+ if (board.pieces[themBase + k] & capturedBit) {
218
+ undo.captured = { color: (1 - us), kind: k };
219
+ board.pieces[themBase + k] ^= capturedBit;
220
+ break;
221
+ }
222
+ }
223
+ const home = white ? 0 : 56;
224
+ const themHome = white ? 56 : 0;
225
+ if (kind === 5)
226
+ board.castling &= ~(rightBit(board.turn, true) | rightBit(board.turn, false));
227
+ if (kind === 3 && (move.from === home || move.from === home + 7)) {
228
+ board.castling &= ~rightBit(board.turn, move.from === home + 7);
229
+ }
230
+ if (undo.captured &&
231
+ undo.captured.kind === 3 &&
232
+ (capturedSquare === themHome || capturedSquare === themHome + 7)) {
233
+ board.castling &= ~rightBit(white ? 'black' : 'white', capturedSquare === themHome + 7);
234
+ }
235
+ const PROMOTION_INDEX = { queen: 4, rook: 3, bishop: 2, knight: 1 };
236
+ board.pieces[colorBase + kind] ^= fromBit;
237
+ if (move.promotion)
238
+ board.pieces[colorBase + PROMOTION_INDEX[move.promotion]] |= toBit;
239
+ else
240
+ board.pieces[colorBase + kind] |= toBit;
241
+ if (kind === 5 && Math.abs(move.to - move.from) === 2) {
242
+ const rook = colorBase + 3;
243
+ if (move.to > move.from)
244
+ board.pieces[rook] ^= bit(home + 7) | bit(home + 5);
245
+ else
246
+ board.pieces[rook] ^= bit(home) | bit(home + 3);
247
+ }
248
+ board.enPassant = kind === 0 && Math.abs(move.to - move.from) === 16 ? (move.from + move.to) / 2 : null;
249
+ board.halfmove = kind === 0 || undo.captured ? 0 : board.halfmove + 1;
250
+ if (kind === 5)
251
+ board.kings[us] = move.to;
252
+ board.turn = white ? 'black' : 'white';
253
+ key = fullKey();
254
+ return undo;
255
+ }
256
+ function undoMove(move, undo) {
257
+ board.turn = board.turn === 'white' ? 'black' : 'white';
258
+ const white = board.turn === 'white';
259
+ const us = white ? 0 : 1;
260
+ const colorBase = us * 6;
261
+ const toBit = bit(move.to);
262
+ const kind = move.promotion
263
+ ? 0
264
+ : (() => {
265
+ for (let k = 0; k < 6; k++) {
266
+ if (board.pieces[colorBase + k] & toBit)
267
+ return k;
268
+ }
269
+ return -1;
270
+ })();
271
+ const PROMOTION_INDEX = { queen: 4, rook: 3, bishop: 2, knight: 1 };
272
+ if (move.promotion)
273
+ board.pieces[colorBase + PROMOTION_INDEX[move.promotion]] ^= toBit;
274
+ else
275
+ board.pieces[colorBase + kind] ^= toBit;
276
+ board.pieces[colorBase + kind] |= bit(move.from);
277
+ if (kind === 5 && Math.abs(move.to - move.from) === 2) {
278
+ const home = white ? 0 : 56;
279
+ const rook = colorBase + 3;
280
+ if (move.to > move.from)
281
+ board.pieces[rook] ^= bit(home + 7) | bit(home + 5);
282
+ else
283
+ board.pieces[rook] ^= bit(home) | bit(home + 3);
284
+ }
285
+ if (undo.captured) {
286
+ const dir = white ? 8 : -8;
287
+ const square = kind === 0 && move.to === undo.enPassant ? move.to - dir : move.to;
288
+ board.pieces[(1 - us) * 6 + undo.captured.kind] |= bit(square);
289
+ }
290
+ board.castling = undo.castling;
291
+ board.enPassant = undo.enPassant;
292
+ board.halfmove = undo.halfmove;
293
+ board.kings[us] = undo.kingSquare;
294
+ key = undo.key;
295
+ }
296
+ function doNull() {
297
+ const undo = {
298
+ captured: null,
299
+ castling: board.castling,
300
+ enPassant: board.enPassant,
301
+ halfmove: board.halfmove,
302
+ kingSquare: -1,
303
+ key,
304
+ nullMove: true,
305
+ };
306
+ board.enPassant = null;
307
+ board.halfmove += 1;
308
+ board.turn = board.turn === 'white' ? 'black' : 'white';
309
+ key = fullKey();
310
+ return undo;
311
+ }
312
+ function undoNull(undo) {
313
+ board.turn = board.turn === 'white' ? 'black' : 'white';
314
+ board.enPassant = undo.enPassant;
315
+ board.halfmove = undo.halfmove;
316
+ key = undo.key;
317
+ }
318
+ function pseudoMoves(from, kind, all, enemy, out) {
319
+ const white = board.turn === 'white';
320
+ const file = from & 7;
321
+ const rank = from >> 3;
322
+ const push = (to, promotion) => {
323
+ out.push(promotion ? { from, to, promotion } : { from, to });
324
+ };
325
+ const slide = (steps) => {
326
+ for (const [df, dr] of steps) {
327
+ let f = file + df;
328
+ let r = rank + dr;
329
+ while (f >= 0 && f < 8 && r >= 0 && r < 8) {
330
+ const to = r * 8 + f;
331
+ const mask = bit(to);
332
+ if (!(all & mask))
333
+ push(to);
334
+ else {
335
+ if (enemy & mask)
336
+ push(to);
337
+ break;
338
+ }
339
+ f += df;
340
+ r += dr;
341
+ }
342
+ }
343
+ };
344
+ const PROMOTIONS = ['queen', 'rook', 'bishop', 'knight'];
345
+ if (kind === 0) {
346
+ const dir = white ? 8 : -8;
347
+ const home = white ? 1 : 6;
348
+ const last = white ? 7 : 0;
349
+ const one = from + dir;
350
+ if (one >= 0 && one < 64 && !(all & bit(one))) {
351
+ if (one >> 3 === last) {
352
+ for (const promotion of PROMOTIONS)
353
+ push(one, promotion);
354
+ }
355
+ else {
356
+ push(one);
357
+ const two = from + 2 * dir;
358
+ if (rank === home && !(all & bit(two)))
359
+ push(two);
360
+ }
361
+ }
362
+ for (const df of [-1, 1]) {
363
+ const f = file + df;
364
+ if (f < 0 || f > 7)
365
+ continue;
366
+ const to = from + dir + df;
367
+ if (to < 0 || to >= 64)
368
+ continue;
369
+ const mask = bit(to);
370
+ if (enemy & mask) {
371
+ if (to >> 3 === last) {
372
+ for (const promotion of PROMOTIONS)
373
+ push(to, promotion);
374
+ }
375
+ else
376
+ push(to);
377
+ }
378
+ else if (to === board.enPassant)
379
+ push(to);
380
+ }
381
+ }
382
+ else if (kind === 1) {
383
+ let targets = KNIGHT_TABLE[from] & ~occupancy(white ? 0 : 6);
384
+ while (targets) {
385
+ const single = targets & -targets;
386
+ push(bitIndex(single));
387
+ targets ^= single;
388
+ }
389
+ }
390
+ else if (kind === 2)
391
+ slide(DIAGONALS);
392
+ else if (kind === 3)
393
+ slide(STRAIGHTS);
394
+ else if (kind === 4) {
395
+ slide(DIAGONALS);
396
+ slide(STRAIGHTS);
397
+ }
398
+ else {
399
+ let targets = KING_TABLE[from] & ~occupancy(white ? 0 : 6);
400
+ targets &= ~board.pieces[white ? 11 : 5];
401
+ while (targets) {
402
+ const single = targets & -targets;
403
+ push(bitIndex(single));
404
+ targets ^= single;
405
+ }
406
+ const home = white ? 0 : 56;
407
+ const rights = board.castling;
408
+ const kingside = white ? rights & 1 : rights & 4;
409
+ const queenside = white ? rights & 2 : rights & 8;
410
+ if (from === home + 4 && !isAttacked(from, !white)) {
411
+ if (kingside &&
412
+ !(all & bit(home + 5)) &&
413
+ !(all & bit(home + 6)) &&
414
+ !isAttacked(home + 5, !white) &&
415
+ !isAttacked(home + 6, !white)) {
416
+ push(home + 6);
417
+ }
418
+ if (queenside &&
419
+ !(all & bit(home + 3)) &&
420
+ !(all & bit(home + 2)) &&
421
+ !(all & bit(home + 1)) &&
422
+ !isAttacked(home + 3, !white) &&
423
+ !isAttacked(home + 2, !white)) {
424
+ push(home + 2);
425
+ }
426
+ }
427
+ }
428
+ }
429
+ function legalMoves() {
430
+ const white = board.turn === 'white';
431
+ const us = white ? 0 : 6;
432
+ const own = occupancy(us);
433
+ const enemy = occupancy(us === 0 ? 6 : 0);
434
+ const all = own | enemy;
435
+ const out = [];
436
+ const pseudo = [];
437
+ for (let from = 0; from < 64; from++) {
438
+ if (!(own & bit(from)))
439
+ continue;
440
+ let kind = -1;
441
+ for (let k = 0; k < 6; k++) {
442
+ if (board.pieces[us + k] & bit(from)) {
443
+ kind = k;
444
+ break;
445
+ }
446
+ }
447
+ pseudo.length = 0;
448
+ pseudoMoves(from, kind, all, enemy, pseudo);
449
+ for (const move of pseudo) {
450
+ const undo = doMove(move);
451
+ if (!isAttacked(board.kings[white ? 0 : 1], !white))
452
+ out.push({ ...move });
453
+ undoMove(move, undo);
454
+ }
455
+ }
456
+ return out;
457
+ }
458
+ function hasNonPawnMaterial(white) {
459
+ const base = white ? 0 : 6;
460
+ return (board.pieces[base + 1] !== 0n ||
461
+ board.pieces[base + 2] !== 0n ||
462
+ board.pieces[base + 3] !== 0n ||
463
+ board.pieces[base + 4] !== 0n);
464
+ }
465
+ function deadMaterial() {
466
+ let count = 0;
467
+ let minor = 0;
468
+ for (let i = 0; i < 12; i++) {
469
+ if (i % 6 === 5)
470
+ continue;
471
+ let bb = board.pieces[i];
472
+ while (bb) {
473
+ bb &= bb - 1n;
474
+ count++;
475
+ if (i % 6 === 1 || i % 6 === 2)
476
+ minor++;
477
+ }
478
+ }
479
+ if (count === 0)
480
+ return true;
481
+ return count === 1 && minor === 1;
482
+ }
483
+ function endgamePhase() {
484
+ let queens = 0;
485
+ let other = 0;
486
+ for (let i = 0; i < 12; i++) {
487
+ let bb = board.pieces[i];
488
+ while (bb) {
489
+ bb &= bb - 1n;
490
+ if (i % 6 === 4)
491
+ queens++;
492
+ else if (i % 6 !== 5 && i % 6 !== 0)
493
+ other++;
494
+ }
495
+ }
496
+ return queens === 0 || (queens === 2 && other <= 2);
497
+ }
498
+ function pieceSquare(kind, square, white, endgame) {
499
+ const file = square & 7;
500
+ const rank = white ? square >> 3 : 7 - (square >> 3);
501
+ const centerFile = 3.5 - Math.abs(file - 3.5);
502
+ const centerRank = 3.5 - Math.abs(rank - 3.5);
503
+ if (kind === 0)
504
+ return (rank - 1) * 12 + Math.max(0, centerFile) * 4;
505
+ if (kind === 1)
506
+ return (centerFile + centerRank) * 6 - 12;
507
+ if (kind === 2)
508
+ return (centerFile + centerRank) * 4;
509
+ if (kind === 3)
510
+ return (centerFile + centerRank) * 2 + (rank === 6 ? 12 : 0);
511
+ if (kind === 4)
512
+ return (centerFile + centerRank) * 2;
513
+ return endgame ? (centerFile + centerRank) * 10 : -rank * 10 + centerFile * 2;
514
+ }
515
+ function evaluate() {
516
+ const endgame = endgamePhase();
517
+ let score = 0;
518
+ for (let i = 0; i < 12; i++) {
519
+ const whitePiece = i < 6;
520
+ const kind = i % 6;
521
+ let bb = board.pieces[i];
522
+ while (bb) {
523
+ const single = bb & -bb;
524
+ const square = bitIndex(single);
525
+ const value = VALUES[kind] + pieceSquare(kind, square, whitePiece, endgame);
526
+ score += whitePiece ? value : -value;
527
+ bb ^= single;
528
+ }
529
+ }
530
+ return board.turn === 'white' ? score : -score;
531
+ }
532
+ const TT_SIZE = 65536;
533
+ const ttKey = new Array(TT_SIZE).fill(-1n);
534
+ const ttDepth = new Array(TT_SIZE).fill(-1);
535
+ const ttScore = new Array(TT_SIZE).fill(0);
536
+ const ttFlag = new Array(TT_SIZE).fill(0);
537
+ const ttMove = new Array(TT_SIZE).fill(0);
538
+ function encodeMove(move) {
539
+ const promo = move.promotion === 'queen'
540
+ ? 1
541
+ : move.promotion === 'rook'
542
+ ? 2
543
+ : move.promotion === 'bishop'
544
+ ? 3
545
+ : move.promotion === 'knight'
546
+ ? 4
547
+ : 0;
548
+ return move.from | (move.to << 6) | (promo << 12);
549
+ }
550
+ function ttProbe(depth, alpha, beta, ply) {
551
+ const slot = Number(key & BigInt(TT_SIZE - 1));
552
+ if (ttKey[slot] !== key)
553
+ return { score: null, move: 0 };
554
+ const move = ttMove[slot];
555
+ if (ttDepth[slot] < depth)
556
+ return { score: null, move };
557
+ let score = ttScore[slot];
558
+ if (score > MATE - 1000)
559
+ score -= ply;
560
+ else if (score < -(MATE - 1000))
561
+ score += ply;
562
+ const flag = ttFlag[slot];
563
+ if (flag === 0)
564
+ return { score, move };
565
+ if (flag === 1 && score >= beta)
566
+ return { score, move };
567
+ if (flag === 2 && score <= alpha)
568
+ return { score, move };
569
+ return { score: null, move };
570
+ }
571
+ function ttStore(depth, score, flag, move, ply) {
572
+ const slot = Number(key & BigInt(TT_SIZE - 1));
573
+ if (ttKey[slot] === key && ttDepth[slot] > depth)
574
+ return;
575
+ let stored = score;
576
+ if (score > MATE - 1000)
577
+ stored += ply;
578
+ else if (score < -(MATE - 1000))
579
+ stored -= ply;
580
+ ttKey[slot] = key;
581
+ ttDepth[slot] = depth;
582
+ ttScore[slot] = stored;
583
+ ttFlag[slot] = flag;
584
+ ttMove[slot] = move;
585
+ }
586
+ const killers = Array.from({ length: MAX_PLY }, () => [0, 0]);
587
+ const history = Array.from({ length: 2 }, () => Array.from({ length: 64 }, () => new Array(64).fill(0)));
588
+ function victimOn(square) {
589
+ const white = board.turn === 'white';
590
+ const themBase = white ? 6 : 0;
591
+ const mask = bit(square);
592
+ for (let k = 4; k >= 0; k--) {
593
+ if (board.pieces[themBase + k] & mask)
594
+ return k;
595
+ }
596
+ return -1;
597
+ }
598
+ function orderMoves(moves, tt, ply) {
599
+ const white = board.turn === 'white';
600
+ const color = white ? 0 : 1;
601
+ const scored = moves.map((move) => {
602
+ const encoded = encodeMove(move);
603
+ if (encoded === tt)
604
+ return 3_000_000;
605
+ const victim = victimOn(move.to);
606
+ const attacker = squareKind(move.from);
607
+ const isEp = move.to === board.enPassant && attacker === 0;
608
+ if (victim !== -1 || isEp) {
609
+ const victimValue = isEp ? VALUES[0] : VALUES[victim];
610
+ let value = 2_000_000 + victimValue * 8 - attacker;
611
+ if (move.promotion)
612
+ value += 900;
613
+ return value;
614
+ }
615
+ if (move.promotion)
616
+ return 1_500_000;
617
+ if (ply < MAX_PLY) {
618
+ if (killers[ply][0] === encoded)
619
+ return 1_000_000;
620
+ if (killers[ply][1] === encoded)
621
+ return 900_000;
622
+ return history[color][move.from][move.to];
623
+ }
624
+ return 0;
625
+ });
626
+ const order = moves.map((_, index) => index);
627
+ for (let i = 0; i < order.length; i++) {
628
+ let best = i;
629
+ for (let j = i + 1; j < order.length; j++) {
630
+ if (scored[order[j]] > scored[order[best]])
631
+ best = j;
632
+ }
633
+ [order[i], order[best]] = [order[best], order[i]];
634
+ }
635
+ return order;
636
+ }
637
+ let nodes = 0;
638
+ let stopped = false;
639
+ const pathKeys = [];
640
+ function checkStop() {
641
+ if (stopped)
642
+ return true;
643
+ if (nodes > maxNodes) {
644
+ stopped = true;
645
+ return true;
646
+ }
647
+ if (options.signal?.aborted) {
648
+ stopped = true;
649
+ return true;
650
+ }
651
+ if (deadline !== -1 && (nodes & 2047) === 0 && Date.now() >= deadline) {
652
+ stopped = true;
653
+ return true;
654
+ }
655
+ return false;
656
+ }
657
+ function quiescence(alpha, beta, ply) {
658
+ nodes++;
659
+ if (checkStop())
660
+ return 0;
661
+ const white = board.turn === 'white';
662
+ if (board.halfmove >= 100 || deadMaterial())
663
+ return 0;
664
+ if (isAttacked(board.kings[white ? 0 : 1], !white)) {
665
+ return search(1, alpha, beta, ply, false);
666
+ }
667
+ const standPat = evaluate();
668
+ if (standPat >= beta)
669
+ return beta;
670
+ if (standPat > alpha)
671
+ alpha = standPat;
672
+ const moves = legalMoves().filter((move) => {
673
+ if (move.promotion)
674
+ return true;
675
+ if (move.to === board.enPassant && squareKind(move.from) === 0)
676
+ return true;
677
+ const themBase = white ? 6 : 0;
678
+ const mask = bit(move.to);
679
+ for (let k = 0; k < 5; k++) {
680
+ if (board.pieces[themBase + k] & mask)
681
+ return true;
682
+ }
683
+ return false;
684
+ });
685
+ const order = orderMoves(moves, 0, Math.min(ply, MAX_PLY - 1));
686
+ for (const index of order) {
687
+ const move = moves[index];
688
+ const victim = victimOn(move.to);
689
+ if (standPat + (victim === -1 ? 900 : VALUES[victim]) + 200 < alpha)
690
+ continue;
691
+ const undo = doMove(move);
692
+ pathKeys.push(key);
693
+ const score = -quiescence(-beta, -alpha, ply + 1);
694
+ pathKeys.pop();
695
+ undoMove(move, undo);
696
+ if (stopped)
697
+ return 0;
698
+ if (score >= beta)
699
+ return beta;
700
+ if (score > alpha)
701
+ alpha = score;
702
+ }
703
+ return alpha;
704
+ }
705
+ function search(depth, alpha, beta, ply, allowNull) {
706
+ nodes++;
707
+ if (checkStop())
708
+ return 0;
709
+ const white = board.turn === 'white';
710
+ if (board.halfmove >= 100 || deadMaterial())
711
+ return 0;
712
+ let repetitions = 0;
713
+ for (const past of pathKeys) {
714
+ if (past === key && ++repetitions >= 2)
715
+ return 0;
716
+ }
717
+ if (ply >= MAX_PLY)
718
+ return evaluate();
719
+ if (depth <= 0)
720
+ return quiescence(alpha, beta, ply);
721
+ const inCheck = isAttacked(board.kings[white ? 0 : 1], !white);
722
+ const probe = ttProbe(depth, alpha, beta, ply);
723
+ if (probe.score !== null)
724
+ return probe.score;
725
+ if (inCheck) {
726
+ allowNull = false;
727
+ }
728
+ else if (allowNull && depth >= 3 && hasNonPawnMaterial(white) && beta < MATE - 1000) {
729
+ const reduction = depth > 4 ? 3 : 2;
730
+ const undo = doNull();
731
+ pathKeys.push(key);
732
+ const score = -search(depth - 1 - reduction, -beta, -beta + 1, ply + 1, false);
733
+ pathKeys.pop();
734
+ undoNull(undo);
735
+ if (stopped)
736
+ return 0;
737
+ if (score >= beta)
738
+ return beta;
739
+ }
740
+ const moves = legalMoves();
741
+ if (moves.length === 0)
742
+ return inCheck ? -(MATE - ply) : 0;
743
+ const order = orderMoves(moves, probe.move, Math.min(ply, MAX_PLY - 1));
744
+ const color = white ? 0 : 1;
745
+ let best = -INFINITY;
746
+ let bestMove = 0;
747
+ let flag = 2;
748
+ for (let i = 0; i < order.length; i++) {
749
+ const move = moves[order[i]];
750
+ const encoded = encodeMove(move);
751
+ const capture = victimOn(move.to) !== -1 || (move.to === board.enPassant && squareKind(move.from) === 0);
752
+ const undo = doMove(move);
753
+ pathKeys.push(key);
754
+ let score;
755
+ if (i === 0) {
756
+ score = -search(depth - 1, -beta, -alpha, ply + 1, true);
757
+ }
758
+ else {
759
+ let reduction = 0;
760
+ if (depth >= 3 && i >= 4 && !capture && !move.promotion && !inCheck)
761
+ reduction = 1;
762
+ if (reduction > 0) {
763
+ score = -search(depth - 1 - reduction, -alpha - 1, -alpha, ply + 1, true);
764
+ if (!stopped && score > alpha) {
765
+ score = -search(depth - 1, -beta, -alpha, ply + 1, true);
766
+ }
767
+ }
768
+ else {
769
+ score = -search(depth - 1, -alpha - 1, -alpha, ply + 1, true);
770
+ if (!stopped && score > alpha && score < beta) {
771
+ score = -search(depth - 1, -beta, -alpha, ply + 1, true);
772
+ }
773
+ }
774
+ }
775
+ pathKeys.pop();
776
+ undoMove(move, undo);
777
+ if (stopped)
778
+ return 0;
779
+ if (score > best) {
780
+ best = score;
781
+ bestMove = encoded;
782
+ }
783
+ if (score > alpha) {
784
+ alpha = score;
785
+ flag = score >= beta ? 1 : 0;
786
+ if (!capture && !move.promotion && ply < MAX_PLY) {
787
+ history[color][move.from][move.to] += depth * depth;
788
+ if (killers[ply][0] !== encoded) {
789
+ killers[ply][1] = killers[ply][0];
790
+ killers[ply][0] = encoded;
791
+ }
792
+ }
793
+ }
794
+ if (alpha >= beta)
795
+ break;
796
+ }
797
+ ttStore(depth, best, flag, bestMove, ply);
798
+ return best;
799
+ }
800
+ const rootMoves = legalMoves();
801
+ if (rootMoves.length === 0) {
802
+ const white = board.turn === 'white';
803
+ const mated = isAttacked(board.kings[white ? 0 : 1], !white);
804
+ return { move: null, score: mated ? -MATE : 0, nodes, depth: 0 };
805
+ }
806
+ pathKeys.push(key);
807
+ let best = {
808
+ from: rootMoves[0].from,
809
+ to: rootMoves[0].to,
810
+ ...(rootMoves[0].promotion ? { promotion: rootMoves[0].promotion } : {}),
811
+ };
812
+ let bestScore = -INFINITY;
813
+ let completed = 0;
814
+ let ordered = rootMoves.map((move) => ({ ...move }));
815
+ for (let depth = 1; depth <= target; depth++) {
816
+ let alpha = -INFINITY;
817
+ let beta = INFINITY;
818
+ let windowed = false;
819
+ let iterationBest = best;
820
+ let iterationScore = bestScore;
821
+ if (depth >= 4 && completed > 0) {
822
+ windowed = true;
823
+ alpha = bestScore - ASPIRATION;
824
+ beta = bestScore + ASPIRATION;
825
+ }
826
+ for (let attempt = 0; attempt < 3; attempt++) {
827
+ const alphaEdge = alpha;
828
+ const betaEdge = beta;
829
+ let roundBest = iterationBest;
830
+ let roundScore = -INFINITY;
831
+ const order = orderMoves(ordered, encodeMove({ from: iterationBest.from, to: iterationBest.to, promotion: iterationBest.promotion }), 0);
832
+ const round = [];
833
+ for (let i = 0; i < order.length; i++) {
834
+ const move = ordered[order[i]];
835
+ const undo = doMove(move);
836
+ pathKeys.push(key);
837
+ let score;
838
+ if (i === 0) {
839
+ score = -search(depth - 1, -beta, -alpha, 1, true);
840
+ }
841
+ else {
842
+ score = -search(depth - 1, -alpha - 1, -alpha, 1, true);
843
+ if (!stopped && score > alpha && score < beta) {
844
+ score = -search(depth - 1, -beta, -alpha, 1, true);
845
+ }
846
+ }
847
+ pathKeys.pop();
848
+ undoMove(move, undo);
849
+ if (stopped)
850
+ break;
851
+ round.push(move);
852
+ if (score > roundScore) {
853
+ roundScore = score;
854
+ roundBest = move;
855
+ }
856
+ if (score > alpha)
857
+ alpha = score;
858
+ }
859
+ if (stopped)
860
+ break;
861
+ for (const move of ordered) {
862
+ if (!round.includes(move))
863
+ round.push(move);
864
+ }
865
+ ordered = round;
866
+ iterationBest = roundBest;
867
+ iterationScore = roundScore;
868
+ if (!windowed)
869
+ break;
870
+ if (roundScore <= alphaEdge || roundScore >= betaEdge) {
871
+ alpha = -INFINITY;
872
+ beta = INFINITY;
873
+ windowed = false;
874
+ continue;
875
+ }
876
+ break;
877
+ }
878
+ if (stopped)
879
+ break;
880
+ best = iterationBest;
881
+ bestScore = iterationScore;
882
+ ordered = [best, ...ordered.filter((move) => move !== best)];
883
+ completed = depth;
884
+ if (bestScore > MATE - 1000 || bestScore < -(MATE - 1000))
885
+ break;
886
+ if (checkStop())
887
+ break;
888
+ }
889
+ pathKeys.pop();
890
+ if (completed === 0)
891
+ return { move: null, score: evaluate(), nodes, depth: 0 };
892
+ return { move: best, score: bestScore, nodes, depth: completed };
893
+ }
894
+ function toBitboard(state) {
895
+ const board = 'pieces' in state ? state : bitboardFromChess(state);
896
+ return {
897
+ pieces: board.pieces.slice(),
898
+ turn: board.turn,
899
+ castling: board.castling,
900
+ enPassant: board.enPassant,
901
+ halfmove: board.halfmove,
902
+ kings: [board.kings[0], board.kings[1]],
903
+ };
904
+ }
905
+ /**
906
+ * Chooses a move with the tournament search: the same `tourneyThink` task the
907
+ * workers run, called directly, so one implementation serves both sides.
908
+ *
909
+ * This is the stronger engine beside `board/Engine`'s small deterministic one: the
910
+ * minigame path keeps the small search and never pays for this module. Scores are
911
+ * centipawns from the side to move's view; mate scores sit near 100000, adjusted so
912
+ * the faster mate scores higher.
913
+ *
914
+ * @example
915
+ * ```ts
916
+ * import { searchTourney, startingBitboard, tourneyThink } from '@datamoc/mw_games/board';
917
+ *
918
+ * const direct = tourneyThink(startingBitboard(), { depth: 1 });
919
+ * const result = searchTourney(startingBitboard(), { depth: 2 });
920
+ * console.log(direct.move !== null && result.move !== null); // true - the opening position always has legal moves
921
+ * ```
922
+ */
923
+ export function searchTourney(state, options = {}) {
924
+ return tourneyThink(toBitboard(state), {
925
+ depth: options.depth,
926
+ maxNodes: options.maxNodes,
927
+ timeMs: options.timeMs,
928
+ signal: options.signal,
929
+ });
930
+ }
931
+ /** hardware threads for a root split, capped: twenty opening moves need lanes, not a storm */
932
+ function defaultTourneyJobs() {
933
+ const scope = globalThis;
934
+ const concurrent = scope.navigator?.hardwareConcurrency;
935
+ if (typeof concurrent === 'number' && Number.isInteger(concurrent) && concurrent > 0) {
936
+ return Math.min(concurrent, 8);
937
+ }
938
+ try {
939
+ const parallelism = scope.process?.getBuiltinModule?.('node:os')?.availableParallelism?.();
940
+ if (typeof parallelism === 'number' && Number.isInteger(parallelism) && parallelism > 0) {
941
+ return Math.min(parallelism, 8);
942
+ }
943
+ }
944
+ catch {
945
+ //a search must never fail for lack of a hint about its own host
946
+ }
947
+ return 4;
948
+ }
949
+ /**
950
+ * Chooses a move the way `searchTourney` does, but thinks on worker threads through
951
+ * `threads.spawn`, so a deep search does not drop the caller's frames. Each root move
952
+ * is a self-contained `tourneyThink` task over a structured-cloned bitboard, scored
953
+ * concurrently down a capped lane queue; the best child score from the opponent's
954
+ * view is the worst for us, so the root plays the move whose child scored lowest,
955
+ * with ties going to the earlier root move. An aborting signal rejects through
956
+ * `threads.spawn` and terminates the workers.
957
+ *
958
+ * @example
959
+ * ```ts
960
+ * import { searchTourneyAsync, startingBitboard } from '@datamoc/mw_games/board';
961
+ *
962
+ * const result = await searchTourneyAsync(startingBitboard(), { depth: 2 });
963
+ * console.log(result.move !== null); // true - the opening position always has legal moves
964
+ * ```
965
+ */
966
+ export async function searchTourneyAsync(state, options = {}) {
967
+ const depth = Math.max(1, Math.floor(options.depth ?? 3));
968
+ const maxNodes = options.maxNodes === undefined ? 500_000 : Math.max(1, Math.floor(options.maxNodes));
969
+ const jobs = options.jobs ?? defaultTourneyJobs();
970
+ if (!Number.isInteger(jobs) || jobs < 1)
971
+ throw new RangeError('jobs must be a positive integer');
972
+ if (options.signal?.aborted)
973
+ throw options.signal.reason;
974
+ const root = toBitboard(state);
975
+ const moves = bitboardMoves(root);
976
+ if (!moves.length)
977
+ return tourneyThink(root, { depth, maxNodes });
978
+ //one chain per lane over the shared move queue, the tools/compress-dist.mjs shape:
979
+ //scores land by index, so ties break in root order no matter who finishes first
980
+ const lanes = Math.max(1, Math.min(jobs, moves.length));
981
+ const children = moves.map((move) => {
982
+ const child = toBitboard(root);
983
+ makeMove(child, move);
984
+ return child;
985
+ });
986
+ const scores = new Array(moves.length);
987
+ let nodes = 0;
988
+ let next = 0;
989
+ const spawnOptions = options.signal ? { signal: options.signal } : {};
990
+ await Promise.all(Array.from({ length: lanes }, async () => {
991
+ for (;;) {
992
+ const index = next++;
993
+ if (index >= moves.length)
994
+ return;
995
+ const task = await spawn(tourneyThink, [children[index], { depth: Math.max(1, depth - 1), maxNodes, timeMs: options.timeMs }], spawnOptions);
996
+ nodes += task.nodes;
997
+ scores[index] = -task.score;
998
+ }
999
+ }));
1000
+ let bestIndex = 0;
1001
+ for (let i = 1; i < moves.length; i++) {
1002
+ if ((scores[i] ?? -Infinity) > (scores[bestIndex] ?? -Infinity))
1003
+ bestIndex = i;
1004
+ }
1005
+ const best = moves[bestIndex];
1006
+ return {
1007
+ move: { from: best.from, to: best.to, ...(best.promotion ? { promotion: best.promotion } : {}) },
1008
+ score: scores[bestIndex] ?? 0,
1009
+ nodes,
1010
+ depth,
1011
+ };
1012
+ }
1013
+ //# sourceMappingURL=tourney.js.map