pgn-viewer-parser 1.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 +340 -0
- package/dist/cursor/game-cursor.js +110 -0
- package/dist/cursor/game-cursor.js.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/model/move-node.js +23 -0
- package/dist/model/move-node.js.map +1 -0
- package/dist/model/pgn-game.js +18 -0
- package/dist/model/pgn-game.js.map +1 -0
- package/dist/parser/header-parser.js +24 -0
- package/dist/parser/header-parser.js.map +1 -0
- package/dist/parser/pgn-parser.js +118 -0
- package/dist/parser/pgn-parser.js.map +1 -0
- package/dist/parser/tokenizer.js +121 -0
- package/dist/parser/tokenizer.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/utils/annotation-parser.js +40 -0
- package/dist/utils/annotation-parser.js.map +1 -0
- package/dist/utils/nag-symbols.js +56 -0
- package/dist/utils/nag-symbols.js.map +1 -0
- package/dist/viewer/PGNControls.js +71 -0
- package/dist/viewer/PGNControls.js.map +1 -0
- package/dist/viewer/PGNViewer.js +94 -0
- package/dist/viewer/PGNViewer.js.map +1 -0
- package/dist/viewer/index.js +7 -0
- package/dist/viewer/index.js.map +1 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# PGN Viewer Parser
|
|
2
|
+
|
|
3
|
+
A production-ready TypeScript library for parsing and viewing chess PGN (Portable Game Notation) files. Designed for Next.js/React applications with full support for variations, comments, NAGs, and annotations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
✅ **Complete PGN Parsing** - Supports all official PGN features including variations, comments, NAGs, and annotations
|
|
8
|
+
✅ **Tree Data Structure** - Represents games as a navigable tree with mainline and variations
|
|
9
|
+
✅ **Pure TypeScript** - No chess.js dependency for parsing (optional for board state)
|
|
10
|
+
✅ **React Components** - Optional viewer components with keyboard navigation
|
|
11
|
+
✅ **Next.js Compatible** - Works with App Router (Server/Client components)
|
|
12
|
+
✅ **Tree-Shakable** - Import only what you need
|
|
13
|
+
✅ **Fully Typed** - Complete TypeScript type definitions
|
|
14
|
+
✅ **Comprehensive Tests** - Extensive test coverage with Vitest
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install pgn-viewer-parser
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For React components:
|
|
23
|
+
```bash
|
|
24
|
+
npm install pgn-viewer-parser react react-dom
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Basic Parsing
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { parsePGN, GameCursor } from 'pgn-viewer-parser';
|
|
33
|
+
|
|
34
|
+
const pgnText = `
|
|
35
|
+
[Event "Live Chess"]
|
|
36
|
+
[White "Magnus Carlsen"]
|
|
37
|
+
[Black "Hikaru Nakamura"]
|
|
38
|
+
|
|
39
|
+
1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 1-0
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
// Parse the PGN
|
|
43
|
+
const game = parsePGN(pgnText);
|
|
44
|
+
|
|
45
|
+
// Access headers
|
|
46
|
+
console.log(game.headers.Event); // "Live Chess"
|
|
47
|
+
console.log(game.headers.White); // "Magnus Carlsen"
|
|
48
|
+
|
|
49
|
+
// Navigate through moves
|
|
50
|
+
const cursor = new GameCursor(game.root);
|
|
51
|
+
cursor.next(); // e4
|
|
52
|
+
cursor.next(); // e5
|
|
53
|
+
console.log(cursor.current.san); // "e5"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### React Viewer
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
'use client';
|
|
60
|
+
|
|
61
|
+
import { parsePGN, GameCursor } from 'pgn-viewer-parser';
|
|
62
|
+
import { PGNViewer, PGNControls } from 'pgn-viewer-parser/viewer';
|
|
63
|
+
|
|
64
|
+
export default function ChessGame() {
|
|
65
|
+
const game = parsePGN(pgnText);
|
|
66
|
+
const cursor = new GameCursor(game.root);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div>
|
|
70
|
+
<PGNViewer root={game.root} cursor={cursor} />
|
|
71
|
+
<PGNControls cursor={cursor} />
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## API Reference
|
|
78
|
+
|
|
79
|
+
### Parsing
|
|
80
|
+
|
|
81
|
+
#### `parsePGN(pgnText: string): PGNGame`
|
|
82
|
+
|
|
83
|
+
Parses PGN text and returns a game object.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const game = parsePGN(pgnText);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Data Structures
|
|
90
|
+
|
|
91
|
+
#### `PGNGame`
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
interface PGNGame {
|
|
95
|
+
headers: Record<string, string>; // PGN headers
|
|
96
|
+
root: MoveNode; // Root of the game tree
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### `MoveNode`
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
interface MoveNode {
|
|
104
|
+
id: string; // Unique identifier
|
|
105
|
+
ply: number; // Half-move number (0-indexed)
|
|
106
|
+
moveNumber: number; // Full move number
|
|
107
|
+
color: 'w' | 'b'; // Side to move
|
|
108
|
+
san: string; // Move in SAN notation
|
|
109
|
+
nags: number[]; // Numeric Annotation Glyphs
|
|
110
|
+
commentBefore?: string; // Comment before move
|
|
111
|
+
commentAfter?: string; // Comment after move
|
|
112
|
+
clock?: number; // Clock time (seconds)
|
|
113
|
+
emt?: number; // Elapsed move time (seconds)
|
|
114
|
+
eval?: number; // Position evaluation
|
|
115
|
+
depth?: number; // Search depth
|
|
116
|
+
parent?: MoveNode; // Parent node
|
|
117
|
+
next?: MoveNode; // Next move in mainline
|
|
118
|
+
variations: MoveNode[]; // Alternative variations
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Navigation
|
|
123
|
+
|
|
124
|
+
#### `GameCursor`
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
class GameCursor {
|
|
128
|
+
current: MoveNode; // Current position
|
|
129
|
+
root: MoveNode; // Game root
|
|
130
|
+
|
|
131
|
+
next(): MoveNode | null; // Move forward
|
|
132
|
+
prev(): MoveNode | null; // Move backward
|
|
133
|
+
goTo(nodeId: string): void; // Jump to node
|
|
134
|
+
enterVariation(index: number): MoveNode | null; // Enter variation
|
|
135
|
+
exitVariation(): MoveNode | null; // Exit variation
|
|
136
|
+
toStart(): void; // Go to start
|
|
137
|
+
toEnd(): void; // Go to end
|
|
138
|
+
isAtStart(): boolean; // Check if at start
|
|
139
|
+
isAtEnd(): boolean; // Check if at end
|
|
140
|
+
getMainlinePath(): MoveNode[]; // Get path to current
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### React Components
|
|
145
|
+
|
|
146
|
+
#### `<PGNViewer />`
|
|
147
|
+
|
|
148
|
+
Displays moves with variations and comments.
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
<PGNViewer
|
|
152
|
+
root={game.root}
|
|
153
|
+
cursor={cursor}
|
|
154
|
+
onMoveClick={(node) => console.log(node.san)}
|
|
155
|
+
className="custom-class"
|
|
156
|
+
/>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Props:**
|
|
160
|
+
- `root: MoveNode` - Root of the game tree
|
|
161
|
+
- `cursor?: GameCursor` - Optional cursor for external control
|
|
162
|
+
- `onMoveClick?: (node: MoveNode) => void` - Callback when move is clicked
|
|
163
|
+
- `className?: string` - Custom CSS class
|
|
164
|
+
|
|
165
|
+
#### `<PGNControls />`
|
|
166
|
+
|
|
167
|
+
Navigation controls with keyboard support.
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<PGNControls
|
|
171
|
+
cursor={cursor}
|
|
172
|
+
onPositionChange={(cursor) => forceUpdate()}
|
|
173
|
+
enableKeyboard={true}
|
|
174
|
+
/>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Props:**
|
|
178
|
+
- `cursor: GameCursor` - The cursor to control
|
|
179
|
+
- `onPositionChange?: (cursor: GameCursor) => void` - Callback on position change
|
|
180
|
+
- `enableKeyboard?: boolean` - Enable keyboard controls (default: true)
|
|
181
|
+
- `className?: string` - Custom CSS class
|
|
182
|
+
|
|
183
|
+
**Keyboard Shortcuts:**
|
|
184
|
+
- `←` Previous move
|
|
185
|
+
- `→` Next move
|
|
186
|
+
- `↑` Exit variation
|
|
187
|
+
- `↓` Enter first variation
|
|
188
|
+
- `Ctrl+←` First move
|
|
189
|
+
- `Ctrl+→` Last move
|
|
190
|
+
|
|
191
|
+
## Advanced Features
|
|
192
|
+
|
|
193
|
+
### Parsing Variations
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const pgn = `
|
|
197
|
+
1. e4 e5 (1... c5 2. Nf3) 2. Nf3
|
|
198
|
+
`;
|
|
199
|
+
|
|
200
|
+
const game = parsePGN(pgn);
|
|
201
|
+
const e4 = game.root.next;
|
|
202
|
+
const e5 = e4.next;
|
|
203
|
+
|
|
204
|
+
// Access variation
|
|
205
|
+
const c5 = e5.variations[0];
|
|
206
|
+
console.log(c5.san); // "c5"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Parsing Annotations
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
const pgn = `
|
|
213
|
+
1. e4 e5 {[%clk 0:04:32][%eval +0.35][%depth 18]}
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
const game = parsePGN(pgn);
|
|
217
|
+
const e5 = game.root.next.next;
|
|
218
|
+
|
|
219
|
+
console.log(e5.clock); // 272 (seconds)
|
|
220
|
+
console.log(e5.eval); // 0.35
|
|
221
|
+
console.log(e5.depth); // 18
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### NAG Symbols
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { nagToSymbol } from 'pgn-viewer-parser';
|
|
228
|
+
|
|
229
|
+
console.log(nagToSymbol(1)); // "!" (good move)
|
|
230
|
+
console.log(nagToSymbol(2)); // "?" (poor move)
|
|
231
|
+
console.log(nagToSymbol(3)); // "!!" (brilliant move)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Next.js Integration
|
|
235
|
+
|
|
236
|
+
### App Router (Recommended)
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
// app/game/page.tsx
|
|
240
|
+
'use client';
|
|
241
|
+
|
|
242
|
+
import { parsePGN, GameCursor } from 'pgn-viewer-parser';
|
|
243
|
+
import { PGNViewer, PGNControls } from 'pgn-viewer-parser/viewer';
|
|
244
|
+
|
|
245
|
+
export default function GamePage() {
|
|
246
|
+
// Your component code
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Server Component
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
// app/game/page.tsx
|
|
254
|
+
import { parsePGN } from 'pgn-viewer-parser';
|
|
255
|
+
|
|
256
|
+
export default function GamePage() {
|
|
257
|
+
const game = parsePGN(pgnText);
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div>
|
|
261
|
+
<h1>{game.headers.Event}</h1>
|
|
262
|
+
{/* Render game info */}
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Comparison with chess.js
|
|
269
|
+
|
|
270
|
+
| Feature | pgn-viewer-parser | chess.js |
|
|
271
|
+
|---------|------------------|----------|
|
|
272
|
+
| PGN Parsing | ✅ Full support | ⚠️ Mainline only |
|
|
273
|
+
| Variations | ✅ Tree structure | ❌ Not supported |
|
|
274
|
+
| Comments | ✅ Before/after | ⚠️ Limited |
|
|
275
|
+
| NAGs | ✅ Full support | ❌ Not supported |
|
|
276
|
+
| Annotations | ✅ Clock/eval/depth | ❌ Not supported |
|
|
277
|
+
| Board State | ⚠️ Optional | ✅ Built-in |
|
|
278
|
+
| Move Validation | ⚠️ Optional | ✅ Built-in |
|
|
279
|
+
| React Components | ✅ Included | ❌ Not included |
|
|
280
|
+
|
|
281
|
+
**Use pgn-viewer-parser when:**
|
|
282
|
+
- You need to parse PGN with variations
|
|
283
|
+
- You want to display games with a tree structure
|
|
284
|
+
- You need annotations and comments
|
|
285
|
+
- You're building a PGN viewer/analyzer
|
|
286
|
+
|
|
287
|
+
**Use chess.js when:**
|
|
288
|
+
- You need move validation
|
|
289
|
+
- You're building a playable chess board
|
|
290
|
+
- You don't need variation support
|
|
291
|
+
|
|
292
|
+
**Use both together:**
|
|
293
|
+
```typescript
|
|
294
|
+
import { parsePGN } from 'pgn-viewer-parser';
|
|
295
|
+
import { Chess } from 'chess.js';
|
|
296
|
+
|
|
297
|
+
const game = parsePGN(pgnText);
|
|
298
|
+
const chess = new Chess();
|
|
299
|
+
|
|
300
|
+
// Apply moves to chess.js for board state
|
|
301
|
+
let node = game.root.next;
|
|
302
|
+
while (node) {
|
|
303
|
+
chess.move(node.san);
|
|
304
|
+
node = node.next;
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Examples
|
|
309
|
+
|
|
310
|
+
See the `examples/` directory for complete examples:
|
|
311
|
+
- `basic-parsing.ts` - Basic parsing and navigation
|
|
312
|
+
- `react-viewer.tsx` - React component usage
|
|
313
|
+
|
|
314
|
+
## Development
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# Install dependencies
|
|
318
|
+
npm install
|
|
319
|
+
|
|
320
|
+
# Run tests
|
|
321
|
+
npm test
|
|
322
|
+
|
|
323
|
+
# Build library
|
|
324
|
+
npm run build
|
|
325
|
+
|
|
326
|
+
# Type check
|
|
327
|
+
npm run type-check
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## License
|
|
331
|
+
|
|
332
|
+
MIT
|
|
333
|
+
|
|
334
|
+
## Contributing
|
|
335
|
+
|
|
336
|
+
Contributions are welcome! Please open an issue or PR.
|
|
337
|
+
|
|
338
|
+
## Support
|
|
339
|
+
|
|
340
|
+
For issues and questions, please open an issue on GitHub.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
class u {
|
|
2
|
+
constructor(t) {
|
|
3
|
+
this._root = t, this._current = t;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Gets the current node.
|
|
7
|
+
*/
|
|
8
|
+
get current() {
|
|
9
|
+
return this._current;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Gets the root node.
|
|
13
|
+
*/
|
|
14
|
+
get root() {
|
|
15
|
+
return this._root;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Moves to the next node in the current line.
|
|
19
|
+
* Returns the new current node, or null if at the end.
|
|
20
|
+
*/
|
|
21
|
+
next() {
|
|
22
|
+
return this._current.next ? (this._current = this._current.next, this._current) : null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Moves to the previous node.
|
|
26
|
+
* Returns the new current node, or null if at the start.
|
|
27
|
+
*/
|
|
28
|
+
prev() {
|
|
29
|
+
return this._current.parent ? (this._current = this._current.parent, this._current) : null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Jumps to a specific node by ID.
|
|
33
|
+
*/
|
|
34
|
+
goTo(t) {
|
|
35
|
+
const r = this.findNodeById(this._root, t);
|
|
36
|
+
if (r)
|
|
37
|
+
this._current = r;
|
|
38
|
+
else
|
|
39
|
+
throw new Error(`Node with ID ${t} not found`);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Enters a variation at the given index.
|
|
43
|
+
* Returns the first node of the variation, or null if invalid index.
|
|
44
|
+
*/
|
|
45
|
+
enterVariation(t) {
|
|
46
|
+
return t >= 0 && t < this._current.variations.length ? (this._current = this._current.variations[t], this._current) : null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Exits the current variation and returns to the parent line.
|
|
50
|
+
* Returns the parent node, or null if already in the mainline.
|
|
51
|
+
*/
|
|
52
|
+
exitVariation() {
|
|
53
|
+
return this._current.parent ? (this._current = this._current.parent, this._current) : null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Goes to the start of the game (root node).
|
|
57
|
+
*/
|
|
58
|
+
toStart() {
|
|
59
|
+
this._current = this._root;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Goes to the end of the current line.
|
|
63
|
+
*/
|
|
64
|
+
toEnd() {
|
|
65
|
+
for (; this._current.next; )
|
|
66
|
+
this._current = this._current.next;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Checks if the cursor is at the start.
|
|
70
|
+
*/
|
|
71
|
+
isAtStart() {
|
|
72
|
+
return this._current === this._root;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Checks if the cursor is at the end of the current line.
|
|
76
|
+
*/
|
|
77
|
+
isAtEnd() {
|
|
78
|
+
return this._current.next === void 0;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Gets the mainline path from root to current position.
|
|
82
|
+
*/
|
|
83
|
+
getMainlinePath() {
|
|
84
|
+
const t = [];
|
|
85
|
+
let r = this._current;
|
|
86
|
+
for (; r; )
|
|
87
|
+
t.unshift(r), r = r.parent;
|
|
88
|
+
return t;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Recursively finds a node by ID.
|
|
92
|
+
*/
|
|
93
|
+
findNodeById(t, r) {
|
|
94
|
+
if (t.id === r)
|
|
95
|
+
return t;
|
|
96
|
+
if (t.next) {
|
|
97
|
+
const n = this.findNodeById(t.next, r);
|
|
98
|
+
if (n) return n;
|
|
99
|
+
}
|
|
100
|
+
for (const n of t.variations) {
|
|
101
|
+
const e = this.findNodeById(n, r);
|
|
102
|
+
if (e) return e;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
u as GameCursor
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=game-cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-cursor.js","sources":["../../src/cursor/game-cursor.ts"],"sourcesContent":["import { MoveNode } from '../model/move-node.js';\n\n/**\n * Cursor for navigating through a chess game tree.\n */\nexport class GameCursor {\n private _current: MoveNode;\n private _root: MoveNode;\n\n constructor(root: MoveNode) {\n this._root = root;\n this._current = root;\n }\n\n /**\n * Gets the current node.\n */\n get current(): MoveNode {\n return this._current;\n }\n\n /**\n * Gets the root node.\n */\n get root(): MoveNode {\n return this._root;\n }\n\n /**\n * Moves to the next node in the current line.\n * Returns the new current node, or null if at the end.\n */\n next(): MoveNode | null {\n if (this._current.next) {\n this._current = this._current.next;\n return this._current;\n }\n return null;\n }\n\n /**\n * Moves to the previous node.\n * Returns the new current node, or null if at the start.\n */\n prev(): MoveNode | null {\n if (this._current.parent) {\n this._current = this._current.parent;\n return this._current;\n }\n return null;\n }\n\n /**\n * Jumps to a specific node by ID.\n */\n goTo(nodeId: string): void {\n const node = this.findNodeById(this._root, nodeId);\n if (node) {\n this._current = node;\n } else {\n throw new Error(`Node with ID ${nodeId} not found`);\n }\n }\n\n /**\n * Enters a variation at the given index.\n * Returns the first node of the variation, or null if invalid index.\n */\n enterVariation(index: number): MoveNode | null {\n if (index >= 0 && index < this._current.variations.length) {\n this._current = this._current.variations[index];\n return this._current;\n }\n return null;\n }\n\n /**\n * Exits the current variation and returns to the parent line.\n * Returns the parent node, or null if already in the mainline.\n */\n exitVariation(): MoveNode | null {\n if (this._current.parent) {\n this._current = this._current.parent;\n return this._current;\n }\n return null;\n }\n\n /**\n * Goes to the start of the game (root node).\n */\n toStart(): void {\n this._current = this._root;\n }\n\n /**\n * Goes to the end of the current line.\n */\n toEnd(): void {\n while (this._current.next) {\n this._current = this._current.next;\n }\n }\n\n /**\n * Checks if the cursor is at the start.\n */\n isAtStart(): boolean {\n return this._current === this._root;\n }\n\n /**\n * Checks if the cursor is at the end of the current line.\n */\n isAtEnd(): boolean {\n return this._current.next === undefined;\n }\n\n /**\n * Gets the mainline path from root to current position.\n */\n getMainlinePath(): MoveNode[] {\n const path: MoveNode[] = [];\n let node: MoveNode | undefined = this._current;\n\n while (node) {\n path.unshift(node);\n node = node.parent;\n }\n\n return path;\n }\n\n /**\n * Recursively finds a node by ID.\n */\n private findNodeById(node: MoveNode, id: string): MoveNode | null {\n if (node.id === id) {\n return node;\n }\n\n // Search in mainline\n if (node.next) {\n const found = this.findNodeById(node.next, id);\n if (found) return found;\n }\n\n // Search in variations\n for (const variation of node.variations) {\n const found = this.findNodeById(variation, id);\n if (found) return found;\n }\n\n return null;\n }\n}\n"],"names":["GameCursor","root","nodeId","node","index","path","id","found","variation"],"mappings":"AAKO,MAAMA,EAAW;AAAA,EAIpB,YAAYC,GAAgB;AACxB,SAAK,QAAQA,GACb,KAAK,WAAWA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAoB;AACpB,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAAiB;AACjB,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAwB;AACpB,WAAI,KAAK,SAAS,QACd,KAAK,WAAW,KAAK,SAAS,MACvB,KAAK,YAET;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAwB;AACpB,WAAI,KAAK,SAAS,UACd,KAAK,WAAW,KAAK,SAAS,QACvB,KAAK,YAET;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKA,KAAKC,GAAsB;AACvB,UAAMC,IAAO,KAAK,aAAa,KAAK,OAAOD,CAAM;AACjD,QAAIC;AACA,WAAK,WAAWA;AAAA;AAEhB,YAAM,IAAI,MAAM,gBAAgBD,CAAM,YAAY;AAAA,EAE1D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAeE,GAAgC;AAC3C,WAAIA,KAAS,KAAKA,IAAQ,KAAK,SAAS,WAAW,UAC/C,KAAK,WAAW,KAAK,SAAS,WAAWA,CAAK,GACvC,KAAK,YAET;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAiC;AAC7B,WAAI,KAAK,SAAS,UACd,KAAK,WAAW,KAAK,SAAS,QACvB,KAAK,YAET;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACZ,SAAK,WAAW,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACV,WAAO,KAAK,SAAS;AACjB,WAAK,WAAW,KAAK,SAAS;AAAA,EAEtC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACjB,WAAO,KAAK,aAAa,KAAK;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAmB;AACf,WAAO,KAAK,SAAS,SAAS;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA8B;AAC1B,UAAMC,IAAmB,CAAA;AACzB,QAAIF,IAA6B,KAAK;AAEtC,WAAOA;AACH,MAAAE,EAAK,QAAQF,CAAI,GACjBA,IAAOA,EAAK;AAGhB,WAAOE;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAaF,GAAgBG,GAA6B;AAC9D,QAAIH,EAAK,OAAOG;AACZ,aAAOH;AAIX,QAAIA,EAAK,MAAM;AACX,YAAMI,IAAQ,KAAK,aAAaJ,EAAK,MAAMG,CAAE;AAC7C,UAAIC,EAAO,QAAOA;AAAA,IACtB;AAGA,eAAWC,KAAaL,EAAK,YAAY;AACrC,YAAMI,IAAQ,KAAK,aAAaC,GAAWF,CAAE;AAC7C,UAAIC,EAAO,QAAOA;AAAA,IACtB;AAEA,WAAO;AAAA,EACX;AACJ;"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createMoveNode as r, createRootNode as a } from "./model/move-node.js";
|
|
2
|
+
import { createPGNGame as p } from "./model/pgn-game.js";
|
|
3
|
+
import { PGNParser as n, parsePGN as s } from "./parser/pgn-parser.js";
|
|
4
|
+
import { PGNTokenizer as x, TokenType as G } from "./parser/tokenizer.js";
|
|
5
|
+
import { parseHeader as T, parseHeaders as i } from "./parser/header-parser.js";
|
|
6
|
+
import { GameCursor as d } from "./cursor/game-cursor.js";
|
|
7
|
+
import { NAG_SYMBOLS as S, nagToSymbol as c, nagsToSymbols as l } from "./utils/nag-symbols.js";
|
|
8
|
+
import { parseAnnotations as b, parseEvalAnnotation as g, parseTimeAnnotation as k } from "./utils/annotation-parser.js";
|
|
9
|
+
export {
|
|
10
|
+
d as GameCursor,
|
|
11
|
+
S as NAG_SYMBOLS,
|
|
12
|
+
n as PGNParser,
|
|
13
|
+
x as PGNTokenizer,
|
|
14
|
+
G as TokenType,
|
|
15
|
+
r as createMoveNode,
|
|
16
|
+
p as createPGNGame,
|
|
17
|
+
a as createRootNode,
|
|
18
|
+
c as nagToSymbol,
|
|
19
|
+
l as nagsToSymbols,
|
|
20
|
+
b as parseAnnotations,
|
|
21
|
+
g as parseEvalAnnotation,
|
|
22
|
+
T as parseHeader,
|
|
23
|
+
i as parseHeaders,
|
|
24
|
+
s as parsePGN,
|
|
25
|
+
k as parseTimeAnnotation
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function e(o) {
|
|
2
|
+
return {
|
|
3
|
+
...o,
|
|
4
|
+
nags: o.nags || [],
|
|
5
|
+
variations: []
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
function n() {
|
|
9
|
+
return {
|
|
10
|
+
id: "root",
|
|
11
|
+
ply: 0,
|
|
12
|
+
moveNumber: 0,
|
|
13
|
+
color: "w",
|
|
14
|
+
san: "",
|
|
15
|
+
nags: [],
|
|
16
|
+
variations: []
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
e as createMoveNode,
|
|
21
|
+
n as createRootNode
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=move-node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"move-node.js","sources":["../../src/model/move-node.ts"],"sourcesContent":["/**\n * Represents a single move node in the game tree.\n * Each node can have a mainline continuation (next) and alternative variations.\n */\nexport interface MoveNode {\n /** Unique identifier for this node */\n id: string;\n\n /** Ply number (half-moves from start, 0-indexed) */\n ply: number;\n\n /** Move number in chess notation (1, 2, 3, etc.) */\n moveNumber: number;\n\n /** Color to move: 'w' for white, 'b' for black */\n color: 'w' | 'b';\n\n /** Standard Algebraic Notation of the move (e.g., \"e4\", \"Nf3\", \"O-O\") */\n san: string;\n\n /** Numeric Annotation Glyphs (e.g., $1 = \"!\", $2 = \"?\") */\n nags: number[];\n\n /** Comment appearing before this move */\n commentBefore?: string;\n\n /** Comment appearing after this move */\n commentAfter?: string;\n\n /** Clock time remaining in seconds */\n clock?: number;\n\n /** Elapsed move time in seconds */\n emt?: number;\n\n /** Position evaluation in centipawns (positive = white advantage) */\n eval?: number;\n\n /** Search depth for the evaluation */\n depth?: number;\n\n /** Parent node (undefined for root) */\n parent?: MoveNode;\n\n /** Next move in the mainline */\n next?: MoveNode;\n\n /** Alternative variations from this position */\n variations: MoveNode[];\n}\n\n/**\n * Creates a new MoveNode with the given properties.\n */\nexport function createMoveNode(props: {\n id: string;\n ply: number;\n moveNumber: number;\n color: 'w' | 'b';\n san: string;\n nags?: number[];\n commentBefore?: string;\n commentAfter?: string;\n clock?: number;\n emt?: number;\n eval?: number;\n depth?: number;\n parent?: MoveNode;\n}): MoveNode {\n return {\n ...props,\n nags: props.nags || [],\n variations: [],\n };\n}\n\n/**\n * Creates a root node (starting position with no move).\n */\nexport function createRootNode(): MoveNode {\n return {\n id: 'root',\n ply: 0,\n moveNumber: 0,\n color: 'w',\n san: '',\n nags: [],\n variations: [],\n };\n}\n"],"names":["createMoveNode","props","createRootNode"],"mappings":"AAsDO,SAASA,EAAeC,GAclB;AACT,SAAO;AAAA,IACH,GAAGA;AAAA,IACH,MAAMA,EAAM,QAAQ,CAAA;AAAA,IACpB,YAAY,CAAA;AAAA,EAAC;AAErB;AAKO,SAASC,IAA2B;AACvC,SAAO;AAAA,IACH,IAAI;AAAA,IACJ,KAAK;AAAA,IACL,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,KAAK;AAAA,IACL,MAAM,CAAA;AAAA,IACN,YAAY,CAAA;AAAA,EAAC;AAErB;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function e(o = {}, r) {
|
|
2
|
+
return {
|
|
3
|
+
headers: o,
|
|
4
|
+
root: r || {
|
|
5
|
+
id: "root",
|
|
6
|
+
ply: 0,
|
|
7
|
+
moveNumber: 0,
|
|
8
|
+
color: "w",
|
|
9
|
+
san: "",
|
|
10
|
+
nags: [],
|
|
11
|
+
variations: []
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
e as createPGNGame
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=pgn-game.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pgn-game.js","sources":["../../src/model/pgn-game.ts"],"sourcesContent":["import { MoveNode } from './move-node.js';\n\n/**\n * Represents a complete chess game parsed from PGN.\n */\nexport interface PGNGame {\n /** PGN headers (Event, Site, Date, White, Black, Result, etc.) */\n headers: Record<string, string>;\n\n /** Root node of the game tree (starting position) */\n root: MoveNode;\n}\n\n/**\n * Creates a new PGNGame with the given headers and root node.\n */\nexport function createPGNGame(\n headers: Record<string, string> = {},\n root?: MoveNode\n): PGNGame {\n return {\n headers,\n root: root || {\n id: 'root',\n ply: 0,\n moveNumber: 0,\n color: 'w',\n san: '',\n nags: [],\n variations: [],\n },\n };\n}\n"],"names":["createPGNGame","headers","root"],"mappings":"AAgBO,SAASA,EACZC,IAAkC,CAAA,GAClCC,GACO;AACP,SAAO;AAAA,IACH,SAAAD;AAAA,IACA,MAAMC,KAAQ;AAAA,MACV,IAAI;AAAA,MACJ,KAAK;AAAA,MACL,YAAY;AAAA,MACZ,OAAO;AAAA,MACP,KAAK;AAAA,MACL,MAAM,CAAA;AAAA,MACN,YAAY,CAAA;AAAA,IAAC;AAAA,EACjB;AAER;"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function s(n) {
|
|
2
|
+
const e = {};
|
|
3
|
+
for (const r of n) {
|
|
4
|
+
const t = r.match(/\[(\w+)\s+"(.*)"\]/);
|
|
5
|
+
if (t) {
|
|
6
|
+
const [, c, o] = t;
|
|
7
|
+
e[c] = o;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return e;
|
|
11
|
+
}
|
|
12
|
+
function a(n) {
|
|
13
|
+
const e = n.match(/\[(\w+)\s+"(.*)"\]/);
|
|
14
|
+
if (e) {
|
|
15
|
+
const [, r, t] = e;
|
|
16
|
+
return { key: r, value: t };
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
a as parseHeader,
|
|
22
|
+
s as parseHeaders
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=header-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header-parser.js","sources":["../../src/parser/header-parser.ts"],"sourcesContent":["/**\n * Parses PGN headers from header tokens.\n * Format: [Key \"Value\"]\n */\nexport function parseHeaders(headerTokens: string[]): Record<string, string> {\n const headers: Record<string, string> = {};\n\n for (const token of headerTokens) {\n const match = token.match(/\\[(\\w+)\\s+\"(.*)\"\\]/);\n if (match) {\n const [, key, value] = match;\n headers[key] = value;\n }\n }\n\n return headers;\n}\n\n/**\n * Parses a single header string.\n */\nexport function parseHeader(header: string): { key: string; value: string } | null {\n const match = header.match(/\\[(\\w+)\\s+\"(.*)\"\\]/);\n if (match) {\n const [, key, value] = match;\n return { key, value };\n }\n return null;\n}\n"],"names":["parseHeaders","headerTokens","headers","token","match","key","value","parseHeader","header"],"mappings":"AAIO,SAASA,EAAaC,GAAgD;AACzE,QAAMC,IAAkC,CAAA;AAExC,aAAWC,KAASF,GAAc;AAC9B,UAAMG,IAAQD,EAAM,MAAM,oBAAoB;AAC9C,QAAIC,GAAO;AACP,YAAM,CAAA,EAAGC,GAAKC,CAAK,IAAIF;AACvB,MAAAF,EAAQG,CAAG,IAAIC;AAAA,IACnB;AAAA,EACJ;AAEA,SAAOJ;AACX;AAKO,SAASK,EAAYC,GAAuD;AAC/E,QAAMJ,IAAQI,EAAO,MAAM,oBAAoB;AAC/C,MAAIJ,GAAO;AACP,UAAM,CAAA,EAAGC,GAAKC,CAAK,IAAIF;AACvB,WAAO,EAAE,KAAAC,GAAK,OAAAC,EAAA;AAAA,EAClB;AACA,SAAO;AACX;"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { PGNTokenizer as E, TokenType as e } from "./tokenizer.js";
|
|
2
|
+
import { parseHeader as k } from "./header-parser.js";
|
|
3
|
+
import { parseAnnotations as T } from "../utils/annotation-parser.js";
|
|
4
|
+
import { createRootNode as y } from "../model/move-node.js";
|
|
5
|
+
import { createPGNGame as A } from "../model/pgn-game.js";
|
|
6
|
+
class M {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.tokens = [], this.position = 0, this.nodeIdCounter = 0;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parses PGN text and returns a PGNGame object.
|
|
12
|
+
*/
|
|
13
|
+
parse(o) {
|
|
14
|
+
const s = new E(o);
|
|
15
|
+
this.tokens = s.tokenize(), this.position = 0, this.nodeIdCounter = 0;
|
|
16
|
+
const t = this.parseHeaders(), r = y();
|
|
17
|
+
return this.parseMoveSequence(r, 0), A(t, r);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parses all headers at the beginning of the PGN.
|
|
21
|
+
*/
|
|
22
|
+
parseHeaders() {
|
|
23
|
+
const o = {};
|
|
24
|
+
for (; this.current().type === e.HEADER; ) {
|
|
25
|
+
const s = this.consume(e.HEADER), t = k(s.value);
|
|
26
|
+
t && (o[t.key] = t.value);
|
|
27
|
+
}
|
|
28
|
+
return o;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parses a sequence of moves and variations, building the tree structure.
|
|
32
|
+
* Returns the FIRST node created in this sequence (for variations).
|
|
33
|
+
*/
|
|
34
|
+
parseMoveSequence(o, s) {
|
|
35
|
+
let t = o, r = s, c, h;
|
|
36
|
+
for (; !this.isAtEnd(); ) {
|
|
37
|
+
const a = this.current();
|
|
38
|
+
if (a.type === e.VARIATION_END)
|
|
39
|
+
return h;
|
|
40
|
+
if (a.type === e.COMMENT) {
|
|
41
|
+
const i = this.consume(e.COMMENT).value;
|
|
42
|
+
c = c ? `${c} ${i}` : i;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (a.type === e.MOVE_NUMBER) {
|
|
46
|
+
this.advance();
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (a.type === e.RESULT) {
|
|
50
|
+
this.advance();
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
if (a.type === e.VARIATION_START) {
|
|
54
|
+
this.consume(e.VARIATION_START);
|
|
55
|
+
const i = t.parent || o, d = t.ply, u = this.parseMoveSequence(i, d);
|
|
56
|
+
u && u !== i && t.variations.push(u), this.consume(e.VARIATION_END);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (a.type === e.MOVE) {
|
|
60
|
+
const i = this.consume(e.MOVE), d = r % 2 === 0 ? "w" : "b", u = Math.floor(r / 2) + 1, f = [];
|
|
61
|
+
for (; this.current().type === e.NAG; ) {
|
|
62
|
+
const l = this.consume(e.NAG), n = parseInt(l.value.slice(1));
|
|
63
|
+
f.push(n);
|
|
64
|
+
}
|
|
65
|
+
let N;
|
|
66
|
+
const p = {};
|
|
67
|
+
if (this.current().type === e.COMMENT) {
|
|
68
|
+
const l = this.consume(e.COMMENT), n = T(l.value);
|
|
69
|
+
n.clock !== void 0 && (p.clock = n.clock), n.emt !== void 0 && (p.emt = n.emt), n.eval !== void 0 && (p.eval = n.eval), n.depth !== void 0 && (p.depth = n.depth), n.cleanComment && (N = n.cleanComment);
|
|
70
|
+
}
|
|
71
|
+
const m = {
|
|
72
|
+
id: this.generateNodeId(),
|
|
73
|
+
ply: r,
|
|
74
|
+
moveNumber: u,
|
|
75
|
+
color: d,
|
|
76
|
+
san: i.value,
|
|
77
|
+
nags: f,
|
|
78
|
+
commentBefore: c,
|
|
79
|
+
commentAfter: N,
|
|
80
|
+
...p,
|
|
81
|
+
parent: t,
|
|
82
|
+
variations: []
|
|
83
|
+
};
|
|
84
|
+
t.next === void 0 && (t.next = m), h || (h = m), t = m, r++, c = void 0;
|
|
85
|
+
} else
|
|
86
|
+
this.advance();
|
|
87
|
+
}
|
|
88
|
+
return h;
|
|
89
|
+
}
|
|
90
|
+
current() {
|
|
91
|
+
return this.tokens[this.position] || { type: e.EOF, value: "", line: 0, column: 0 };
|
|
92
|
+
}
|
|
93
|
+
advance() {
|
|
94
|
+
return this.tokens[this.position++];
|
|
95
|
+
}
|
|
96
|
+
consume(o) {
|
|
97
|
+
const s = this.current();
|
|
98
|
+
if (s.type !== o)
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Expected token type ${o}, got ${s.type} at line ${s.line}`
|
|
101
|
+
);
|
|
102
|
+
return this.advance();
|
|
103
|
+
}
|
|
104
|
+
isAtEnd() {
|
|
105
|
+
return this.current().type === e.EOF;
|
|
106
|
+
}
|
|
107
|
+
generateNodeId() {
|
|
108
|
+
return `node_${this.nodeIdCounter++}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function V(v) {
|
|
112
|
+
return new M().parse(v);
|
|
113
|
+
}
|
|
114
|
+
export {
|
|
115
|
+
M as PGNParser,
|
|
116
|
+
V as parsePGN
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=pgn-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pgn-parser.js","sources":["../../src/parser/pgn-parser.ts"],"sourcesContent":["import { Token, TokenType, PGNTokenizer } from './tokenizer.js';\nimport { parseHeader } from './header-parser.js';\nimport { parseAnnotations } from '../utils/annotation-parser.js';\nimport { MoveNode, createRootNode } from '../model/move-node.js';\nimport { PGNGame, createPGNGame } from '../model/pgn-game.js';\n\n/**\n * Main PGN parser that converts PGN text into a game tree structure.\n */\nexport class PGNParser {\n private tokens: Token[] = [];\n private position: number = 0;\n private nodeIdCounter: number = 0;\n\n /**\n * Parses PGN text and returns a PGNGame object.\n */\n parse(pgnText: string): PGNGame {\n const tokenizer = new PGNTokenizer(pgnText);\n this.tokens = tokenizer.tokenize();\n this.position = 0;\n this.nodeIdCounter = 0;\n\n // Parse headers\n const headers = this.parseHeaders();\n\n // Create root node\n const root = createRootNode();\n\n // Parse moves starting from root\n this.parseMoveSequence(root, 0);\n\n return createPGNGame(headers, root);\n }\n\n /**\n * Parses all headers at the beginning of the PGN.\n */\n private parseHeaders(): Record<string, string> {\n const headers: Record<string, string> = {};\n\n while (this.current().type === TokenType.HEADER) {\n const headerToken = this.consume(TokenType.HEADER);\n const parsed = parseHeader(headerToken.value);\n if (parsed) {\n headers[parsed.key] = parsed.value;\n }\n }\n\n return headers;\n }\n\n /**\n * Parses a sequence of moves and variations, building the tree structure.\n * Returns the FIRST node created in this sequence (for variations).\n */\n private parseMoveSequence(\n parentNode: MoveNode,\n startPly: number\n ): MoveNode | undefined {\n let currentNode = parentNode;\n let ply = startPly;\n let pendingComment: string | undefined;\n let firstNode: MoveNode | undefined; // Track the first node we create\n\n while (!this.isAtEnd()) {\n const token = this.current();\n\n // End of variation\n if (token.type === TokenType.VARIATION_END) {\n return firstNode; // Return first node, not current\n }\n\n // Comment before move\n if (token.type === TokenType.COMMENT) {\n const comment = this.consume(TokenType.COMMENT).value;\n pendingComment = pendingComment\n ? `${pendingComment} ${comment}`\n : comment;\n continue;\n }\n\n // Skip move numbers\n if (token.type === TokenType.MOVE_NUMBER) {\n this.advance();\n continue;\n }\n\n // Result marker (end of game)\n if (token.type === TokenType.RESULT) {\n this.advance();\n break;\n }\n\n // Variation start\n if (token.type === TokenType.VARIATION_START) {\n this.consume(TokenType.VARIATION_START);\n\n // Variations are stored on the move they replace (currentNode)\n // but they branch from the parent's position\n // For example: \"1. e4 e5 (1... c5)\" - variation stored on e5, but branches from e4's position\n const variationParent = currentNode.parent || parentNode;\n const variationPly = currentNode.ply; // Same ply as the move being replaced\n\n // Parse the variation\n const variationStart = this.parseMoveSequence(variationParent, variationPly);\n\n if (variationStart && variationStart !== variationParent) {\n currentNode.variations.push(variationStart);\n }\n\n this.consume(TokenType.VARIATION_END);\n continue;\n }\n\n // Move\n if (token.type === TokenType.MOVE) {\n const moveToken = this.consume(TokenType.MOVE);\n const color = ply % 2 === 0 ? 'w' : 'b';\n const moveNumber = Math.floor(ply / 2) + 1;\n\n // Collect NAGs\n const nags: number[] = [];\n while (this.current().type === TokenType.NAG) {\n const nagToken = this.consume(TokenType.NAG);\n const nagValue = parseInt(nagToken.value.slice(1)); // Remove '$'\n nags.push(nagValue);\n }\n\n // Collect comment after move\n let commentAfter: string | undefined;\n const annotations: {\n clock?: number;\n emt?: number;\n eval?: number;\n depth?: number;\n } = {};\n\n if (this.current().type === TokenType.COMMENT) {\n const commentToken = this.consume(TokenType.COMMENT);\n const parsed = parseAnnotations(commentToken.value);\n\n if (parsed.clock !== undefined) annotations.clock = parsed.clock;\n if (parsed.emt !== undefined) annotations.emt = parsed.emt;\n if (parsed.eval !== undefined) annotations.eval = parsed.eval;\n if (parsed.depth !== undefined) annotations.depth = parsed.depth;\n\n if (parsed.cleanComment) {\n commentAfter = parsed.cleanComment;\n }\n }\n\n // Create new move node\n const newNode: MoveNode = {\n id: this.generateNodeId(),\n ply,\n moveNumber,\n color,\n san: moveToken.value,\n nags,\n commentBefore: pendingComment,\n commentAfter,\n ...annotations,\n parent: currentNode,\n variations: [],\n };\n\n // Link to parent\n if (currentNode.next === undefined) {\n currentNode.next = newNode;\n }\n\n // Track first node created\n if (!firstNode) {\n firstNode = newNode;\n }\n\n currentNode = newNode;\n ply++;\n pendingComment = undefined;\n } else {\n // Unknown token, skip\n this.advance();\n }\n }\n\n return firstNode; // Return first node for variations\n }\n\n private current(): Token {\n return this.tokens[this.position] || { type: TokenType.EOF, value: '', line: 0, column: 0 };\n }\n\n private advance(): Token {\n return this.tokens[this.position++];\n }\n\n private consume(expectedType: TokenType): Token {\n const token = this.current();\n if (token.type !== expectedType) {\n throw new Error(\n `Expected token type ${expectedType}, got ${token.type} at line ${token.line}`\n );\n }\n return this.advance();\n }\n\n private isAtEnd(): boolean {\n return this.current().type === TokenType.EOF;\n }\n\n private generateNodeId(): string {\n return `node_${this.nodeIdCounter++}`;\n }\n}\n\n/**\n * Convenience function to parse PGN text.\n */\nexport function parsePGN(pgnText: string): PGNGame {\n const parser = new PGNParser();\n return parser.parse(pgnText);\n}\n"],"names":["PGNParser","pgnText","tokenizer","PGNTokenizer","headers","root","createRootNode","createPGNGame","TokenType","headerToken","parsed","parseHeader","parentNode","startPly","currentNode","ply","pendingComment","firstNode","token","comment","variationParent","variationPly","variationStart","moveToken","color","moveNumber","nags","nagToken","nagValue","commentAfter","annotations","commentToken","parseAnnotations","newNode","expectedType","parsePGN"],"mappings":"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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
var n = /* @__PURE__ */ ((i) => (i.HEADER = "HEADER", i.MOVE_NUMBER = "MOVE_NUMBER", i.MOVE = "MOVE", i.NAG = "NAG", i.COMMENT = "COMMENT", i.VARIATION_START = "VARIATION_START", i.VARIATION_END = "VARIATION_END", i.RESULT = "RESULT", i.EOF = "EOF", i))(n || {});
|
|
2
|
+
class r {
|
|
3
|
+
constructor(t) {
|
|
4
|
+
this.position = 0, this.line = 1, this.column = 1, this.input = t;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Returns all tokens from the input.
|
|
8
|
+
*/
|
|
9
|
+
tokenize() {
|
|
10
|
+
const t = [];
|
|
11
|
+
let e;
|
|
12
|
+
for (; (e = this.nextToken()).type !== "EOF"; )
|
|
13
|
+
t.push(e);
|
|
14
|
+
return t.push(e), t;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Gets the next token from the input.
|
|
18
|
+
*/
|
|
19
|
+
nextToken() {
|
|
20
|
+
if (this.skipWhitespace(), this.position >= this.input.length)
|
|
21
|
+
return this.createToken("EOF", "");
|
|
22
|
+
const t = this.input[this.position];
|
|
23
|
+
return t === "[" ? this.readHeader() : t === "{" ? this.readBraceComment() : t === ";" ? this.readLineComment() : t === "(" ? this.createToken("VARIATION_START", this.advance()) : t === ")" ? this.createToken("VARIATION_END", this.advance()) : t === "$" ? this.readNAG() : this.isResultStart() ? this.readResult() : this.isDigit(t) ? this.readMoveNumber() : this.isMoveStart(t) ? this.readMove() : (this.advance(), this.nextToken());
|
|
24
|
+
}
|
|
25
|
+
readHeader() {
|
|
26
|
+
this.advance();
|
|
27
|
+
let t = "[", e = !1;
|
|
28
|
+
for (; this.position < this.input.length; ) {
|
|
29
|
+
const s = this.current();
|
|
30
|
+
if (s === '"' && (e = !e), s === "]" && !e) {
|
|
31
|
+
t += this.advance();
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
t += this.advance();
|
|
35
|
+
}
|
|
36
|
+
return this.createToken("HEADER", t);
|
|
37
|
+
}
|
|
38
|
+
readBraceComment() {
|
|
39
|
+
this.advance();
|
|
40
|
+
let t = "";
|
|
41
|
+
for (; this.position < this.input.length && this.current() !== "}"; )
|
|
42
|
+
t += this.advance();
|
|
43
|
+
return this.current() === "}" && this.advance(), this.createToken("COMMENT", t.trim());
|
|
44
|
+
}
|
|
45
|
+
readLineComment() {
|
|
46
|
+
this.advance();
|
|
47
|
+
let t = "";
|
|
48
|
+
for (; this.position < this.input.length && this.current() !== `
|
|
49
|
+
`; )
|
|
50
|
+
t += this.advance();
|
|
51
|
+
return this.createToken("COMMENT", t.trim());
|
|
52
|
+
}
|
|
53
|
+
readNAG() {
|
|
54
|
+
this.advance();
|
|
55
|
+
let t = "$";
|
|
56
|
+
for (; this.position < this.input.length && this.isDigit(this.current()); )
|
|
57
|
+
t += this.advance();
|
|
58
|
+
return this.createToken("NAG", t);
|
|
59
|
+
}
|
|
60
|
+
readResult() {
|
|
61
|
+
let t = "";
|
|
62
|
+
if (this.current() === "*")
|
|
63
|
+
t = this.advance();
|
|
64
|
+
else
|
|
65
|
+
for (; this.position < this.input.length && /[01\-\/]/.test(this.current()); )
|
|
66
|
+
t += this.advance();
|
|
67
|
+
return this.createToken("RESULT", t);
|
|
68
|
+
}
|
|
69
|
+
readMoveNumber() {
|
|
70
|
+
let t = "";
|
|
71
|
+
for (; this.position < this.input.length && this.isDigit(this.current()); )
|
|
72
|
+
t += this.advance();
|
|
73
|
+
for (; this.current() === "."; )
|
|
74
|
+
t += this.advance();
|
|
75
|
+
return this.createToken("MOVE_NUMBER", t);
|
|
76
|
+
}
|
|
77
|
+
readMove() {
|
|
78
|
+
let t = "";
|
|
79
|
+
for (; this.position < this.input.length && this.isMoveChar(this.current()); )
|
|
80
|
+
t += this.advance();
|
|
81
|
+
return this.createToken("MOVE", t);
|
|
82
|
+
}
|
|
83
|
+
isMoveStart(t) {
|
|
84
|
+
return /[NBRQK]/.test(t) || /[a-h]/.test(t) || t === "O";
|
|
85
|
+
}
|
|
86
|
+
isMoveChar(t) {
|
|
87
|
+
return /[a-h1-8NBRQKO\-+=x#]/.test(t) || t === "+" || t === "#" || t === "!";
|
|
88
|
+
}
|
|
89
|
+
isResultStart() {
|
|
90
|
+
const t = this.input.slice(this.position);
|
|
91
|
+
return t.startsWith("1-0") || t.startsWith("0-1") || t.startsWith("1/2-1/2") || t.startsWith("*");
|
|
92
|
+
}
|
|
93
|
+
isDigit(t) {
|
|
94
|
+
return /[0-9]/.test(t);
|
|
95
|
+
}
|
|
96
|
+
skipWhitespace() {
|
|
97
|
+
for (; this.position < this.input.length && /\s/.test(this.current()); )
|
|
98
|
+
this.advance();
|
|
99
|
+
}
|
|
100
|
+
current() {
|
|
101
|
+
return this.input[this.position];
|
|
102
|
+
}
|
|
103
|
+
advance() {
|
|
104
|
+
const t = this.input[this.position];
|
|
105
|
+
return this.position++, t === `
|
|
106
|
+
` ? (this.line++, this.column = 1) : this.column++, t;
|
|
107
|
+
}
|
|
108
|
+
createToken(t, e) {
|
|
109
|
+
return {
|
|
110
|
+
type: t,
|
|
111
|
+
value: e,
|
|
112
|
+
line: this.line,
|
|
113
|
+
column: this.column
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
r as PGNTokenizer,
|
|
119
|
+
n as TokenType
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=tokenizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenizer.js","sources":["../../src/parser/tokenizer.ts"],"sourcesContent":["/**\n * Token types for PGN lexical analysis\n */\nexport enum TokenType {\n HEADER = 'HEADER',\n MOVE_NUMBER = 'MOVE_NUMBER',\n MOVE = 'MOVE',\n NAG = 'NAG',\n COMMENT = 'COMMENT',\n VARIATION_START = 'VARIATION_START',\n VARIATION_END = 'VARIATION_END',\n RESULT = 'RESULT',\n EOF = 'EOF',\n}\n\n/**\n * Represents a single token from PGN text\n */\nexport interface Token {\n type: TokenType;\n value: string;\n line: number;\n column: number;\n}\n\n/**\n * Tokenizes PGN text into a stream of tokens.\n */\nexport class PGNTokenizer {\n private input: string;\n private position: number = 0;\n private line: number = 1;\n private column: number = 1;\n\n constructor(input: string) {\n this.input = input;\n }\n\n /**\n * Returns all tokens from the input.\n */\n tokenize(): Token[] {\n const tokens: Token[] = [];\n let token: Token;\n\n while ((token = this.nextToken()).type !== TokenType.EOF) {\n tokens.push(token);\n }\n\n tokens.push(token); // Add EOF token\n return tokens;\n }\n\n /**\n * Gets the next token from the input.\n */\n private nextToken(): Token {\n this.skipWhitespace();\n\n if (this.position >= this.input.length) {\n return this.createToken(TokenType.EOF, '');\n }\n\n const char = this.input[this.position];\n\n // Header: [Key \"Value\"]\n if (char === '[') {\n return this.readHeader();\n }\n\n // Comment: {text} or ; line comment\n if (char === '{') {\n return this.readBraceComment();\n }\n\n if (char === ';') {\n return this.readLineComment();\n }\n\n // Variation markers\n if (char === '(') {\n return this.createToken(TokenType.VARIATION_START, this.advance());\n }\n\n if (char === ')') {\n return this.createToken(TokenType.VARIATION_END, this.advance());\n }\n\n // NAG: $1, $2, etc.\n if (char === '$') {\n return this.readNAG();\n }\n\n // Result: 1-0, 0-1, 1/2-1/2, *\n if (this.isResultStart()) {\n return this.readResult();\n }\n\n // Move number: 1. or 1...\n if (this.isDigit(char)) {\n return this.readMoveNumber();\n }\n\n // Move in SAN notation\n if (this.isMoveStart(char)) {\n return this.readMove();\n }\n\n // Unknown character, skip it\n this.advance();\n return this.nextToken();\n }\n\n private readHeader(): Token {\n this.advance(); // skip '['\n\n let value = '[';\n let inQuotes = false;\n\n while (this.position < this.input.length) {\n const char = this.current();\n\n if (char === '\"') {\n inQuotes = !inQuotes;\n }\n\n if (char === ']' && !inQuotes) {\n value += this.advance();\n break;\n }\n\n value += this.advance();\n }\n\n return this.createToken(TokenType.HEADER, value);\n }\n\n private readBraceComment(): Token {\n this.advance(); // skip '{'\n let value = '';\n\n while (this.position < this.input.length && this.current() !== '}') {\n value += this.advance();\n }\n\n if (this.current() === '}') {\n this.advance(); // skip '}'\n }\n\n return this.createToken(TokenType.COMMENT, value.trim());\n }\n\n private readLineComment(): Token {\n this.advance(); // skip ';'\n let value = '';\n\n while (this.position < this.input.length && this.current() !== '\\n') {\n value += this.advance();\n }\n\n return this.createToken(TokenType.COMMENT, value.trim());\n }\n\n private readNAG(): Token {\n this.advance(); // skip '$'\n let value = '$';\n\n while (this.position < this.input.length && this.isDigit(this.current())) {\n value += this.advance();\n }\n\n return this.createToken(TokenType.NAG, value);\n }\n\n private readResult(): Token {\n let value = '';\n\n // Match: 1-0, 0-1, 1/2-1/2, or *\n if (this.current() === '*') {\n value = this.advance();\n } else {\n while (\n this.position < this.input.length &&\n /[01\\-\\/]/.test(this.current())\n ) {\n value += this.advance();\n }\n }\n\n return this.createToken(TokenType.RESULT, value);\n }\n\n private readMoveNumber(): Token {\n let value = '';\n\n while (this.position < this.input.length && this.isDigit(this.current())) {\n value += this.advance();\n }\n\n // Skip dots: 1. or 1...\n while (this.current() === '.') {\n value += this.advance();\n }\n\n return this.createToken(TokenType.MOVE_NUMBER, value);\n }\n\n private readMove(): Token {\n let value = '';\n\n // Read SAN move: e4, Nf3, O-O, exd5, etc.\n while (\n this.position < this.input.length &&\n this.isMoveChar(this.current())\n ) {\n value += this.advance();\n }\n\n return this.createToken(TokenType.MOVE, value);\n }\n\n private isMoveStart(char: string): boolean {\n return /[NBRQK]/.test(char) || /[a-h]/.test(char) || char === 'O';\n }\n\n private isMoveChar(char: string): boolean {\n return (\n /[a-h1-8NBRQKO\\-+=x#]/.test(char) ||\n char === '+' ||\n char === '#' ||\n char === '!'\n );\n }\n\n private isResultStart(): boolean {\n const remaining = this.input.slice(this.position);\n return (\n remaining.startsWith('1-0') ||\n remaining.startsWith('0-1') ||\n remaining.startsWith('1/2-1/2') ||\n remaining.startsWith('*')\n );\n }\n\n private isDigit(char: string): boolean {\n return /[0-9]/.test(char);\n }\n\n private skipWhitespace(): void {\n while (\n this.position < this.input.length &&\n /\\s/.test(this.current())\n ) {\n this.advance();\n }\n }\n\n private current(): string {\n return this.input[this.position];\n }\n\n private advance(): string {\n const char = this.input[this.position];\n this.position++;\n\n if (char === '\\n') {\n this.line++;\n this.column = 1;\n } else {\n this.column++;\n }\n\n return char;\n }\n\n private createToken(type: TokenType, value: string): Token {\n return {\n type,\n value,\n line: this.line,\n column: this.column,\n };\n }\n}\n"],"names":["TokenType","PGNTokenizer","input","tokens","token","char","value","inQuotes","remaining","type"],"mappings":"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"}
|
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.pgn-viewer{font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif;font-size:14px;line-height:1.8;padding:16px;background-color:#f9f9f9;border:1px solid #ddd;border-radius:4px;max-width:100%;overflow-x:auto}.pgn-move-number{color:#666;margin-right:4px;font-weight:600}.pgn-move{display:inline-block;padding:2px 6px;margin:0 2px;border-radius:3px;transition:background-color .2s}.pgn-move:hover{background-color:#e0e0e0}.pgn-move-current{background-color:#4a90e2;color:#fff;font-weight:600}.pgn-move-current:hover{background-color:#357abd}.pgn-nag{color:#d9534f;margin-left:2px;font-weight:700}.pgn-comment{color:#5a5a5a;font-style:italic;margin:0 4px}.pgn-annotation{color:#888;font-size:12px;margin-left:4px}.pgn-variation{margin-top:4px;margin-bottom:4px;color:#555}.pgn-controls{display:flex;gap:8px;padding:12px;background-color:#f0f0f0;border-radius:4px;justify-content:center}.pgn-control-button{background-color:#4a90e2;color:#fff;border:none;border-radius:4px;padding:8px 16px;font-size:16px;cursor:pointer;transition:background-color .2s;min-width:44px}.pgn-control-button:hover:not(:disabled){background-color:#357abd}.pgn-control-button:disabled{background-color:#ccc;cursor:not-allowed;opacity:.6}.pgn-control-button:active:not(:disabled){transform:scale(.95)}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function o(e) {
|
|
2
|
+
const t = {}, s = e.match(/\[%clk\s+(\d+):(\d+):(\d+)\]/);
|
|
3
|
+
if (s) {
|
|
4
|
+
const [, n, c, r] = s;
|
|
5
|
+
t.clock = parseInt(n) * 3600 + parseInt(c) * 60 + parseInt(r);
|
|
6
|
+
}
|
|
7
|
+
const a = e.match(/\[%emt\s+(\d+):(\d+):(\d+)\]/);
|
|
8
|
+
if (a) {
|
|
9
|
+
const [, n, c, r] = a;
|
|
10
|
+
t.emt = parseInt(n) * 3600 + parseInt(c) * 60 + parseInt(r);
|
|
11
|
+
}
|
|
12
|
+
return t;
|
|
13
|
+
}
|
|
14
|
+
function d(e) {
|
|
15
|
+
const t = {}, s = e.match(/\[%eval\s+([\+\-]?\d+\.?\d*|#[\+\-]?\d+)\]/);
|
|
16
|
+
if (s) {
|
|
17
|
+
const n = s[1];
|
|
18
|
+
if (n.startsWith("#")) {
|
|
19
|
+
const c = parseInt(n.slice(1));
|
|
20
|
+
t.eval = c > 0 ? 1e4 : -1e4;
|
|
21
|
+
} else
|
|
22
|
+
t.eval = parseFloat(n);
|
|
23
|
+
}
|
|
24
|
+
const a = e.match(/\[%depth\s+(\d+)\]/);
|
|
25
|
+
return a && (t.depth = parseInt(a[1])), t;
|
|
26
|
+
}
|
|
27
|
+
function l(e) {
|
|
28
|
+
const t = o(e), s = d(e), a = e.replace(/\[%clk\s+\d+:\d+:\d+\]/g, "").replace(/\[%emt\s+\d+:\d+:\d+\]/g, "").replace(/\[%eval\s+[\+\-]?\d+\.?\d*\]/g, "").replace(/\[%eval\s+#[\+\-]?\d+\]/g, "").replace(/\[%depth\s+\d+\]/g, "").trim();
|
|
29
|
+
return {
|
|
30
|
+
...t,
|
|
31
|
+
...s,
|
|
32
|
+
cleanComment: a
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
l as parseAnnotations,
|
|
37
|
+
d as parseEvalAnnotation,
|
|
38
|
+
o as parseTimeAnnotation
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=annotation-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotation-parser.js","sources":["../../src/utils/annotation-parser.ts"],"sourcesContent":["/**\n * Parses time annotations from comments.\n * Supports: [%clk 0:04:32], [%emt 0:00:03]\n */\nexport function parseTimeAnnotation(comment: string): {\n clock?: number;\n emt?: number;\n} {\n const result: { clock?: number; emt?: number } = {};\n\n // Parse clock: [%clk 0:04:32]\n const clockMatch = comment.match(/\\[%clk\\s+(\\d+):(\\d+):(\\d+)\\]/);\n if (clockMatch) {\n const [, hours, minutes, seconds] = clockMatch;\n result.clock =\n parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(seconds);\n }\n\n // Parse EMT: [%emt 0:00:03]\n const emtMatch = comment.match(/\\[%emt\\s+(\\d+):(\\d+):(\\d+)\\]/);\n if (emtMatch) {\n const [, hours, minutes, seconds] = emtMatch;\n result.emt =\n parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(seconds);\n }\n\n return result;\n}\n\n/**\n * Parses evaluation annotation from comments.\n * Supports: [%eval +0.35], [%eval -1.2], [%eval #3]\n */\nexport function parseEvalAnnotation(comment: string): {\n eval?: number;\n depth?: number;\n} {\n const result: { eval?: number; depth?: number } = {};\n\n // Parse eval: [%eval +0.35] or [%eval #3]\n const evalMatch = comment.match(/\\[%eval\\s+([\\+\\-]?\\d+\\.?\\d*|#[\\+\\-]?\\d+)\\]/);\n if (evalMatch) {\n const evalStr = evalMatch[1];\n if (evalStr.startsWith('#')) {\n // Mate score: convert to large number\n const mateIn = parseInt(evalStr.slice(1));\n result.eval = mateIn > 0 ? 10000 : -10000;\n } else {\n result.eval = parseFloat(evalStr);\n }\n }\n\n // Parse depth: [%depth 18]\n const depthMatch = comment.match(/\\[%depth\\s+(\\d+)\\]/);\n if (depthMatch) {\n result.depth = parseInt(depthMatch[1]);\n }\n\n return result;\n}\n\n/**\n * Parses all annotations from a comment string.\n */\nexport function parseAnnotations(comment: string): {\n clock?: number;\n emt?: number;\n eval?: number;\n depth?: number;\n cleanComment: string;\n} {\n const timeData = parseTimeAnnotation(comment);\n const evalData = parseEvalAnnotation(comment);\n\n // Remove annotations from comment\n const cleanComment = comment\n .replace(/\\[%clk\\s+\\d+:\\d+:\\d+\\]/g, '')\n .replace(/\\[%emt\\s+\\d+:\\d+:\\d+\\]/g, '')\n .replace(/\\[%eval\\s+[\\+\\-]?\\d+\\.?\\d*\\]/g, '')\n .replace(/\\[%eval\\s+#[\\+\\-]?\\d+\\]/g, '')\n .replace(/\\[%depth\\s+\\d+\\]/g, '')\n .trim();\n\n return {\n ...timeData,\n ...evalData,\n cleanComment,\n };\n}\n"],"names":["parseTimeAnnotation","comment","result","clockMatch","hours","minutes","seconds","emtMatch","parseEvalAnnotation","evalMatch","evalStr","mateIn","depthMatch","parseAnnotations","timeData","evalData","cleanComment"],"mappings":"AAIO,SAASA,EAAoBC,GAGlC;AACE,QAAMC,IAA2C,CAAA,GAG3CC,IAAaF,EAAQ,MAAM,8BAA8B;AAC/D,MAAIE,GAAY;AACZ,UAAM,GAAGC,GAAOC,GAASC,CAAO,IAAIH;AACpC,IAAAD,EAAO,QACH,SAASE,CAAK,IAAI,OAAO,SAASC,CAAO,IAAI,KAAK,SAASC,CAAO;AAAA,EAC1E;AAGA,QAAMC,IAAWN,EAAQ,MAAM,8BAA8B;AAC7D,MAAIM,GAAU;AACV,UAAM,GAAGH,GAAOC,GAASC,CAAO,IAAIC;AACpC,IAAAL,EAAO,MACH,SAASE,CAAK,IAAI,OAAO,SAASC,CAAO,IAAI,KAAK,SAASC,CAAO;AAAA,EAC1E;AAEA,SAAOJ;AACX;AAMO,SAASM,EAAoBP,GAGlC;AACE,QAAMC,IAA4C,CAAA,GAG5CO,IAAYR,EAAQ,MAAM,4CAA4C;AAC5E,MAAIQ,GAAW;AACX,UAAMC,IAAUD,EAAU,CAAC;AAC3B,QAAIC,EAAQ,WAAW,GAAG,GAAG;AAEzB,YAAMC,IAAS,SAASD,EAAQ,MAAM,CAAC,CAAC;AACxC,MAAAR,EAAO,OAAOS,IAAS,IAAI,MAAQ;AAAA,IACvC;AACI,MAAAT,EAAO,OAAO,WAAWQ,CAAO;AAAA,EAExC;AAGA,QAAME,IAAaX,EAAQ,MAAM,oBAAoB;AACrD,SAAIW,MACAV,EAAO,QAAQ,SAASU,EAAW,CAAC,CAAC,IAGlCV;AACX;AAKO,SAASW,EAAiBZ,GAM/B;AACE,QAAMa,IAAWd,EAAoBC,CAAO,GACtCc,IAAWP,EAAoBP,CAAO,GAGtCe,IAAef,EAChB,QAAQ,2BAA2B,EAAE,EACrC,QAAQ,2BAA2B,EAAE,EACrC,QAAQ,iCAAiC,EAAE,EAC3C,QAAQ,4BAA4B,EAAE,EACtC,QAAQ,qBAAqB,EAAE,EAC/B,KAAA;AAEL,SAAO;AAAA,IACH,GAAGa;AAAA,IACH,GAAGC;AAAA,IACH,cAAAC;AAAA,EAAA;AAER;"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const o = {
|
|
2
|
+
1: "!",
|
|
3
|
+
// Good move
|
|
4
|
+
2: "?",
|
|
5
|
+
// Poor move
|
|
6
|
+
3: "!!",
|
|
7
|
+
// Very good move
|
|
8
|
+
4: "??",
|
|
9
|
+
// Very poor move
|
|
10
|
+
5: "!?",
|
|
11
|
+
// Interesting move
|
|
12
|
+
6: "?!",
|
|
13
|
+
// Questionable move
|
|
14
|
+
7: "□",
|
|
15
|
+
// Forced move
|
|
16
|
+
10: "=",
|
|
17
|
+
// Equal position
|
|
18
|
+
13: "∞",
|
|
19
|
+
// Unclear position
|
|
20
|
+
14: "⩲",
|
|
21
|
+
// White has slight advantage
|
|
22
|
+
15: "⩱",
|
|
23
|
+
// Black has slight advantage
|
|
24
|
+
16: "±",
|
|
25
|
+
// White has moderate advantage
|
|
26
|
+
17: "∓",
|
|
27
|
+
// Black has moderate advantage
|
|
28
|
+
18: "+−",
|
|
29
|
+
// White has decisive advantage
|
|
30
|
+
19: "−+",
|
|
31
|
+
// Black has decisive advantage
|
|
32
|
+
22: "⨀",
|
|
33
|
+
// Zugzwang
|
|
34
|
+
32: "⟳",
|
|
35
|
+
// Development advantage
|
|
36
|
+
36: "↑",
|
|
37
|
+
// Initiative
|
|
38
|
+
40: "→",
|
|
39
|
+
// Attack
|
|
40
|
+
132: "⇆",
|
|
41
|
+
// Counterplay
|
|
42
|
+
138: "⊕"
|
|
43
|
+
// Time pressure
|
|
44
|
+
};
|
|
45
|
+
function t(n) {
|
|
46
|
+
return o[n] || `$${n}`;
|
|
47
|
+
}
|
|
48
|
+
function r(n) {
|
|
49
|
+
return n.map(t);
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
o as NAG_SYMBOLS,
|
|
53
|
+
t as nagToSymbol,
|
|
54
|
+
r as nagsToSymbols
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=nag-symbols.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nag-symbols.js","sources":["../../src/utils/nag-symbols.ts"],"sourcesContent":["/**\n * Maps NAG (Numeric Annotation Glyph) codes to their symbolic representations.\n */\nexport const NAG_SYMBOLS: Record<number, string> = {\n 1: '!', // Good move\n 2: '?', // Poor move\n 3: '!!', // Very good move\n 4: '??', // Very poor move\n 5: '!?', // Interesting move\n 6: '?!', // Questionable move\n 7: '□', // Forced move\n 10: '=', // Equal position\n 13: '∞', // Unclear position\n 14: '⩲', // White has slight advantage\n 15: '⩱', // Black has slight advantage\n 16: '±', // White has moderate advantage\n 17: '∓', // Black has moderate advantage\n 18: '+−', // White has decisive advantage\n 19: '−+', // Black has decisive advantage\n 22: '⨀', // Zugzwang\n 32: '⟳', // Development advantage\n 36: '↑', // Initiative\n 40: '→', // Attack\n 132: '⇆', // Counterplay\n 138: '⊕', // Time pressure\n};\n\n/**\n * Converts a NAG code to its symbolic representation.\n */\nexport function nagToSymbol(nag: number): string {\n return NAG_SYMBOLS[nag] || `$${nag}`;\n}\n\n/**\n * Converts an array of NAG codes to their symbolic representations.\n */\nexport function nagsToSymbols(nags: number[]): string[] {\n return nags.map(nagToSymbol);\n}\n"],"names":["NAG_SYMBOLS","nagToSymbol","nag","nagsToSymbols","nags"],"mappings":"AAGO,MAAMA,IAAsC;AAAA,EAC/C,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EACH,GAAG;AAAA;AAAA,EACH,IAAI;AAAA;AAAA,EACJ,IAAI;AAAA;AAAA,EACJ,IAAI;AAAA;AAAA,EACJ,IAAI;AAAA;AAAA,EACJ,IAAI;AAAA;AAAA,EACJ,IAAI;AAAA;AAAA,EACJ,IAAI;AAAA;AAAA,EACJ,IAAI;AAAA;AAAA,EACJ,IAAI;AAAA;AAAA,EACJ,IAAI;AAAA;AAAA,EACJ,IAAI;AAAA;AAAA,EACJ,IAAI;AAAA;AAAA,EACJ,KAAK;AAAA;AAAA,EACL,KAAK;AAAA;AACT;AAKO,SAASC,EAAYC,GAAqB;AAC7C,SAAOF,EAAYE,CAAG,KAAK,IAAIA,CAAG;AACtC;AAKO,SAASC,EAAcC,GAA0B;AACpD,SAAOA,EAAK,IAAIH,CAAW;AAC/B;"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsxs as y, jsx as d } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback as f, useEffect as A } from "react";
|
|
3
|
+
/* empty css */
|
|
4
|
+
function x({
|
|
5
|
+
cursor: l,
|
|
6
|
+
onPositionChange: t,
|
|
7
|
+
enableKeyboard: w = !0,
|
|
8
|
+
className: b = ""
|
|
9
|
+
}) {
|
|
10
|
+
const r = f(() => {
|
|
11
|
+
l.toStart(), t == null || t(l);
|
|
12
|
+
}, [l, t]), p = f(() => {
|
|
13
|
+
l.prev(), t == null || t(l);
|
|
14
|
+
}, [l, t]), v = f(() => {
|
|
15
|
+
l.next(), t == null || t(l);
|
|
16
|
+
}, [l, t]), m = f(() => {
|
|
17
|
+
l.toEnd(), t == null || t(l);
|
|
18
|
+
}, [l, t]);
|
|
19
|
+
return A(() => {
|
|
20
|
+
if (!w) return;
|
|
21
|
+
const k = (e) => {
|
|
22
|
+
e.ctrlKey || e.metaKey ? e.key === "ArrowLeft" ? (e.preventDefault(), r()) : e.key === "ArrowRight" && (e.preventDefault(), m()) : e.key === "ArrowLeft" ? (e.preventDefault(), p()) : e.key === "ArrowRight" ? (e.preventDefault(), v()) : e.key === "ArrowUp" ? (e.preventDefault(), l.exitVariation(), t == null || t(l)) : e.key === "ArrowDown" && (e.preventDefault(), l.enterVariation(0), t == null || t(l));
|
|
23
|
+
};
|
|
24
|
+
return window.addEventListener("keydown", k), () => window.removeEventListener("keydown", k);
|
|
25
|
+
}, [w, r, p, v, m, l, t]), /* @__PURE__ */ y("div", { className: `pgn-controls ${b}`, children: [
|
|
26
|
+
/* @__PURE__ */ d(
|
|
27
|
+
"button",
|
|
28
|
+
{
|
|
29
|
+
className: "pgn-control-button",
|
|
30
|
+
onClick: r,
|
|
31
|
+
disabled: l.isAtStart(),
|
|
32
|
+
title: "First move (Ctrl+←)",
|
|
33
|
+
children: "⏮"
|
|
34
|
+
}
|
|
35
|
+
),
|
|
36
|
+
/* @__PURE__ */ d(
|
|
37
|
+
"button",
|
|
38
|
+
{
|
|
39
|
+
className: "pgn-control-button",
|
|
40
|
+
onClick: p,
|
|
41
|
+
disabled: l.isAtStart(),
|
|
42
|
+
title: "Previous move (←)",
|
|
43
|
+
children: "◀"
|
|
44
|
+
}
|
|
45
|
+
),
|
|
46
|
+
/* @__PURE__ */ d(
|
|
47
|
+
"button",
|
|
48
|
+
{
|
|
49
|
+
className: "pgn-control-button",
|
|
50
|
+
onClick: v,
|
|
51
|
+
disabled: l.isAtEnd(),
|
|
52
|
+
title: "Next move (→)",
|
|
53
|
+
children: "▶"
|
|
54
|
+
}
|
|
55
|
+
),
|
|
56
|
+
/* @__PURE__ */ d(
|
|
57
|
+
"button",
|
|
58
|
+
{
|
|
59
|
+
className: "pgn-control-button",
|
|
60
|
+
onClick: m,
|
|
61
|
+
disabled: l.isAtEnd(),
|
|
62
|
+
title: "Last move (Ctrl+→)",
|
|
63
|
+
children: "⏭"
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
] });
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
x as PGNControls
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=PGNControls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PGNControls.js","sources":["../../src/viewer/PGNControls.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useCallback } from 'react';\nimport { GameCursor } from '../cursor/game-cursor.js';\nimport './styles.css';\n\nexport interface PGNControlsProps {\n /** The game cursor to control */\n cursor: GameCursor;\n /** Callback when position changes */\n onPositionChange?: (cursor: GameCursor) => void;\n /** Enable keyboard controls (default: true) */\n enableKeyboard?: boolean;\n /** Custom class name */\n className?: string;\n}\n\n/**\n * Navigation controls for a chess game.\n */\nexport function PGNControls({\n cursor,\n onPositionChange,\n enableKeyboard = true,\n className = '',\n}: PGNControlsProps) {\n const handleFirst = useCallback(() => {\n cursor.toStart();\n onPositionChange?.(cursor);\n }, [cursor, onPositionChange]);\n\n const handlePrev = useCallback(() => {\n cursor.prev();\n onPositionChange?.(cursor);\n }, [cursor, onPositionChange]);\n\n const handleNext = useCallback(() => {\n cursor.next();\n onPositionChange?.(cursor);\n }, [cursor, onPositionChange]);\n\n const handleLast = useCallback(() => {\n cursor.toEnd();\n onPositionChange?.(cursor);\n }, [cursor, onPositionChange]);\n\n // Keyboard controls\n useEffect(() => {\n if (!enableKeyboard) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.ctrlKey || e.metaKey) {\n if (e.key === 'ArrowLeft') {\n e.preventDefault();\n handleFirst();\n } else if (e.key === 'ArrowRight') {\n e.preventDefault();\n handleLast();\n }\n } else {\n if (e.key === 'ArrowLeft') {\n e.preventDefault();\n handlePrev();\n } else if (e.key === 'ArrowRight') {\n e.preventDefault();\n handleNext();\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n cursor.exitVariation();\n onPositionChange?.(cursor);\n } else if (e.key === 'ArrowDown') {\n e.preventDefault();\n cursor.enterVariation(0);\n onPositionChange?.(cursor);\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [enableKeyboard, handleFirst, handlePrev, handleNext, handleLast, cursor, onPositionChange]);\n\n return (\n <div className={`pgn-controls ${className}`}>\n <button\n className=\"pgn-control-button\"\n onClick={handleFirst}\n disabled={cursor.isAtStart()}\n title=\"First move (Ctrl+←)\"\n >\n ⏮\n </button>\n <button\n className=\"pgn-control-button\"\n onClick={handlePrev}\n disabled={cursor.isAtStart()}\n title=\"Previous move (←)\"\n >\n ◀\n </button>\n <button\n className=\"pgn-control-button\"\n onClick={handleNext}\n disabled={cursor.isAtEnd()}\n title=\"Next move (→)\"\n >\n ▶\n </button>\n <button\n className=\"pgn-control-button\"\n onClick={handleLast}\n disabled={cursor.isAtEnd()}\n title=\"Last move (Ctrl+→)\"\n >\n ⏭\n </button>\n </div>\n );\n}\n"],"names":["PGNControls","cursor","onPositionChange","enableKeyboard","className","handleFirst","useCallback","handlePrev","handleNext","handleLast","useEffect","handleKeyDown","jsxs","jsx"],"mappings":";;;AAoBO,SAASA,EAAY;AAAA,EACxB,QAAAC;AAAA,EACA,kBAAAC;AAAA,EACA,gBAAAC,IAAiB;AAAA,EACjB,WAAAC,IAAY;AAChB,GAAqB;AACjB,QAAMC,IAAcC,EAAY,MAAM;AAClC,IAAAL,EAAO,QAAA,GACPC,KAAA,QAAAA,EAAmBD;AAAA,EACvB,GAAG,CAACA,GAAQC,CAAgB,CAAC,GAEvBK,IAAaD,EAAY,MAAM;AACjC,IAAAL,EAAO,KAAA,GACPC,KAAA,QAAAA,EAAmBD;AAAA,EACvB,GAAG,CAACA,GAAQC,CAAgB,CAAC,GAEvBM,IAAaF,EAAY,MAAM;AACjC,IAAAL,EAAO,KAAA,GACPC,KAAA,QAAAA,EAAmBD;AAAA,EACvB,GAAG,CAACA,GAAQC,CAAgB,CAAC,GAEvBO,IAAaH,EAAY,MAAM;AACjC,IAAAL,EAAO,MAAA,GACPC,KAAA,QAAAA,EAAmBD;AAAA,EACvB,GAAG,CAACA,GAAQC,CAAgB,CAAC;AAG7B,SAAAQ,EAAU,MAAM;AACZ,QAAI,CAACP,EAAgB;AAErB,UAAMQ,IAAgB,CAAC,MAAqB;AACxC,MAAI,EAAE,WAAW,EAAE,UACX,EAAE,QAAQ,eACV,EAAE,eAAA,GACFN,EAAA,KACO,EAAE,QAAQ,iBACjB,EAAE,eAAA,GACFI,EAAA,KAGA,EAAE,QAAQ,eACV,EAAE,eAAA,GACFF,EAAA,KACO,EAAE,QAAQ,gBACjB,EAAE,eAAA,GACFC,EAAA,KACO,EAAE,QAAQ,aACjB,EAAE,eAAA,GACFP,EAAO,cAAA,GACPC,KAAA,QAAAA,EAAmBD,MACZ,EAAE,QAAQ,gBACjB,EAAE,eAAA,GACFA,EAAO,eAAe,CAAC,GACvBC,KAAA,QAAAA,EAAmBD;AAAA,IAG/B;AAEA,kBAAO,iBAAiB,WAAWU,CAAa,GACzC,MAAM,OAAO,oBAAoB,WAAWA,CAAa;AAAA,EACpE,GAAG,CAACR,GAAgBE,GAAaE,GAAYC,GAAYC,GAAYR,GAAQC,CAAgB,CAAC,GAG1F,gBAAAU,EAAC,OAAA,EAAI,WAAW,gBAAgBR,CAAS,IACrC,UAAA;AAAA,IAAA,gBAAAS;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,WAAU;AAAA,QACV,SAASR;AAAA,QACT,UAAUJ,EAAO,UAAA;AAAA,QACjB,OAAM;AAAA,QACT,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAGD,gBAAAY;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,WAAU;AAAA,QACV,SAASN;AAAA,QACT,UAAUN,EAAO,UAAA;AAAA,QACjB,OAAM;AAAA,QACT,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAGD,gBAAAY;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,WAAU;AAAA,QACV,SAASL;AAAA,QACT,UAAUP,EAAO,QAAA;AAAA,QACjB,OAAM;AAAA,QACT,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAGD,gBAAAY;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,WAAU;AAAA,QACV,SAASJ;AAAA,QACT,UAAUR,EAAO,QAAA;AAAA,QACjB,OAAM;AAAA,QACT,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAED,GACJ;AAER;"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsx as o, jsxs as l, Fragment as v } from "react/jsx-runtime";
|
|
2
|
+
import { useState as f, useEffect as $, useCallback as N } from "react";
|
|
3
|
+
import { GameCursor as x } from "../cursor/game-cursor.js";
|
|
4
|
+
import { nagToSymbol as b } from "../utils/nag-symbols.js";
|
|
5
|
+
/* empty css */
|
|
6
|
+
function k({
|
|
7
|
+
root: s,
|
|
8
|
+
cursor: a,
|
|
9
|
+
onMoveClick: t,
|
|
10
|
+
className: i = ""
|
|
11
|
+
}) {
|
|
12
|
+
const [e] = f(() => new x(s)), n = a || e, [p, c] = f(n.current.id);
|
|
13
|
+
$(() => {
|
|
14
|
+
c(n.current.id);
|
|
15
|
+
}, [n.current.id]);
|
|
16
|
+
const m = N(
|
|
17
|
+
(r) => {
|
|
18
|
+
n.goTo(r.id), c(r.id), t == null || t(r);
|
|
19
|
+
},
|
|
20
|
+
[n, t]
|
|
21
|
+
);
|
|
22
|
+
return /* @__PURE__ */ o("div", { className: `pgn-viewer ${i}`, children: h(s, p, m, 0) });
|
|
23
|
+
}
|
|
24
|
+
function h(s, a, t, i) {
|
|
25
|
+
const e = [];
|
|
26
|
+
let n = s.next;
|
|
27
|
+
for (; n; ) {
|
|
28
|
+
const p = n.id === a, c = n.color === "w";
|
|
29
|
+
n.commentBefore && e.push(
|
|
30
|
+
/* @__PURE__ */ o("span", { className: "pgn-comment", children: `{${n.commentBefore}}` }, `comment-before-${n.id}`)
|
|
31
|
+
), c && e.push(
|
|
32
|
+
/* @__PURE__ */ l("span", { className: "pgn-move-number", children: [
|
|
33
|
+
n.moveNumber,
|
|
34
|
+
"."
|
|
35
|
+
] }, `move-num-${n.id}`)
|
|
36
|
+
);
|
|
37
|
+
const m = n.nags.map(b).join("");
|
|
38
|
+
e.push(
|
|
39
|
+
/* @__PURE__ */ l(
|
|
40
|
+
"span",
|
|
41
|
+
{
|
|
42
|
+
className: `pgn-move ${p ? "pgn-move-current" : ""}`,
|
|
43
|
+
onClick: () => t(n),
|
|
44
|
+
style: { cursor: "pointer" },
|
|
45
|
+
children: [
|
|
46
|
+
n.san,
|
|
47
|
+
m && /* @__PURE__ */ o("span", { className: "pgn-nag", children: m })
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
n.id
|
|
51
|
+
)
|
|
52
|
+
), n.commentAfter && e.push(
|
|
53
|
+
/* @__PURE__ */ o("span", { className: "pgn-comment", children: `{${n.commentAfter}}` }, `comment-after-${n.id}`)
|
|
54
|
+
);
|
|
55
|
+
const r = [];
|
|
56
|
+
if (n.clock !== void 0) {
|
|
57
|
+
const u = Math.floor(n.clock / 3600), d = Math.floor(n.clock % 3600 / 60), g = n.clock % 60;
|
|
58
|
+
r.push(`clk ${u}:${d.toString().padStart(2, "0")}:${g.toString().padStart(2, "0")}`);
|
|
59
|
+
}
|
|
60
|
+
n.eval !== void 0 && r.push(`eval ${n.eval > 0 ? "+" : ""}${n.eval.toFixed(2)}`), r.length > 0 && e.push(
|
|
61
|
+
/* @__PURE__ */ l("span", { className: "pgn-annotation", children: [
|
|
62
|
+
"[",
|
|
63
|
+
r.join(", "),
|
|
64
|
+
"]"
|
|
65
|
+
] }, `annotations-${n.id}`)
|
|
66
|
+
), n.variations.length > 0 && n.variations.forEach((u, d) => {
|
|
67
|
+
e.push(
|
|
68
|
+
/* @__PURE__ */ l(
|
|
69
|
+
"div",
|
|
70
|
+
{
|
|
71
|
+
className: "pgn-variation",
|
|
72
|
+
style: { marginLeft: `${(i + 1) * 20}px` },
|
|
73
|
+
children: [
|
|
74
|
+
"(",
|
|
75
|
+
h(
|
|
76
|
+
{ ...n, next: u },
|
|
77
|
+
a,
|
|
78
|
+
t,
|
|
79
|
+
i + 1
|
|
80
|
+
),
|
|
81
|
+
")"
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
`variation-${n.id}-${d}`
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
}), n = n.next;
|
|
88
|
+
}
|
|
89
|
+
return /* @__PURE__ */ o(v, { children: e });
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
k as PGNViewer
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=PGNViewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PGNViewer.js","sources":["../../src/viewer/PGNViewer.tsx"],"sourcesContent":["'use client';\n\nimport React, { useState, useEffect, useCallback } from 'react';\nimport { MoveNode } from '../model/move-node.js';\nimport { GameCursor } from '../cursor/game-cursor.js';\nimport { nagToSymbol } from '../utils/nag-symbols.js';\nimport './styles.css';\n\nexport interface PGNViewerProps {\n /** The root node of the game tree */\n root: MoveNode;\n /** Optional cursor for external control */\n cursor?: GameCursor;\n /** Callback when a move is clicked */\n onMoveClick?: (node: MoveNode) => void;\n /** Custom class name */\n className?: string;\n}\n\n/**\n * Displays a chess game in PGN notation with support for variations.\n */\nexport function PGNViewer({\n root,\n cursor: externalCursor,\n onMoveClick,\n className = '',\n}: PGNViewerProps) {\n const [internalCursor] = useState(() => new GameCursor(root));\n const cursor = externalCursor || internalCursor;\n const [currentNodeId, setCurrentNodeId] = useState(cursor.current.id);\n\n // Update when cursor changes\n useEffect(() => {\n setCurrentNodeId(cursor.current.id);\n }, [cursor.current.id]);\n\n const handleMoveClick = useCallback(\n (node: MoveNode) => {\n cursor.goTo(node.id);\n setCurrentNodeId(node.id);\n onMoveClick?.(node);\n },\n [cursor, onMoveClick]\n );\n\n return (\n <div className={`pgn-viewer ${className}`}>\n {renderMoveTree(root, currentNodeId, handleMoveClick, 0)}\n </div>\n );\n}\n\n/**\n * Recursively renders the move tree with proper indentation for variations.\n */\nfunction renderMoveTree(\n node: MoveNode,\n currentNodeId: string,\n onMoveClick: (node: MoveNode) => void,\n depth: number\n): React.ReactNode {\n const elements: React.ReactNode[] = [];\n let currentNode: MoveNode | undefined = node.next;\n let moveIndex = 0;\n\n while (currentNode) {\n const isCurrentMove = currentNode.id === currentNodeId;\n const showMoveNumber = currentNode.color === 'w';\n\n // Comment before move\n if (currentNode.commentBefore) {\n elements.push(\n <span key={`comment-before-${currentNode.id}`} className=\"pgn-comment\">\n {`{${currentNode.commentBefore}}`}\n </span>\n );\n }\n\n // Move number for white moves\n if (showMoveNumber) {\n elements.push(\n <span key={`move-num-${currentNode.id}`} className=\"pgn-move-number\">\n {currentNode.moveNumber}.\n </span>\n );\n }\n\n // The move itself\n const nags = currentNode.nags.map(nagToSymbol).join('');\n elements.push(\n <span\n key={currentNode.id}\n className={`pgn-move ${isCurrentMove ? 'pgn-move-current' : ''}`}\n onClick={() => onMoveClick(currentNode!)}\n style={{ cursor: 'pointer' }}\n >\n {currentNode.san}\n {nags && <span className=\"pgn-nag\">{nags}</span>}\n </span>\n );\n\n // Comment after move\n if (currentNode.commentAfter) {\n elements.push(\n <span key={`comment-after-${currentNode.id}`} className=\"pgn-comment\">\n {`{${currentNode.commentAfter}}`}\n </span>\n );\n }\n\n // Annotations (clock, eval)\n const annotations: string[] = [];\n if (currentNode.clock !== undefined) {\n const hours = Math.floor(currentNode.clock / 3600);\n const minutes = Math.floor((currentNode.clock % 3600) / 60);\n const seconds = currentNode.clock % 60;\n annotations.push(`clk ${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`);\n }\n if (currentNode.eval !== undefined) {\n annotations.push(`eval ${currentNode.eval > 0 ? '+' : ''}${currentNode.eval.toFixed(2)}`);\n }\n\n if (annotations.length > 0) {\n elements.push(\n <span key={`annotations-${currentNode.id}`} className=\"pgn-annotation\">\n [{annotations.join(', ')}]\n </span>\n );\n }\n\n // Variations\n if (currentNode.variations.length > 0) {\n currentNode.variations.forEach((variation, index) => {\n elements.push(\n <div\n key={`variation-${currentNode!.id}-${index}`}\n className=\"pgn-variation\"\n style={{ marginLeft: `${(depth + 1) * 20}px` }}\n >\n (\n {renderMoveTree(\n { ...currentNode!, next: variation },\n currentNodeId,\n onMoveClick,\n depth + 1\n )}\n )\n </div>\n );\n });\n }\n\n currentNode = currentNode.next;\n moveIndex++;\n }\n\n return <>{elements}</>;\n}\n"],"names":["PGNViewer","root","externalCursor","onMoveClick","className","internalCursor","useState","GameCursor","cursor","currentNodeId","setCurrentNodeId","useEffect","handleMoveClick","useCallback","node","jsx","renderMoveTree","depth","elements","currentNode","isCurrentMove","showMoveNumber","jsxs","nags","nagToSymbol","annotations","hours","minutes","seconds","variation","index"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pgn-viewer-parser",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A production-ready PGN parser and viewer library for chess, with full support for variations, comments, NAGs, and annotations",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./viewer": {
|
|
15
|
+
"types": "./dist/viewer/index.d.ts",
|
|
16
|
+
"import": "./dist/viewer/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc && vite build",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest",
|
|
27
|
+
"type-check": "tsc --noEmit",
|
|
28
|
+
"prepublishOnly": "npm run type-check && npm run test && npm run build"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"chess",
|
|
32
|
+
"pgn",
|
|
33
|
+
"parser",
|
|
34
|
+
"viewer",
|
|
35
|
+
"react",
|
|
36
|
+
"nextjs",
|
|
37
|
+
"typescript",
|
|
38
|
+
"variations",
|
|
39
|
+
"tree"
|
|
40
|
+
],
|
|
41
|
+
"author": "",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": "^18.0.0",
|
|
45
|
+
"react-dom": "^18.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"react": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"react-dom": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^20.11.0",
|
|
57
|
+
"@types/react": "^18.2.48",
|
|
58
|
+
"@types/react-dom": "^18.2.18",
|
|
59
|
+
"react": "^18.2.0",
|
|
60
|
+
"react-dom": "^18.2.0",
|
|
61
|
+
"typescript": "^5.3.3",
|
|
62
|
+
"vite": "^5.0.11",
|
|
63
|
+
"vitest": "^1.2.0"
|
|
64
|
+
},
|
|
65
|
+
"repository": {
|
|
66
|
+
"type": "git",
|
|
67
|
+
"url": "https://github.com/yourusername/pgn-viewer-parser.git"
|
|
68
|
+
}
|
|
69
|
+
}
|