pgn-manager 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -12,6 +12,8 @@ declare class PGNManager {
12
12
  private sortedMoves;
13
13
  /** Map of moves to their FEN position strings */
14
14
  private moveFen;
15
+ /** Map of FEN position string to their move object */
16
+ private fenMove;
15
17
  /** Map of moves to their parent variations (or null for mainline) */
16
18
  private moveParent;
17
19
  /** Map of variations to their parent moves */
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ class PGNManager {
16
16
  sortedMoves;
17
17
  /** Map of moves to their FEN position strings */
18
18
  moveFen;
19
+ /** Map of FEN position string to their move object */
20
+ fenMove = new Map();
19
21
  /** Map of moves to their parent variations (or null for mainline) */
20
22
  moveParent;
21
23
  /** Map of variations to their parent moves */
@@ -33,6 +35,7 @@ class PGNManager {
33
35
  this.moveParent = new Map();
34
36
  this.ravParent = new Map();
35
37
  this.moveFen = new Map();
38
+ this.fenMove = new Map();
36
39
  this.moveColor = new Map();
37
40
  this.dfOnGame(this.game);
38
41
  }
@@ -42,7 +45,8 @@ class PGNManager {
42
45
  */
43
46
  dfOnGame = (game) => {
44
47
  this.sortedMoves = [];
45
- var chessGame = new Chess(exports.FEN_START_POSITION);
48
+ var chessGame = new Chess(game.headers?.find((h) => h.name.toUpperCase() === "FEN")?.value ||
49
+ exports.FEN_START_POSITION);
46
50
  for (let move of game.moves) {
47
51
  this.dfsOnGame(move, game, chessGame);
48
52
  }
@@ -70,6 +74,7 @@ class PGNManager {
70
74
  !chessGame.move(move.move, { sloppy: true }))
71
75
  console.log("Invalid move: " + move.move);
72
76
  this.moveFen.set(move, chessGame.fen());
77
+ this.fenMove.set(chessGame.fen(), move);
73
78
  this.moveColor.set(move, chessGame.turn() === "w" ? "b" : "w");
74
79
  };
75
80
  /**
@@ -267,56 +272,99 @@ class PGNManager {
267
272
  * @throws Error if the move parameter is invalid
268
273
  */
269
274
  pushMove = (moveId, newMove, result = "*") => {
270
- const currentMove = this.getMove(moveId);
271
- // check if it's a valid move
272
- const chess = new Chess(this.getMoveFen(currentMove));
273
- if (!chess.move(newMove)) {
274
- throw Error("Invalid move");
275
+ let chess;
276
+ let parentRav;
277
+ let current = null;
278
+ // 1) Initialize Chess and parent variation
279
+ if (moveId === 0) {
280
+ parentRav = this.parsedPGN;
281
+ const fenHdr = this.headers.find((h) => h.name.toLowerCase() === "fen");
282
+ const startFen = fenHdr ? fenHdr.value : exports.FEN_START_POSITION;
283
+ chess = new Chess(startFen);
284
+ }
285
+ else {
286
+ current = this.getMove(moveId);
287
+ if (!current)
288
+ throw new Error("Invalid moveId while pushing a new move!");
289
+ parentRav = this.moveParent.get(current) || this.parsedPGN;
290
+ chess = new Chess(this.getMoveFen(current));
275
291
  }
276
- // check if this is a new variation
277
- let isNewVariation = false;
278
- const parentRav = this.getParentRav(currentMove);
279
- if (parentRav.moves[parentRav.moves.length - 1] != currentMove) {
280
- isNewVariation = true;
292
+ // 2) Play the SAN move (strict → sloppy)
293
+ const played = chess.move(newMove, { sloppy: false }) ||
294
+ chess.move(newMove, { sloppy: true });
295
+ if (!played)
296
+ throw new Error("Invalid move");
297
+ const san = chess.history().slice(-1)[0];
298
+ const nextToMove = chess.turn(); // 'w' or 'b'
299
+ // If the FEN already exists, we are trying to add a move that is already played
300
+ if (this.fenMove.has(chess.fen())) {
301
+ return this.fenMove.get(chess.fen());
281
302
  }
282
- // add the move to the game
283
- let move = undefined;
284
- if (isNewVariation) {
285
- move = {
286
- move: chess.history().slice(-1)[0],
287
- ravs: undefined,
288
- move_number: currentMove.move_number,
289
- comments: [],
290
- };
291
- const newVar = {
292
- moves: [move],
293
- result,
294
- };
295
- currentMove.ravs.push(newVar);
296
- // update class variables
297
- this.moveParent.set(move, newVar);
298
- this.ravParent.set(newVar, currentMove);
299
- this.moveFen.set(move, chess.fen());
300
- this.moveColor.set(move, this.getMoveColor(currentMove));
303
+ // 3) Build move object with provisional move_number
304
+ const provisionalNumber = (() => {
305
+ if (!current) {
306
+ return 1; // brand‐new mainline
307
+ }
308
+ const currNum = current.move_number || this.previousMove(current)?.move_number;
309
+ const currColor = this.getMoveColor(current);
310
+ return currColor === "w" ? currNum : currNum + 1;
311
+ })();
312
+ const moveObj = {
313
+ move: san,
314
+ ravs: undefined,
315
+ move_number: provisionalNumber,
316
+ comments: [],
317
+ };
318
+ // 4) Insert into structure & flag first‐of‐variation
319
+ let isFirstOfVariation = false;
320
+ if (!current) {
321
+ // brand-new mainline => variation of first move if exists
322
+ const first = parentRav.moves[0];
323
+ if (first) {
324
+ isFirstOfVariation = true;
325
+ const newRav = { moves: [moveObj], result };
326
+ first.ravs = first.ravs || [];
327
+ first.ravs.push(newRav);
328
+ this.moveParent.set(moveObj, newRav);
329
+ this.ravParent.set(newRav, first);
330
+ }
331
+ else {
332
+ parentRav.moves.push(moveObj);
333
+ this.moveParent.set(moveObj, parentRav);
334
+ }
301
335
  }
302
336
  else {
303
- move = {
304
- move: chess.history().slice(-1)[0],
305
- ravs: undefined,
306
- move_number: this.getMoveColor(currentMove) === "w"
307
- ? undefined
308
- : currentMove.move_number + 1,
309
- comments: [],
310
- };
311
- parentRav.moves.push(move);
312
- // update class variables
313
- this.moveParent.set(move, parentRav);
314
- this.moveFen.set(move, chess.fen());
315
- this.moveColor.set(move, this.getMoveColor(currentMove) === "w" ? "b" : "w");
337
+ // existing‐move branch
338
+ const lastInRav = parentRav.moves[parentRav.moves.length - 1];
339
+ const isContinuation = lastInRav === current;
340
+ if (!isContinuation) {
341
+ // new variation off the next move
342
+ isFirstOfVariation = true;
343
+ const anchor = this.nextMove(current) || current;
344
+ anchor.ravs = anchor.ravs || [];
345
+ const newRav = { moves: [moveObj], result };
346
+ anchor.ravs.push(newRav);
347
+ this.moveParent.set(moveObj, newRav);
348
+ this.ravParent.set(newRav, anchor);
349
+ }
350
+ else {
351
+ // continuation
352
+ parentRav.moves.push(moveObj);
353
+ this.moveParent.set(moveObj, parentRav);
354
+ }
355
+ }
356
+ // 5) Conditionally remove move_number
357
+ // Keep it only if first‐of‐variation OR new move is White to play
358
+ if (!(isFirstOfVariation || nextToMove === "b")) {
359
+ delete moveObj.move_number;
316
360
  }
361
+ // 6) Final bookkeeping
362
+ this.moveFen.set(moveObj, chess.fen());
363
+ this.fenMove.set(chess.fen(), moveObj);
364
+ this.moveColor.set(moveObj, nextToMove === "w" ? "b" : "w");
317
365
  this.rawPGN = (0, utils_1.regeneratePGN)(this.game, this.moveColor);
318
366
  this.dfOnGame(this.game);
319
- return move;
367
+ return moveObj;
320
368
  };
321
369
  /**
322
370
  * Delete a move and all subsequent moves in its variation from the game
@@ -348,6 +396,7 @@ class PGNManager {
348
396
  // clean up all maps at once
349
397
  movsToClean.forEach((move) => {
350
398
  this.moveParent.delete(move);
399
+ this.fenMove.delete(this.getMoveFen(move));
351
400
  this.moveFen.delete(move);
352
401
  this.moveColor.delete(move);
353
402
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgn-manager",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Libraray built on top of chess.js and pgn-parser to load and process PGN files in typescript.",
5
5
  "main": "dist/index",
6
6
  "typings": "dist/index",