@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.
- package/LICENSE.txt +674 -0
- package/README.md +61 -0
- package/dist/cjs/attacks.js +152 -0
- package/dist/cjs/attacks.js.map +1 -0
- package/dist/cjs/board.js +143 -0
- package/dist/cjs/board.js.map +1 -0
- package/dist/cjs/chess.js +638 -0
- package/dist/cjs/chess.js.map +1 -0
- package/dist/cjs/compat.js +89 -0
- package/dist/cjs/compat.js.map +1 -0
- package/dist/cjs/debug.js +103 -0
- package/dist/cjs/debug.js.map +1 -0
- package/dist/cjs/fen.js +325 -0
- package/dist/cjs/fen.js.map +1 -0
- package/dist/cjs/index.js +94 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/pgn.js +796 -0
- package/dist/cjs/pgn.js.map +1 -0
- package/dist/cjs/san.js +174 -0
- package/dist/cjs/san.js.map +1 -0
- package/dist/cjs/setup.js +167 -0
- package/dist/cjs/setup.js.map +1 -0
- package/dist/cjs/squareSet.js +206 -0
- package/dist/cjs/squareSet.js.map +1 -0
- package/dist/cjs/transform.js +57 -0
- package/dist/cjs/transform.js.map +1 -0
- package/dist/cjs/types.js +24 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/util.js +104 -0
- package/dist/cjs/util.js.map +1 -0
- package/dist/cjs/variant.js +833 -0
- package/dist/cjs/variant.js.map +1 -0
- package/dist/esm/attacks.js +140 -0
- package/dist/esm/attacks.js.map +1 -0
- package/dist/esm/board.js +138 -0
- package/dist/esm/board.js.map +1 -0
- package/dist/esm/chess.js +624 -0
- package/dist/esm/chess.js.map +1 -0
- package/dist/esm/compat.js +81 -0
- package/dist/esm/compat.js.map +1 -0
- package/dist/esm/debug.js +94 -0
- package/dist/esm/debug.js.map +1 -0
- package/dist/esm/fen.js +308 -0
- package/dist/esm/fen.js.map +1 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/pgn.js +769 -0
- package/dist/esm/pgn.js.map +1 -0
- package/dist/esm/san.js +167 -0
- package/dist/esm/san.js.map +1 -0
- package/dist/esm/setup.js +157 -0
- package/dist/esm/setup.js.map +1 -0
- package/dist/esm/squareSet.js +202 -0
- package/dist/esm/squareSet.js.map +1 -0
- package/dist/esm/transform.js +48 -0
- package/dist/esm/transform.js.map +1 -0
- package/dist/esm/types.js +19 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/util.js +87 -0
- package/dist/esm/util.js.map +1 -0
- package/dist/esm/variant.js +812 -0
- package/dist/esm/variant.js.map +1 -0
- package/dist/types/attacks.d.ts +58 -0
- package/dist/types/board.d.ts +62 -0
- package/dist/types/chess.d.ts +82 -0
- package/dist/types/compat.d.ts +26 -0
- package/dist/types/debug.d.ts +10 -0
- package/dist/types/fen.d.ts +40 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/pgn.d.ts +203 -0
- package/dist/types/san.d.ts +6 -0
- package/dist/types/setup.d.ts +65 -0
- package/dist/types/squareSet.d.ts +50 -0
- package/dist/types/transform.d.ts +9 -0
- package/dist/types/types.d.ts +58 -0
- package/dist/types/util.d.ts +21 -0
- package/dist/types/variant.d.ts +92 -0
- package/package.json +86 -0
- package/src/attacks.ts +160 -0
- package/src/board.ts +168 -0
- package/src/chess.ts +687 -0
- package/src/compat.ts +120 -0
- package/src/debug.ts +100 -0
- package/src/fen.ts +328 -0
- package/src/index.ts +85 -0
- package/src/pgn.ts +876 -0
- package/src/san.ts +190 -0
- package/src/setup.ts +203 -0
- package/src/squareSet.ts +243 -0
- package/src/transform.ts +49 -0
- package/src/types.ts +93 -0
- package/src/util.ts +116 -0
- package/src/variant.ts +939 -0
package/dist/cjs/pgn.js
ADDED
|
@@ -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
|