pgn-manager 1.0.1 → 2.0.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/README.md CHANGED
@@ -6,7 +6,6 @@ A powerful TypeScript/JavaScript library for managing chess PGN (Portable Game N
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
8
8
  [![npm downloads](https://img.shields.io/npm/dm/pgn-manager.svg)](https://www.npmjs.com/package/pgn-manager)
9
- [![Coverage Status](https://coveralls.io/repos/github/username/pgn-manager/badge.svg?branch=main)](https://coveralls.io/github/username/pgn-manager?branch=main)
10
9
 
11
10
 
12
11
  ## Features ✨
@@ -58,14 +57,15 @@ const headers = manager.headers;
58
57
 
59
58
  ### Methods
60
59
  - `getMove(moveNumber: number)`: Get move by number
61
- - `getMoveNumber(move: Move)`: Get number for a move
62
- - `nextMove(move: Move)`: Get next move in the sequence
63
- - `previousMove(move: Move)`: Get previous move
64
- - `hasNextMove(move: Move)`: Check if move has a next move
60
+ - `getMoveNumber(moveOrMoveId: Move | number)`: Get number for a move
61
+ - `nextMove(moveOrMoveId: Move | number)`: Get next move in the sequence
62
+ - `previousMove(moveOrMoveId: Move | number)`: Get previous move
63
+ - `hasNextMove(moveOrMoveId: Move | number)`: Check if move has a next move
65
64
  - `getFirstMove()`: Get the first move of the game
66
65
  - `getLastMove()`: Get the last move of the game
67
- - `getMoveFen(move: Move)`: Get FEN position after move
68
- - `getParentRav(move: Move)`: Get parent variation for move
66
+ - `getMoveFen(moveOrMoveId: Move | number)`: Get FEN position after move
67
+ - `getParentRav(moveOrMoveId: Move | number)`: Get parent variation for move
68
+ - `getMoveColor(moveOrMoveId: Move | number)`: Gets the color of the player who made the move ("w" for white or "b" for black)
69
69
 
70
70
  ## Examples 🎯
71
71
 
package/dist/index.d.ts CHANGED
@@ -1,27 +1,134 @@
1
- import { ParsedPGN, Move, Rav, Header } from "pgn-parser";
1
+ import { ShortMove } from "chess.js";
2
+ import * as pgnParser from "pgn-parser";
3
+ import type { ParsedPGN, Move, Rav, Header, Result } from "pgn-parser";
2
4
  export declare const FEN_START_POSITION = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
3
5
  export declare const FEN_EMPTY_POSITION = "8/8/8/8/8/8/8/8";
4
6
  declare class PGNManager {
7
+ /** The raw PGN string input */
5
8
  private rawPGN;
9
+ /** The parsed PGN game object */
6
10
  private game;
11
+ /** Array of moves in traversal order */
7
12
  private sortedMoves;
13
+ /** Map of moves to their FEN position strings */
8
14
  private moveFen;
15
+ /** Map of moves to their parent variations (or null for mainline) */
9
16
  private moveParent;
17
+ /** Map of variations to their parent moves */
10
18
  private ravParent;
19
+ /** Map of moves to the color of the player who made the move */
20
+ private moveColor;
21
+ /**
22
+ * Creates a new PGNManager instance
23
+ * @param pgn - The PGN string to parse and manage
24
+ */
11
25
  constructor(pgn: string);
26
+ /**
27
+ * Initializes the game traversal starting from the initial position
28
+ * @param game - The parsed PGN game object
29
+ */
12
30
  private dfOnGame;
31
+ /**
32
+ * Performs depth-first traversal of the game moves and variations
33
+ * @param move - The current move being processed
34
+ * @param parent - The parent RAV (variation) containing the move
35
+ * @param chessGame - The chess instance for the current position
36
+ */
13
37
  private dfsOnGame;
38
+ /**
39
+ * Gets the raw PGN string
40
+ * @returns The original PGN string
41
+ */
14
42
  get pgn(): string;
43
+ /**
44
+ * Gets the parsed PGN object
45
+ * @returns The parsed PGN game object
46
+ */
15
47
  get parsedPGN(): ParsedPGN;
48
+ /**
49
+ * Gets the game headers
50
+ * @returns Array of game headers
51
+ */
16
52
  get headers(): Array<Header>;
17
- getMove: (moveNumber: number) => Move;
53
+ /**
54
+ * Gets a move by its number in the sequence
55
+ * @param moveNumber - The 1-based index of the move
56
+ * @returns The move object at the specified position
57
+ */
58
+ getMove: (moveNumber: number) => pgnParser.Move;
59
+ /**
60
+ * Gets the number of a move in the sequence
61
+ * @param move - The move object
62
+ * @returns The 1-based index of the move
63
+ */
18
64
  getMoveNumber: (move: Move) => number;
19
- nextMove: (move: Move | undefined) => Move;
20
- hasNextMove: (move: Move) => boolean;
21
- previousMove: (move: Move) => Move | undefined;
22
- getFirstMove: () => Move;
23
- getLastMove: () => any;
24
- getMoveFen: (move: Move) => string;
25
- getParentRav: (move: Move) => Rav | null;
65
+ /**
66
+ * Gets the next move in the sequence
67
+ * @param moveOrId - The current move object or move number
68
+ * @returns The next move in the sequence
69
+ * @throws Error if there are no moves in the game
70
+ */
71
+ nextMove: (moveOrId: Move | number | undefined) => Move;
72
+ /**
73
+ * Checks if there is a next move available
74
+ * @param moveOrId - The current move object or move number
75
+ * @returns True if there is a next move, false otherwise
76
+ */
77
+ hasNextMove: (moveOrId: Move | number) => boolean;
78
+ /**
79
+ * Gets the previous move in the sequence
80
+ * @param moveOrId - The current move object or move number
81
+ * @returns The previous move or undefined if at the start
82
+ * @throws Error if there are no moves or if the move parameter is invalid
83
+ */
84
+ previousMove: (moveOrId: Move | number) => Move | undefined;
85
+ /**
86
+ * Gets the first move in the game
87
+ * @returns The first move
88
+ * @throws Error if there are no moves in the game
89
+ */
90
+ getFirstMove: () => pgnParser.Move;
91
+ /**
92
+ * Gets the last move in the game
93
+ * @returns The last move
94
+ * @throws Error if there are no moves in the game
95
+ */
96
+ getLastMove: () => pgnParser.Move;
97
+ /**
98
+ * Gets the FEN string for a specific move
99
+ * @param moveOrId - The move object or move number
100
+ * @returns The FEN string representing the position after the move
101
+ * @throws Error if the move parameter is invalid
102
+ */
103
+ getMoveFen: (moveOrId: Move | number) => string;
104
+ /**
105
+ * Gets the parent RAV (variation) for a move
106
+ * @param moveOrId - The move object or move number
107
+ * @returns The parent RAV or null if the move is in the main line
108
+ * @throws Error if the move parameter is invalid
109
+ */
110
+ getParentRav: (moveOrId: Move | number) => Rav | null;
111
+ /**
112
+ * Gets the color of the player who made the move
113
+ * @param moveOrId - The move object or move ID number
114
+ * @returns "w" for white or "b" for black
115
+ * @throws Error if the move parameter is invalid
116
+ */
117
+ getMoveColor: (moveOrId: Move | number) => "w" | "b";
118
+ /***
119
+ * Pushes a new move into the game
120
+ * @param moveId - The ID of the move to push
121
+ * @param newMove - The move object to add
122
+ * @param result - The result of the game after this move (default is "*")
123
+ * @returns The newly created move object
124
+ * @throws Error if the move parameter is invalid
125
+ */
126
+ pushMove: (moveId: number, newMove: ShortMove, result?: Result) => Move;
127
+ /**
128
+ * Delete a move and all subsequent moves in its variation from the game
129
+ * @param moveId - The ID of the move to delete from
130
+ * @throws Error if the move parameter is invalid
131
+ */
132
+ deleteMove: (moveId: number) => void;
26
133
  }
27
134
  export default PGNManager;
package/dist/index.js CHANGED
@@ -3,25 +3,43 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FEN_EMPTY_POSITION = exports.FEN_START_POSITION = void 0;
4
4
  const ChessJS = require("chess.js");
5
5
  const Chess = typeof ChessJS === "function" ? ChessJS : ChessJS.Chess;
6
- const pgn_parser_1 = require("pgn-parser");
6
+ const pgnParser = require("pgn-parser");
7
+ const utils_1 = require("./utils");
7
8
  exports.FEN_START_POSITION = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
8
9
  exports.FEN_EMPTY_POSITION = "8/8/8/8/8/8/8/8";
9
10
  class PGNManager {
11
+ /** The raw PGN string input */
10
12
  rawPGN;
13
+ /** The parsed PGN game object */
11
14
  game;
15
+ /** Array of moves in traversal order */
12
16
  sortedMoves;
17
+ /** Map of moves to their FEN position strings */
13
18
  moveFen;
19
+ /** Map of moves to their parent variations (or null for mainline) */
14
20
  moveParent;
21
+ /** Map of variations to their parent moves */
15
22
  ravParent;
23
+ /** Map of moves to the color of the player who made the move */
24
+ moveColor;
25
+ /**
26
+ * Creates a new PGNManager instance
27
+ * @param pgn - The PGN string to parse and manage
28
+ */
16
29
  constructor(pgn) {
17
30
  this.rawPGN = pgn;
18
- this.game = pgn_parser_1.default.parse(pgn + " *")[0];
31
+ this.game = pgnParser.parse(pgn + " *")[0];
19
32
  this.sortedMoves = [];
20
33
  this.moveParent = new Map();
21
34
  this.ravParent = new Map();
22
35
  this.moveFen = new Map();
36
+ this.moveColor = new Map();
23
37
  this.dfOnGame(this.game);
24
38
  }
39
+ /**
40
+ * Initializes the game traversal starting from the initial position
41
+ * @param game - The parsed PGN game object
42
+ */
25
43
  dfOnGame = (game) => {
26
44
  this.sortedMoves = [];
27
45
  var chessGame = new Chess(exports.FEN_START_POSITION);
@@ -29,6 +47,12 @@ class PGNManager {
29
47
  this.dfsOnGame(move, game, chessGame);
30
48
  }
31
49
  };
50
+ /**
51
+ * Performs depth-first traversal of the game moves and variations
52
+ * @param move - The current move being processed
53
+ * @param parent - The parent RAV (variation) containing the move
54
+ * @param chessGame - The chess instance for the current position
55
+ */
32
56
  dfsOnGame = (move, parent, chessGame) => {
33
57
  this.sortedMoves.push(move);
34
58
  this.moveParent.set(move, parent);
@@ -46,26 +70,62 @@ class PGNManager {
46
70
  !chessGame.move(move.move, { sloppy: true }))
47
71
  console.log("Invalid move: " + move.move);
48
72
  this.moveFen.set(move, chessGame.fen());
73
+ this.moveColor.set(move, chessGame.turn() === "w" ? "b" : "w");
49
74
  };
75
+ /**
76
+ * Gets the raw PGN string
77
+ * @returns The original PGN string
78
+ */
50
79
  get pgn() {
51
80
  return this.rawPGN;
52
81
  }
82
+ /**
83
+ * Gets the parsed PGN object
84
+ * @returns The parsed PGN game object
85
+ */
53
86
  get parsedPGN() {
54
87
  return this.game;
55
88
  }
89
+ /**
90
+ * Gets the game headers
91
+ * @returns Array of game headers
92
+ */
56
93
  get headers() {
57
94
  if (!this.game || !this.game.headers)
58
95
  return [];
59
96
  return this.game.headers;
60
97
  }
98
+ /**
99
+ * Gets a move by its number in the sequence
100
+ * @param moveNumber - The 1-based index of the move
101
+ * @returns The move object at the specified position
102
+ */
61
103
  getMove = (moveNumber) => {
62
104
  let move = this.sortedMoves[moveNumber - 1];
63
105
  return move;
64
106
  };
107
+ /**
108
+ * Gets the number of a move in the sequence
109
+ * @param move - The move object
110
+ * @returns The 1-based index of the move
111
+ */
65
112
  getMoveNumber = (move) => {
66
113
  return this.sortedMoves.indexOf(move) + 1;
67
114
  };
68
- nextMove = (move) => {
115
+ /**
116
+ * Gets the next move in the sequence
117
+ * @param moveOrId - The current move object or move number
118
+ * @returns The next move in the sequence
119
+ * @throws Error if there are no moves in the game
120
+ */
121
+ nextMove = (moveOrId) => {
122
+ let move;
123
+ if (typeof moveOrId === "number") {
124
+ move = this.getMove(moveOrId);
125
+ }
126
+ else {
127
+ move = moveOrId;
128
+ }
69
129
  if (!move) {
70
130
  if (this.sortedMoves.length == 0) {
71
131
  throw Error("No moves in game");
@@ -92,10 +152,23 @@ class PGNManager {
92
152
  }
93
153
  return tempNextMove;
94
154
  };
95
- hasNextMove = (move) => {
155
+ /**
156
+ * Checks if there is a next move available
157
+ * @param moveOrId - The current move object or move number
158
+ * @returns True if there is a next move, false otherwise
159
+ */
160
+ hasNextMove = (moveOrId) => {
161
+ const move = typeof moveOrId === "number" ? this.getMove(moveOrId) : moveOrId;
96
162
  return !move || this.nextMove(move) !== move;
97
163
  };
98
- previousMove = (move) => {
164
+ /**
165
+ * Gets the previous move in the sequence
166
+ * @param moveOrId - The current move object or move number
167
+ * @returns The previous move or undefined if at the start
168
+ * @throws Error if there are no moves or if the move parameter is invalid
169
+ */
170
+ previousMove = (moveOrId) => {
171
+ const move = typeof moveOrId === "number" ? this.getMove(moveOrId) : moveOrId;
99
172
  if (!move) {
100
173
  if (this.sortedMoves.length == 0) {
101
174
  throw Error("No moves in game");
@@ -122,31 +195,168 @@ class PGNManager {
122
195
  }
123
196
  return tempPrevMove;
124
197
  };
198
+ /**
199
+ * Gets the first move in the game
200
+ * @returns The first move
201
+ * @throws Error if there are no moves in the game
202
+ */
125
203
  getFirstMove = () => {
126
204
  if (this.sortedMoves.length == 0) {
127
205
  throw Error("No moves in game");
128
206
  }
129
207
  return this.sortedMoves[0];
130
208
  };
209
+ /**
210
+ * Gets the last move in the game
211
+ * @returns The last move
212
+ * @throws Error if there are no moves in the game
213
+ */
131
214
  getLastMove = () => {
132
215
  if (this.sortedMoves.length == 0) {
133
216
  throw Error("No moves in game");
134
217
  }
135
218
  return this.game.moves[this.game.moves.length - 1];
136
219
  };
137
- getMoveFen = (move) => {
138
- if (!move) {
220
+ /**
221
+ * Gets the FEN string for a specific move
222
+ * @param moveOrId - The move object or move number
223
+ * @returns The FEN string representing the position after the move
224
+ * @throws Error if the move parameter is invalid
225
+ */
226
+ getMoveFen = (moveOrId) => {
227
+ const move = typeof moveOrId === "number" ? this.getMove(moveOrId) : moveOrId;
228
+ if (!move || !this.moveFen.has(move)) {
139
229
  throw Error("Invalid 'move' parameter while getting fen");
140
230
  }
141
231
  let moveFen = this.moveFen.get(move);
142
232
  return moveFen ? moveFen : exports.FEN_EMPTY_POSITION;
143
233
  };
144
- getParentRav = (move) => {
145
- if (!move) {
234
+ /**
235
+ * Gets the parent RAV (variation) for a move
236
+ * @param moveOrId - The move object or move number
237
+ * @returns The parent RAV or null if the move is in the main line
238
+ * @throws Error if the move parameter is invalid
239
+ */
240
+ getParentRav = (moveOrId) => {
241
+ const move = typeof moveOrId === "number" ? this.getMove(moveOrId) : moveOrId;
242
+ if (!move || !this.moveParent.has(move)) {
146
243
  throw Error("Invalid 'move' parameter while getting parent rav");
147
244
  }
148
245
  let parentRav = this.moveParent.get(move);
149
246
  return parentRav ? parentRav : null;
150
247
  };
248
+ /**
249
+ * Gets the color of the player who made the move
250
+ * @param moveOrId - The move object or move ID number
251
+ * @returns "w" for white or "b" for black
252
+ * @throws Error if the move parameter is invalid
253
+ */
254
+ getMoveColor = (moveOrId) => {
255
+ const move = typeof moveOrId === "number" ? this.getMove(moveOrId) : moveOrId;
256
+ if (!move || !this.moveColor.has(move)) {
257
+ throw Error("Invalid 'move' parameter while getting move color");
258
+ }
259
+ return this.moveColor.get(move);
260
+ };
261
+ /***
262
+ * Pushes a new move into the game
263
+ * @param moveId - The ID of the move to push
264
+ * @param newMove - The move object to add
265
+ * @param result - The result of the game after this move (default is "*")
266
+ * @returns The newly created move object
267
+ * @throws Error if the move parameter is invalid
268
+ */
269
+ 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
+ }
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;
281
+ }
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));
301
+ }
302
+ 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");
316
+ }
317
+ this.rawPGN = (0, utils_1.regeneratePGN)(this.game, this.moveColor);
318
+ this.dfOnGame(this.game);
319
+ return move;
320
+ };
321
+ /**
322
+ * Delete a move and all subsequent moves in its variation from the game
323
+ * @param moveId - The ID of the move to delete from
324
+ * @throws Error if the move parameter is invalid
325
+ */
326
+ deleteMove = (moveId) => {
327
+ const move = this.getMove(moveId);
328
+ if (!move) {
329
+ throw Error("Invalid move");
330
+ }
331
+ // delete the move and subsequent moves from the game
332
+ const parentRav = this.getParentRav(move);
333
+ const index = parentRav.moves.indexOf(move);
334
+ const deletedMoves = parentRav.moves.splice(index);
335
+ // collect all moves and variations that need cleanup
336
+ const movsToClean = [];
337
+ const ravsToClean = [];
338
+ for (const deletedMove of deletedMoves) {
339
+ movsToClean.push(deletedMove);
340
+ if (deletedMove.ravs) {
341
+ for (const rav of deletedMove.ravs) {
342
+ ravsToClean.push(rav);
343
+ }
344
+ }
345
+ }
346
+ // Sort moves by their moveId in decreasing order
347
+ movsToClean.sort((a, b) => this.getMoveNumber(b) - this.getMoveNumber(a));
348
+ // clean up all maps at once
349
+ movsToClean.forEach((move) => {
350
+ this.moveParent.delete(move);
351
+ this.moveFen.delete(move);
352
+ this.moveColor.delete(move);
353
+ });
354
+ ravsToClean.forEach((rav) => {
355
+ this.ravParent.delete(rav);
356
+ });
357
+ // update the PGN and rebuild internal state
358
+ this.rawPGN = (0, utils_1.regeneratePGN)(this.game, this.moveColor);
359
+ this.dfOnGame(this.game);
360
+ };
151
361
  }
152
362
  exports.default = PGNManager;
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "pgn-manager",
3
- "version": "1.0.1",
3
+ "version": "2.0.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",
7
7
  "scripts": {
8
- "test": "echo \"Error: no test specified\" && exit 1",
8
+ "test": "jest",
9
+ "test:watch": "jest --watch",
10
+ "test:coverage": "jest --coverage",
9
11
  "prepublishOnly": "npm run compile",
10
12
  "compile": "npm run clean && tsc -p .",
11
13
  "watch": "tsc -w -p .",
@@ -31,7 +33,12 @@
31
33
  "pgn-parser": "2.1.0"
32
34
  },
33
35
  "devDependencies": {
34
- "typescript": "4.5.2"
36
+ "@types/chess.js": "^0.11.2",
37
+ "@types/jest": "^30.0.0",
38
+ "@types/pgn-parser": "^2.1.0",
39
+ "jest": "^30.0.4",
40
+ "ts-jest": "^29.4.0",
41
+ "typescript": "^5.8.3"
35
42
  },
36
43
  "files": [
37
44
  "dist/index.js",