@echecs/pgn 3.1.2 → 3.4.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/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to
7
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
+
9
+ ## [Unreleased]
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Adrian de la Rosa
3
+ Copyright (c) 2025 Adrian de la Rosa
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,140 +1,143 @@
1
1
  # PGN
2
2
 
3
- `PGN` is a parser that is part of the **ECHECS** project, designed to interpret
4
- the
5
- [PGN (Portable Game Notation) specification](http://www.saremba.de/chessgml/standards/pgn/pgn-complete.htm).
3
+ [![npm](https://img.shields.io/npm/v/@echecs/pgn)](https://www.npmjs.com/package/@echecs/pgn)
4
+ [![Test](https://github.com/mormubis/pgn/actions/workflows/test.yml/badge.svg)](https://github.com/mormubis/pgn/actions/workflows/test.yml)
5
+ [![Coverage](https://codecov.io/gh/mormubis/pgn/branch/main/graph/badge.svg)](https://codecov.io/gh/mormubis/pgn)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ **PGN** is a fast TypeScript parser for
9
+ [Portable Game Notation](http://www.saremba.de/chessgml/standards/pgn/pgn-complete.htm)
10
+ — the standard format for recording chess games.
11
+
12
+ It parses PGN input into structured move objects with decomposed SAN, paired
13
+ white/black moves, and full support for annotations and variations. Zero runtime
14
+ dependencies.
15
+
16
+ ## Why this library?
17
+
18
+ Most PGN parsers on npm either give you raw strings with no structure, or fail
19
+ on anything beyond a plain game record. If you're building a chess engine,
20
+ opening book, or game viewer, you need more:
21
+
22
+ - **Decomposed SAN** — every move is parsed into `piece`, `from`, `to`,
23
+ `capture`, `promotion`, `check`, and `checkmate` fields. No regex on your
24
+ side.
25
+ - **Paired move structure** — moves are returned as
26
+ `[moveNumber, whiteMove, blackMove]` tuples, ready to render or process
27
+ without further work.
28
+ - **RAV support** — recursive annotation variations (`(...)` sub-lines) are
29
+ parsed into a `variants` tree on each move. Essential for opening books and
30
+ annotated games.
31
+ - **NAG support** — symbolic (`!`, `?`, `!!`, `??`, `!?`, `?!`) and numeric
32
+ (`$1`–`$255`) annotations are surfaced as an `annotations` array. Essential
33
+ for Lichess and ChessBase exports.
34
+ - **Multi-game files** — parse entire PGN databases in one call. Tested on files
35
+ with 3 500+ games.
36
+ - **Fast** — built on a [Peggy](https://peggyjs.org/) PEG parser. Throughput is
37
+ within 1.1–1.2x of the fastest parsers on npm, which do far less work per move
38
+ (see [BENCHMARK_RESULTS.md](./BENCHMARK_RESULTS.md)).
39
+
40
+ If you only need raw SAN strings and a flat move list, any PGN parser will do.
41
+ If you need structured, engine-ready output with annotations and variations,
42
+ this is the one.
6
43
 
7
44
  ## Installation
8
45
 
9
46
  ```bash
10
- npm install --save-dev @echecs/pgn
47
+ npm install @echecs/pgn
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ```typescript
53
+ import parse from '@echecs/pgn';
54
+
55
+ const games = parse(`
56
+ [Event "Example"]
57
+ [White "Player1"]
58
+ [Black "Player2"]
59
+ [Result "1-0"]
60
+
61
+ 1. e4 e5 2. Nf3 Nc6 3. Bb5 1-0
62
+ `);
63
+
64
+ console.log(games[0].moves[0]);
65
+ // [1, { piece: 'P', to: 'e4' }, { piece: 'P', to: 'e5' }]
11
66
  ```
12
67
 
13
68
  ## Usage
14
69
 
15
- The `parse` function takes a PGN formatted string as input and returns an array
16
- of parsed PGN objects.
70
+ `parse()` takes a PGN string and returns an array of game objects — one per game
71
+ in the file.
17
72
 
18
73
  ```typescript
19
74
  parse(input: string): PGN[]
20
75
  ```
21
76
 
22
- ### PGN Object Format
23
-
24
- Here’s the structure of the `PGN` object:
25
-
26
- #### PGN Object
77
+ ### PGN object
27
78
 
28
79
  ```typescript
29
80
  {
30
- "meta": Meta,
31
- "moves": Moves,
32
- "result": "1-0" // possible values: "1-0", "0-1", "1/2-1/2", "?"
81
+ meta: Meta, // tag pairs (Event, Site, Date, White, Black, …)
82
+ moves: Moves, // paired move list
83
+ result: 1 | 0 | 0.5 | '?'
33
84
  }
34
85
  ```
35
86
 
36
- #### Meta Object
37
-
38
- The `meta` object contains metadata about the chess game.
87
+ ### Move object
39
88
 
40
89
  ```typescript
41
90
  {
42
- "Event": "name of the tournament or match event",
43
- "Site": "location of the event",
44
- "Date": "starting date of the game",
45
- "Round": "playing round ordinal of the game",
46
- "White": "player of the white pieces",
47
- "Black": "player of the black pieces",
48
- "Result": "result of the game",
49
- // Any other additional tags
50
- [key]: "string"
91
+ piece: 'P' | 'R' | 'N' | 'B' | 'Q' | 'K', // always present
92
+ to: string, // destination square, e.g. "e4"
93
+ from?: string, // disambiguation, e.g. "e" or "e2"
94
+ capture?: true,
95
+ castling?: true,
96
+ check?: true,
97
+ checkmate?: true,
98
+ promotion?: 'R' | 'N' | 'B' | 'Q',
99
+ annotations?: string[], // e.g. ["!", "$14"]
100
+ comment?: string,
101
+ variants?: Moves[], // recursive annotation variations
51
102
  }
52
103
  ```
53
104
 
54
- #### Moves Array
105
+ Moves are grouped into tuples: `[moveNumber, whiteMove, blackMove]`. If the last
106
+ move of a game or variation was made by white, `blackMove` is `undefined`.
55
107
 
56
- `Moves` is an array representing the sequence of moves in the game. Each element
57
- is an array containing the move number, the white move, and the black move.
108
+ ### Annotations and comments
58
109
 
59
- ```typescript
60
- [moveNumber, Move, Move];
110
+ ```pgn
111
+ 12. Nf3! $14 { White has a slight advantage }
61
112
  ```
62
113
 
63
- Note: Half moves are included for variations or in cases where the last move was
64
- made by white.
65
-
66
- #### Move Object
67
-
68
- Each move is represented by the following structure:
69
-
70
114
  ```typescript
71
115
  {
72
- "annotations": ["!", "$126"], // optional, annotations for the move
73
- "capture": false, // optional, indicates if any piece was captured
74
- "castling": true, // optional, indicates if the move was castling
75
- "check": false, // optional, indicates if the move put the rival king in check
76
- "checkmate": false, // optional, indicates if it is a checkmate
77
- "comment": "Some comment", // optional, comment about the move
78
- "from": "e", // optional, disambiguation of the move
79
- "piece": "K", // required, type of piece (P, R, N, B, Q, K)
80
- "promotion": "Piece", // optional, promotion piece (R, N, B, Q)
81
- "to": "g1", // required, ending square of the move
82
- "variants": [...] // optional, array of moves for variations following Moves format
116
+ piece: 'N', to: 'f3',
117
+ annotations: ['!', '$14'],
118
+ comment: 'White has a slight advantage'
83
119
  }
84
120
  ```
85
121
 
86
- ### Example
122
+ ### Variations
123
+
124
+ ```pgn
125
+ 5... Ba5 (5... Be7 6. d4) 6. Qb3
126
+ ```
87
127
 
88
- Here's a sample usage of the `PGN` parser:
128
+ The alternative line appears as a `variants` array on the move where it
129
+ branches:
89
130
 
90
131
  ```typescript
91
- import { readFileSync } from 'fs';
92
- import parse from '@echecs/pgn';
93
-
94
- function readFile(path) {
95
- const filename = require.resolve(path);
96
- return readFileSync(filename, 'utf8');
132
+ {
133
+ piece: 'B', to: 'a5',
134
+ variants: [
135
+ [ [5, undefined, { piece: 'B', to: 'e7' }], [6, { piece: 'P', to: 'd4' }] ]
136
+ ]
97
137
  }
98
-
99
- const pgn = parse(readFile('./games/file.pgn'));
100
-
101
- // Output example of parsed `PGN`
102
- console.log(pgn);
103
- /*
104
- [
105
- {
106
- "meta": {
107
- "Event": "Some Tournament",
108
- "Site": "Some Location",
109
- "Date": "2023.10.04",
110
- "Round": "1",
111
- "White": "Player1",
112
- "Black": "Player2",
113
- "Result": "1-0",
114
- // additional tags...
115
- },
116
- "moves": [
117
- [
118
- 1,
119
- { "piece": "P", "to": "e4" },
120
- { "piece": "P", "to": "e5" }
121
- ],
122
- [
123
- 2,
124
- { "piece": "N", "to": "f3" },
125
- { "piece": "N", "to": "c6" }
126
- ],
127
- // more moves...
128
- ],
129
- "result": "1-0"
130
- }
131
- ];
132
- */
133
138
  ```
134
139
 
135
- ## Important Notes
140
+ ## Contributing
136
141
 
137
- - `PGN` is a parser and does not verify the validity of the PGN games. It only
138
- parses the provided content.
139
- - For game validation, use **@echecs/game** as it is responsible for verifying
140
- game correctness as part of the **ECHECS** project.
142
+ Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) for
143
+ guidelines on how to submit issues and pull requests.