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