@echecs/pgn 3.1.3 → 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 +9 -0
- package/README.md +100 -97
- package/dist/grammar.cjs +2887 -131
- package/dist/grammar.cjs.map +1 -1
- package/dist/grammar.d.cts +11 -13
- package/dist/index.js +8 -31
- package/dist/index.js.map +1 -1
- package/package.json +31 -16
- package/dist/lexer.d.ts +0 -3
- package/dist/lexer.js +0 -94
- package/dist/lexer.js.map +0 -1
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/README.md
CHANGED
|
@@ -1,140 +1,143 @@
|
|
|
1
1
|
# PGN
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
[
|
|
3
|
+
[](https://www.npmjs.com/package/@echecs/pgn)
|
|
4
|
+
[](https://github.com/mormubis/pgn/actions/workflows/test.yml)
|
|
5
|
+
[](https://codecov.io/gh/mormubis/pgn)
|
|
6
|
+
[](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
|
|
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
|
-
|
|
16
|
-
|
|
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
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
The `meta` object contains metadata about the chess game.
|
|
87
|
+
### Move object
|
|
39
88
|
|
|
40
89
|
```typescript
|
|
41
90
|
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
is an array containing the move number, the white move, and the black move.
|
|
108
|
+
### Annotations and comments
|
|
58
109
|
|
|
59
|
-
```
|
|
60
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
###
|
|
122
|
+
### Variations
|
|
123
|
+
|
|
124
|
+
```pgn
|
|
125
|
+
5... Ba5 (5... Be7 6. d4) 6. Qb3
|
|
126
|
+
```
|
|
87
127
|
|
|
88
|
-
|
|
128
|
+
The alternative line appears as a `variants` array on the move where it
|
|
129
|
+
branches:
|
|
89
130
|
|
|
90
131
|
```typescript
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
##
|
|
140
|
+
## Contributing
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
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.
|