@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,769 @@
1
+ /**
2
+ * Parse, transform and write PGN.
3
+ *
4
+ * ## Parser
5
+ *
6
+ * The parser will interpret any input as a PGN, creating a tree of
7
+ * syntactically valid (but not necessarily legal) moves, skipping any invalid
8
+ * tokens.
9
+ *
10
+ * ```ts
11
+ * import { parsePgn, startingPosition } from '@connectorvol/chessops/pgn';
12
+ * import { parseSan } from '@connectorvol/chessops/san';
13
+ *
14
+ * const pgn = '1. d4 d5 *';
15
+ * const games = parsePgn(pgn);
16
+ * for (const game of games) {
17
+ * const pos = startingPosition(game.headers).unwrap();
18
+ * for (const node of game.moves.mainline()) {
19
+ * const move = parseSan(pos, node.san);
20
+ * if (!move) break; // Illegal move
21
+ * pos.play(move);
22
+ * }
23
+ * }
24
+ * ```
25
+ *
26
+ * ## Streaming parser
27
+ *
28
+ * The module also provides a denial-of-service resistant streaming parser.
29
+ * It can be configured with a budget for reasonable complexity of a single
30
+ * game, fed with chunks of text, and will yield parsed games as they are
31
+ * completed.
32
+ *
33
+ * ```ts
34
+ *
35
+ * import { createReadStream } from 'fs';
36
+ * import { PgnParser } from '@connectorvol/chessops/pgn';
37
+ *
38
+ * const stream = createReadStream('games.pgn', { encoding: 'utf-8' });
39
+ *
40
+ * const parser = new PgnParser((game, err) => {
41
+ * if (err) {
42
+ * // Budget exceeded.
43
+ * stream.destroy(err);
44
+ * }
45
+ *
46
+ * // Use game ...
47
+ * });
48
+ *
49
+ * await new Promise<void>(resolve =>
50
+ * stream
51
+ * .on('data', (chunk: string) => parser.parse(chunk, { stream: true }))
52
+ * .on('close', () => {
53
+ * parser.parse('');
54
+ * resolve();
55
+ * })
56
+ * );
57
+ * ```
58
+ *
59
+ * ## Augmenting the game tree
60
+ *
61
+ * You can use `walk` to visit all nodes in the game tree, or `transform`
62
+ * to augment it with user data.
63
+ *
64
+ * Both allow you to provide context. You update the context inside the
65
+ * callback, and it is automatically `clone()`-ed at each fork.
66
+ * In the example below, the current position `pos` is provided as context.
67
+ *
68
+ * ```ts
69
+ * import { transform } from '@connectorvol/chessops/pgn';
70
+ * import { makeFen } from '@connectorvol/chessops/fen';
71
+ * import { parseSan, makeSanAndPlay } from '@connectorvol/chessops/san';
72
+ *
73
+ * const pos = startingPosition(game.headers).unwrap();
74
+ * game.moves = transform(game.moves, pos, (pos, node) => {
75
+ * const move = parseSan(pos, node.san);
76
+ * if (!move) {
77
+ * // Illegal move. Returning undefined cuts off the tree here.
78
+ * return;
79
+ * }
80
+ *
81
+ * const san = makeSanAndPlay(pos, move); // Mutating pos!
82
+ *
83
+ * return {
84
+ * ...node, // Keep comments and annotation glyphs
85
+ * san, // Normalized SAN
86
+ * fen: makeFen(pos.toSetup()), // Add arbitrary user data to node
87
+ * };
88
+ * });
89
+ * ```
90
+ *
91
+ * ## Writing
92
+ *
93
+ * Requires each node to at least have a `san` property.
94
+ *
95
+ * ```
96
+ * import { makePgn } from '@connectorvol/chessops/pgn';
97
+ *
98
+ * const rewrittenPgn = makePgn(game);
99
+ * ```
100
+ *
101
+ * @packageDocumentation
102
+ */
103
+ import { Result } from "@badrap/result";
104
+ import { IllegalSetup, PositionError } from "./chess.js";
105
+ import { makeFen, parseFen } from "./fen.js";
106
+ import { defined, makeSquare, parseSquare } from "./util.js";
107
+ import { defaultPosition, setupPosition } from "./variant.js";
108
+ export const defaultGame = (initHeaders = defaultHeaders) => ({
109
+ headers: initHeaders(),
110
+ moves: new Node(),
111
+ });
112
+ export class Node {
113
+ constructor() {
114
+ this.children = [];
115
+ }
116
+ *mainlineNodes() {
117
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
118
+ let node = this;
119
+ while (node.children.length) {
120
+ const child = node.children[0];
121
+ yield child;
122
+ node = child;
123
+ }
124
+ }
125
+ *mainline() {
126
+ for (const child of this.mainlineNodes())
127
+ yield child.data;
128
+ }
129
+ end() {
130
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
131
+ let node = this;
132
+ while (node.children.length)
133
+ node = node.children[0];
134
+ return node;
135
+ }
136
+ }
137
+ export class ChildNode extends Node {
138
+ constructor(data) {
139
+ super();
140
+ this.data = data;
141
+ }
142
+ }
143
+ export const isChildNode = (node) => node instanceof ChildNode;
144
+ export const extend = (node, data) => {
145
+ for (const d of data) {
146
+ const child = new ChildNode(d);
147
+ node.children.push(child);
148
+ node = child;
149
+ }
150
+ return node;
151
+ };
152
+ export class Box {
153
+ constructor(value) {
154
+ this.value = value;
155
+ }
156
+ clone() {
157
+ return new Box(this.value);
158
+ }
159
+ }
160
+ export const transform = (node, ctx, f) => {
161
+ const root = new Node();
162
+ const stack = [
163
+ {
164
+ before: node,
165
+ after: root,
166
+ ctx,
167
+ },
168
+ ];
169
+ let frame;
170
+ while ((frame = stack.pop())) {
171
+ for (let childIndex = 0; childIndex < frame.before.children.length; childIndex++) {
172
+ const ctx = childIndex < frame.before.children.length - 1 ? frame.ctx.clone() : frame.ctx;
173
+ const childBefore = frame.before.children[childIndex];
174
+ const data = f(ctx, childBefore.data, childIndex);
175
+ if (defined(data)) {
176
+ const childAfter = new ChildNode(data);
177
+ frame.after.children.push(childAfter);
178
+ stack.push({
179
+ before: childBefore,
180
+ after: childAfter,
181
+ ctx,
182
+ });
183
+ }
184
+ }
185
+ }
186
+ return root;
187
+ };
188
+ export const walk = (node, ctx, f) => {
189
+ const stack = [{ node, ctx }];
190
+ let frame;
191
+ while ((frame = stack.pop())) {
192
+ for (let childIndex = 0; childIndex < frame.node.children.length; childIndex++) {
193
+ const ctx = childIndex < frame.node.children.length - 1 ? frame.ctx.clone() : frame.ctx;
194
+ const child = frame.node.children[childIndex];
195
+ if (f(ctx, child.data, childIndex) !== false)
196
+ stack.push({ node: child, ctx });
197
+ }
198
+ }
199
+ };
200
+ export const makeOutcome = (outcome) => {
201
+ if (!outcome)
202
+ return "*";
203
+ else if (outcome.winner === "white")
204
+ return "1-0";
205
+ else if (outcome.winner === "black")
206
+ return "0-1";
207
+ else
208
+ return "1/2-1/2";
209
+ };
210
+ export const parseOutcome = (s) => {
211
+ if (s === "1-0" || s === "1–0" || s === "1—0")
212
+ return { winner: "white" };
213
+ else if (s === "0-1" || s === "0–1" || s === "0—1")
214
+ return { winner: "black" };
215
+ else if (s === "1/2-1/2" || s === "1/2–1/2" || s === "1/2—1/2")
216
+ return { winner: undefined };
217
+ else
218
+ return;
219
+ };
220
+ const escapeHeader = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
221
+ const safeComment = (comment) => comment.replace(/\}/g, "");
222
+ export const makePgn = (game) => {
223
+ const builder = [], tokens = [];
224
+ if (game.headers.size) {
225
+ for (const [key, value] of game.headers.entries()) {
226
+ builder.push("[", key, ' "', escapeHeader(value), '"]\n');
227
+ }
228
+ builder.push("\n");
229
+ }
230
+ for (const comment of game.comments || [])
231
+ tokens.push("{", safeComment(comment), "}");
232
+ const fen = game.headers.get("FEN");
233
+ const initialPly = fen
234
+ ? parseFen(fen).unwrap((setup) => (setup.fullmoves - 1) * 2 + (setup.turn === "white" ? 0 : 1), (_) => 0)
235
+ : 0;
236
+ const stack = [];
237
+ const variations = game.moves.children[Symbol.iterator]();
238
+ const firstVariation = variations.next();
239
+ if (!firstVariation.done) {
240
+ stack.push({
241
+ state: 0 /* MakePgnState.Pre */,
242
+ ply: initialPly,
243
+ node: firstVariation.value,
244
+ sidelines: variations,
245
+ startsVariation: false,
246
+ inVariation: false,
247
+ });
248
+ }
249
+ let forceMoveNumber = true;
250
+ while (stack.length) {
251
+ const frame = stack[stack.length - 1];
252
+ if (frame.inVariation) {
253
+ tokens.push(")");
254
+ frame.inVariation = false;
255
+ forceMoveNumber = true;
256
+ }
257
+ switch (frame.state) {
258
+ case 0 /* MakePgnState.Pre */:
259
+ for (const comment of frame.node.data.startingComments || []) {
260
+ tokens.push("{", safeComment(comment), "}");
261
+ forceMoveNumber = true;
262
+ }
263
+ if (forceMoveNumber || frame.ply % 2 === 0) {
264
+ tokens.push(Math.floor(frame.ply / 2) + 1 + (frame.ply % 2 ? "..." : "."));
265
+ forceMoveNumber = false;
266
+ }
267
+ tokens.push(frame.node.data.san);
268
+ for (const nag of frame.node.data.nags || []) {
269
+ tokens.push("$" + nag);
270
+ forceMoveNumber = true;
271
+ }
272
+ for (const comment of frame.node.data.comments || []) {
273
+ tokens.push("{", safeComment(comment), "}");
274
+ }
275
+ frame.state = 1 /* MakePgnState.Sidelines */; // fall through
276
+ case 1 /* MakePgnState.Sidelines */: {
277
+ const child = frame.sidelines.next();
278
+ if (child.done) {
279
+ const variations = frame.node.children[Symbol.iterator]();
280
+ const firstVariation = variations.next();
281
+ if (!firstVariation.done) {
282
+ stack.push({
283
+ state: 0 /* MakePgnState.Pre */,
284
+ ply: frame.ply + 1,
285
+ node: firstVariation.value,
286
+ sidelines: variations,
287
+ startsVariation: false,
288
+ inVariation: false,
289
+ });
290
+ }
291
+ frame.state = 2 /* MakePgnState.End */;
292
+ }
293
+ else {
294
+ tokens.push("(");
295
+ forceMoveNumber = true;
296
+ stack.push({
297
+ state: 0 /* MakePgnState.Pre */,
298
+ ply: frame.ply,
299
+ node: child.value,
300
+ sidelines: [][Symbol.iterator](),
301
+ startsVariation: true,
302
+ inVariation: false,
303
+ });
304
+ frame.inVariation = true;
305
+ }
306
+ break;
307
+ }
308
+ case 2 /* MakePgnState.End */:
309
+ stack.pop();
310
+ }
311
+ }
312
+ tokens.push(makeOutcome(parseOutcome(game.headers.get("Result"))));
313
+ builder.push(tokens.join(" "), "\n");
314
+ return builder.join("");
315
+ };
316
+ export const defaultHeaders = () => new Map([
317
+ ["Event", "?"],
318
+ ["Site", "?"],
319
+ ["Date", "????.??.??"],
320
+ ["Round", "?"],
321
+ ["White", "?"],
322
+ ["Black", "?"],
323
+ ["Result", "*"],
324
+ ]);
325
+ export const emptyHeaders = () => new Map();
326
+ const BOM = "\ufeff";
327
+ const isWhitespace = (line) => /^\s*$/.test(line);
328
+ const isCommentLine = (line) => line.startsWith("%");
329
+ export class PgnError extends Error {
330
+ }
331
+ export class PgnParser {
332
+ constructor(emitGame, initHeaders = defaultHeaders, maxBudget = 1000000) {
333
+ this.emitGame = emitGame;
334
+ this.initHeaders = initHeaders;
335
+ this.maxBudget = maxBudget;
336
+ this.lineBuf = [];
337
+ this.resetGame();
338
+ this.state = 0 /* ParserState.Bom */;
339
+ }
340
+ resetGame() {
341
+ this.budget = this.maxBudget;
342
+ this.found = false;
343
+ this.state = 1 /* ParserState.Pre */;
344
+ this.game = defaultGame(this.initHeaders);
345
+ this.stack = [{ parent: this.game.moves, root: true }];
346
+ this.commentBuf = [];
347
+ }
348
+ consumeBudget(cost) {
349
+ this.budget -= cost;
350
+ if (this.budget < 0)
351
+ throw new PgnError("ERR_PGN_BUDGET");
352
+ }
353
+ parse(data, options) {
354
+ if (this.budget < 0)
355
+ return;
356
+ try {
357
+ let idx = 0;
358
+ for (;;) {
359
+ const nlIdx = data.indexOf("\n", idx);
360
+ if (nlIdx === -1) {
361
+ break;
362
+ }
363
+ const crIdx = nlIdx > idx && data[nlIdx - 1] === "\r" ? nlIdx - 1 : nlIdx;
364
+ this.consumeBudget(nlIdx - idx);
365
+ this.lineBuf.push(data.slice(idx, crIdx));
366
+ idx = nlIdx + 1;
367
+ this.handleLine();
368
+ }
369
+ this.consumeBudget(data.length - idx);
370
+ this.lineBuf.push(data.slice(idx));
371
+ if (!(options === null || options === void 0 ? void 0 : options.stream)) {
372
+ this.handleLine();
373
+ this.emit(undefined);
374
+ }
375
+ }
376
+ catch (err) {
377
+ this.emit(err);
378
+ }
379
+ }
380
+ handleLine() {
381
+ let freshLine = true;
382
+ let line = this.lineBuf.join("");
383
+ this.lineBuf = [];
384
+ continuedLine: for (;;) {
385
+ switch (this.state) {
386
+ case 0 /* ParserState.Bom */:
387
+ if (line.startsWith(BOM))
388
+ line = line.slice(BOM.length);
389
+ this.state = 1 /* ParserState.Pre */; // fall through
390
+ case 1 /* ParserState.Pre */:
391
+ if (isWhitespace(line) || isCommentLine(line))
392
+ return;
393
+ this.found = true;
394
+ this.state = 2 /* ParserState.Headers */; // fall through
395
+ case 2 /* ParserState.Headers */: {
396
+ if (isCommentLine(line))
397
+ return;
398
+ let moreHeaders = true;
399
+ while (moreHeaders) {
400
+ moreHeaders = false;
401
+ line = line.replace(/^\s*\[([A-Za-z0-9][A-Za-z0-9_+#=:-]*)\s+"((?:[^"\\]|\\"|\\\\)*)"\]/, (_match, headerName, headerValue) => {
402
+ this.consumeBudget(200);
403
+ this.handleHeader(headerName, headerValue.replace(/\\"/g, '"').replace(/\\\\/g, "\\"));
404
+ moreHeaders = true;
405
+ freshLine = false;
406
+ return "";
407
+ });
408
+ }
409
+ if (isWhitespace(line))
410
+ return;
411
+ this.state = 3 /* ParserState.Moves */; // fall through
412
+ }
413
+ case 3 /* ParserState.Moves */: {
414
+ if (freshLine) {
415
+ if (isCommentLine(line))
416
+ return;
417
+ if (isWhitespace(line))
418
+ return this.emit(undefined);
419
+ }
420
+ const tokenRegex = /(?:[NBKRQ]?[a-h]?[1-8]?[-x]?[a-h][1-8](?:=?[nbrqkNBRQK])?|[pnbrqkPNBRQK]?@[a-h][1-8]|[O0o][-–—][O0o](?:[-–—][O0o])?)[+#]?|--|Z0|0000|@@@@|{|;|\$\d{1,4}|[?!]{1,2}|\(|\)|\*|1[-–—]0|0[-–—]1|1\/2[-–—]1\/2/g;
421
+ let match;
422
+ while ((match = tokenRegex.exec(line))) {
423
+ const frame = this.stack[this.stack.length - 1];
424
+ let token = match[0];
425
+ if (token === ";")
426
+ return;
427
+ else if (token.startsWith("$"))
428
+ this.handleNag(parseInt(token.slice(1), 10));
429
+ else if (token === "!")
430
+ this.handleNag(1);
431
+ else if (token === "?")
432
+ this.handleNag(2);
433
+ else if (token === "!!")
434
+ this.handleNag(3);
435
+ else if (token === "??")
436
+ this.handleNag(4);
437
+ else if (token === "!?")
438
+ this.handleNag(5);
439
+ else if (token === "?!")
440
+ this.handleNag(6);
441
+ else if (token === "1-0" ||
442
+ token === "1–0" ||
443
+ token === "1—0" ||
444
+ token === "0-1" ||
445
+ token === "0–1" ||
446
+ token === "0—1" ||
447
+ token === "1/2-1/2" ||
448
+ token === "1/2–1/2" ||
449
+ token === "1/2—1/2" ||
450
+ token === "*") {
451
+ if (this.stack.length === 1 && token !== "*")
452
+ this.handleHeader("Result", token);
453
+ }
454
+ else if (token === "(") {
455
+ this.consumeBudget(100);
456
+ this.stack.push({ parent: frame.parent, root: false });
457
+ }
458
+ else if (token === ")") {
459
+ if (this.stack.length > 1)
460
+ this.stack.pop();
461
+ }
462
+ else if (token === "{") {
463
+ const openIndex = tokenRegex.lastIndex;
464
+ const beginIndex = line[openIndex] === " " ? openIndex + 1 : openIndex;
465
+ line = line.slice(beginIndex);
466
+ this.state = 4 /* ParserState.Comment */;
467
+ continue continuedLine;
468
+ }
469
+ else {
470
+ this.consumeBudget(100);
471
+ if (token.startsWith("O") || token.startsWith("0") || token.startsWith("o")) {
472
+ token = token.replace(/[0o]/g, "O").replace(/[–—]/g, "-");
473
+ }
474
+ else if (token === "Z0" || token === "0000" || token === "@@@@")
475
+ token = "--";
476
+ if (frame.node)
477
+ frame.parent = frame.node;
478
+ frame.node = new ChildNode({
479
+ san: token,
480
+ startingComments: frame.startingComments,
481
+ });
482
+ frame.startingComments = undefined;
483
+ frame.root = false;
484
+ frame.parent.children.push(frame.node);
485
+ }
486
+ }
487
+ return;
488
+ }
489
+ case 4 /* ParserState.Comment */: {
490
+ const closeIndex = line.indexOf("}");
491
+ if (closeIndex === -1) {
492
+ this.commentBuf.push(line);
493
+ return;
494
+ }
495
+ else {
496
+ const endIndex = closeIndex > 0 && line[closeIndex - 1] === " " ? closeIndex - 1 : closeIndex;
497
+ this.commentBuf.push(line.slice(0, endIndex));
498
+ this.handleComment();
499
+ line = line.slice(closeIndex);
500
+ this.state = 3 /* ParserState.Moves */;
501
+ freshLine = false;
502
+ }
503
+ }
504
+ }
505
+ }
506
+ }
507
+ handleHeader(name, value) {
508
+ this.game.headers.set(name, name === "Result" ? makeOutcome(parseOutcome(value)) : value);
509
+ }
510
+ handleNag(nag) {
511
+ var _a;
512
+ this.consumeBudget(50);
513
+ const frame = this.stack[this.stack.length - 1];
514
+ if (frame.node) {
515
+ (_a = frame.node.data).nags || (_a.nags = []);
516
+ frame.node.data.nags.push(nag);
517
+ }
518
+ }
519
+ handleComment() {
520
+ var _a, _b;
521
+ this.consumeBudget(100);
522
+ const frame = this.stack[this.stack.length - 1];
523
+ const comment = this.commentBuf.join("\n");
524
+ this.commentBuf = [];
525
+ if (frame.node) {
526
+ (_a = frame.node.data).comments || (_a.comments = []);
527
+ frame.node.data.comments.push(comment);
528
+ }
529
+ else if (frame.root) {
530
+ (_b = this.game).comments || (_b.comments = []);
531
+ this.game.comments.push(comment);
532
+ }
533
+ else {
534
+ frame.startingComments || (frame.startingComments = []);
535
+ frame.startingComments.push(comment);
536
+ }
537
+ }
538
+ emit(err) {
539
+ if (this.state === 4 /* ParserState.Comment */)
540
+ this.handleComment();
541
+ if (err)
542
+ return this.emitGame(this.game, err);
543
+ if (this.found)
544
+ this.emitGame(this.game, undefined);
545
+ this.resetGame();
546
+ }
547
+ }
548
+ export const parsePgn = (pgn, initHeaders = defaultHeaders) => {
549
+ const games = [];
550
+ new PgnParser((game) => games.push(game), initHeaders, NaN).parse(pgn);
551
+ return games;
552
+ };
553
+ export const parseVariant = (variant) => {
554
+ switch ((variant || "chess").toLowerCase()) {
555
+ case "chess":
556
+ case "chess960":
557
+ case "chess 960":
558
+ case "standard":
559
+ case "from position":
560
+ case "classical":
561
+ case "normal":
562
+ case "fischerandom": // Cute Chess
563
+ case "fischerrandom":
564
+ case "fischer random":
565
+ case "wild/0":
566
+ case "wild/1":
567
+ case "wild/2":
568
+ case "wild/3":
569
+ case "wild/4":
570
+ case "wild/5":
571
+ case "wild/6":
572
+ case "wild/7":
573
+ case "wild/8":
574
+ case "wild/8a":
575
+ return "chess";
576
+ case "crazyhouse":
577
+ case "crazy house":
578
+ case "house":
579
+ case "zh":
580
+ return "crazyhouse";
581
+ case "king of the hill":
582
+ case "koth":
583
+ case "kingofthehill":
584
+ return "kingofthehill";
585
+ case "three-check":
586
+ case "three check":
587
+ case "threecheck":
588
+ case "three check chess":
589
+ case "3-check":
590
+ case "3 check":
591
+ case "3check":
592
+ return "3check";
593
+ case "antichess":
594
+ case "anti chess":
595
+ case "anti":
596
+ return "antichess";
597
+ case "atomic":
598
+ case "atom":
599
+ case "atomic chess":
600
+ return "atomic";
601
+ case "horde":
602
+ case "horde chess":
603
+ return "horde";
604
+ case "racing kings":
605
+ case "racingkings":
606
+ case "racing":
607
+ case "race":
608
+ return "racingkings";
609
+ default:
610
+ return;
611
+ }
612
+ };
613
+ export const makeVariant = (rules) => {
614
+ switch (rules) {
615
+ case "chess":
616
+ return;
617
+ case "crazyhouse":
618
+ return "Crazyhouse";
619
+ case "racingkings":
620
+ return "Racing Kings";
621
+ case "horde":
622
+ return "Horde";
623
+ case "atomic":
624
+ return "Atomic";
625
+ case "antichess":
626
+ return "Antichess";
627
+ case "3check":
628
+ return "Three-check";
629
+ case "kingofthehill":
630
+ return "King of the Hill";
631
+ }
632
+ };
633
+ export const startingPosition = (headers) => {
634
+ const rules = parseVariant(headers.get("Variant"));
635
+ if (!rules)
636
+ return Result.err(new PositionError(IllegalSetup.Variant));
637
+ const fen = headers.get("FEN");
638
+ if (fen)
639
+ return parseFen(fen).chain((setup) => setupPosition(rules, setup));
640
+ else
641
+ return Result.ok(defaultPosition(rules));
642
+ };
643
+ export const setStartingPosition = (headers, pos) => {
644
+ const variant = makeVariant(pos.rules);
645
+ if (variant)
646
+ headers.set("Variant", variant);
647
+ else
648
+ headers.delete("Variant");
649
+ const fen = makeFen(pos.toSetup());
650
+ const defaultFen = makeFen(defaultPosition(pos.rules).toSetup());
651
+ if (fen !== defaultFen)
652
+ headers.set("FEN", fen);
653
+ else
654
+ headers.delete("FEN");
655
+ };
656
+ export const isPawns = (ev) => "pawns" in ev;
657
+ export const isMate = (ev) => "mate" in ev;
658
+ const makeClk = (seconds) => {
659
+ seconds = Math.max(0, seconds);
660
+ const hours = Math.floor(seconds / 3600);
661
+ const minutes = Math.floor((seconds % 3600) / 60);
662
+ seconds = (seconds % 3600) % 60;
663
+ return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toLocaleString("en", {
664
+ minimumIntegerDigits: 2,
665
+ maximumFractionDigits: 3,
666
+ })}`;
667
+ };
668
+ const makeCommentShapeColor = (color) => {
669
+ switch (color) {
670
+ case "green":
671
+ return "G";
672
+ case "red":
673
+ return "R";
674
+ case "yellow":
675
+ return "Y";
676
+ case "blue":
677
+ return "B";
678
+ }
679
+ };
680
+ function parseCommentShapeColor(str) {
681
+ switch (str) {
682
+ case "G":
683
+ return "green";
684
+ case "R":
685
+ return "red";
686
+ case "Y":
687
+ return "yellow";
688
+ case "B":
689
+ return "blue";
690
+ default:
691
+ return;
692
+ }
693
+ }
694
+ const makeCommentShape = (shape) => shape.to === shape.from
695
+ ? `${makeCommentShapeColor(shape.color)}${makeSquare(shape.to)}`
696
+ : `${makeCommentShapeColor(shape.color)}${makeSquare(shape.from)}${makeSquare(shape.to)}`;
697
+ const parseCommentShape = (str) => {
698
+ const color = parseCommentShapeColor(str.slice(0, 1));
699
+ const from = parseSquare(str.slice(1, 3));
700
+ const to = parseSquare(str.slice(3, 5));
701
+ if (!color || !defined(from))
702
+ return;
703
+ if (str.length === 3)
704
+ return { color, from, to: from };
705
+ if (str.length === 5 && defined(to))
706
+ return { color, from, to };
707
+ return;
708
+ };
709
+ const makeEval = (ev) => {
710
+ const str = isMate(ev) ? "#" + ev.mate : ev.pawns.toFixed(2);
711
+ return defined(ev.depth) ? str + "," + ev.depth : str;
712
+ };
713
+ export const makeComment = (comment) => {
714
+ const builder = [];
715
+ if (defined(comment.text))
716
+ builder.push(comment.text);
717
+ const circles = (comment.shapes || [])
718
+ .filter((shape) => shape.to === shape.from)
719
+ .map(makeCommentShape);
720
+ if (circles.length)
721
+ builder.push(`[%csl ${circles.join(",")}]`);
722
+ const arrows = (comment.shapes || [])
723
+ .filter((shape) => shape.to !== shape.from)
724
+ .map(makeCommentShape);
725
+ if (arrows.length)
726
+ builder.push(`[%cal ${arrows.join(",")}]`);
727
+ if (comment.evaluation)
728
+ builder.push(`[%eval ${makeEval(comment.evaluation)}]`);
729
+ if (defined(comment.emt))
730
+ builder.push(`[%emt ${makeClk(comment.emt)}]`);
731
+ if (defined(comment.clock))
732
+ builder.push(`[%clk ${makeClk(comment.clock)}]`);
733
+ return builder.join(" ");
734
+ };
735
+ export const parseComment = (comment) => {
736
+ let emt, clock, evaluation;
737
+ const shapes = [];
738
+ const text = comment
739
+ .replace(/\s?\[%(emt|clk)\s(\d{1,5}):(\d{1,2}):(\d{1,2}(?:\.\d{0,3})?)\]\s?/g, (_, annotation, hours, minutes, seconds) => {
740
+ const value = parseInt(hours, 10) * 3600 + parseInt(minutes, 10) * 60 + parseFloat(seconds);
741
+ if (annotation === "emt")
742
+ emt = value;
743
+ else if (annotation === "clk")
744
+ clock = value;
745
+ return " ";
746
+ })
747
+ .replace(/\s?\[%(?:csl|cal)\s([RGYB][a-h][1-8](?:[a-h][1-8])?(?:,[RGYB][a-h][1-8](?:[a-h][1-8])?)*)\]\s?/g, (_, arrows) => {
748
+ for (const arrow of arrows.split(",")) {
749
+ shapes.push(parseCommentShape(arrow));
750
+ }
751
+ return " ";
752
+ })
753
+ .replace(/\s?\[%eval\s(?:#([+-]?\d{1,5})|([+-]?(?:\d{1,5}|\d{0,5}\.\d{1,2})))(?:,(\d{1,5}))?\]\s?/g, (_, mate, pawns, d) => {
754
+ const depth = d && parseInt(d, 10);
755
+ evaluation = mate
756
+ ? { mate: parseInt(mate, 10), depth }
757
+ : { pawns: parseFloat(pawns), depth };
758
+ return " ";
759
+ })
760
+ .trim();
761
+ return {
762
+ text,
763
+ shapes,
764
+ emt,
765
+ clock,
766
+ evaluation,
767
+ };
768
+ };
769
+ //# sourceMappingURL=pgn.js.map