Chess PGN, Portable Game Notation, Javascript Library
npm install pgn.jsChess PGN, Portable Game Notation, Javascript Library
1. Multiple Games Parsing and Generation
2. Commments and Variations Handling
3. Sync for CLI use, Async for large file handling with callbacks
4. Single dependency chess.js, Exported as well
``js[Event "Sample"]
import { Pgn } from 'pgn.js';
const pgn = new Pgn(
[Site "Toronto"]
[Date "2022.11.17"]
{This is a sample game for Pgn.js}
1.d4 (1.e4 c5 (1...e5 Bc4) (1...e6)) d5 {move comment}
2.c4! +- (2.Nf3 {variation move comment} Nf6)
(2. {comment before a move} Bf4) 2...c6 Nf3);
console.log(pgn.pgn());
`$3
`
[Event "Sample"]
[Site "Toronto"]
[Date "2022.11.17"]
{ This is a sample game for Pgn.js }
1.d4
( 1.e4 c5
( 1...e5 2.Bc4 )
( 1...e6 )
)
1...d5 { move comment } 2.c4! +-
( 2.Nf3 { variation move comment } 2...Nf6 )
( 2. { comment before a move } Bf4 )
2...c6 3.Nf3 *
`
`js
import { Pgn } from 'pgn.js';
const pgn = new Pgn();
let game = pgn.newgame();
game.setTag('Event', 'Sample');
game.setFen('rn3rk1/ppp1b1pp/1n2p3/4N2Q/3qNR2/8/PPP3PP/R1B4K b - - 0 13');
// add a move
let mvQxe4 = game.add('Qxe4');
// 13...Qxe4
// add a move next to the previous one
game.add('Rxf8');
// 13...Qxe4 14.Rxf8+
// add a varation to the next to 'Qxe4'
game.add('Nd3', mvQxe4);
// 13...Qxe4 14.Rxf8+
// ( 14.Nd3 )
// add 2 variations at the beginning
// null == the begin of the game
let mvN8d7 = game.add('N8d7', null);
game.add('Nc6', null);
// 13...Qxe4
// ( 13...N8d7 )
// ( 13...Nc6 )
// 14.Rxf8+
// ( 14.Nd3 )
// add moves next to 'N8d7'
game.add('Nxd7', mvN8d7);
game.add('Qxe4'); // undefined == next to the previous
// 13...Qxe4
// ( 13...N8d7 14.Nxd7 Qxe4 )
// ( 13...Nc6 )
// 14.Rxf8+
// ( 14.Nd3 )
// add a variation next to' N8d7'
game.add('Nf7', mvN8d7);
// 13...Qxe4
// ( 13...N8d7 14.Nxd7
// ( 14.Nf7 )
// 14...Qxe4 )
// ( 13...Nc6 )
// 14.Rxf8+
// ( 14.Nd3 )
// add moves to the previous
game.add('Qd5');
game.add('Nh6');
console.log(game.pgn());
`
`
[Event "Sample"]
[FEN "rn3rk1/ppp1b1pp/1n2p3/4N2Q/3qNR2/8/PPP3PP/R1B4K b - - 0 13"]
[SetUp "1"]
13...Qxe4
( 13...N8d7 14.Nxd7
( 14.Nf7 Qd5 15.Nh6+ )
14...Qxe4 )
( 13...Nc6 )
14.Rxf8+
( 14.Nd3 )
*
`
`js`
Game.add(san: string, prev?: Move | null, varmode?: VAR): Move | null;
// prev
// if 'undefined', next to the previously added move
// if 'null', as the first move
// otherwise, next to 'prev' move
//
// varmode
// if 'undefined' or 'null', Game.var, default==VAR.last
// VAR.last, as the last variation
// VAR.next, as the next variation
// VAR.main, as the main line
// VAR.replace, delete existing, no variation
`js
// add a move
let mvQxe4 = game.add('Qxe4');
// 13...Qxe4
// add a move next to the previous move 'Qxe4'
game.add('Rxf8');
// 13...Qxe4 14.Rxf8+
``$3
js`
game.add('Nd3', mvQxe4);
game.add('Bd2', mvQxe4);
game.add('Be3', mvQxe4);
// 13...Qxe4 14.Rxf8+
// ( 14.Nd3 )
// ( 14.Bd2 )
// ( 14.Be3 )
`js`
game.add('Nd3', mvQxe4, VAR.next);
game.add('Bd2', mvQxe4, VAR.next);
game.add('Be3', mvQxe4, VAR.next);
// 13...Qxe4 14.Rxf8+
// ( 14.Be3 )
// ( 14.Bd2 )
// ( 14.Nd3 )
`js`
game.add('Nd3', mvQxe4, VAR.main);
game.add('Bd2', mvQxe4, VAR.main);
game.add('Be3', mvQxe4, VAR.main);
// 13...Qxe4 14.Be3
// ( 14.Bd2 )
// ( 14.Nd3 )
// ( 14.Rxf8+ )
`js`
game.add('Nd3', mvQxe4, VAR.replace);
game.add('Bd2', mvQxe4, VAR.replace);
game.add('Be3', mvQxe4, VAR.replace);
// 13...Qxe4 14.Be3
`js`
game.var = VAR.main;
game.add('Nd3', mvQxe4);
game.add('Bd2', mvQxe4);
game.add('Be3', mvQxe4);
// 13...Qxe4 14.Be3
// ( 14.Bd2 )
// ( 14.Nd3 )
// ( 14.Rxf8+ )
Without loading the whole file, parsing games as reading the input file.
`js
async Pgn.load(path: string, opts?: Option): Pgn;
// stdin if path == ''
opts.onGame: (game: Game, error: Error) => void
opts.onFinish: () => void
`
In this example, we write a parsed game to the output file as soon as it's parsed.
`js
import { createWriteStream } from 'node:fs';
import { Pgn } from 'pgn.js';
var writer = createWriteStream('output.pgn');
let ngames = 0;
Pgn.load('./pgn/big.pgn', {onGame: (game) => {
ngames++;
writer.write(game.pgn() + '\n');
console.log(${ngames} parsed);
}});
`
`ts`
type Err = {
msg?: string | undefined;
fen?: string | undefined;
san?: string | undefined;
num?: number | undefined;
movetext?: string | undefined;
/**
* unexpected token
*/
found?: string | undefined;
location?: any | undefined;
start: {
offset: number;
line: number;
column: number;
};
end: {
offset: number;
line: number;
column: number;
};
};
`js
import { createWriteStream } from 'node:fs';
import { Pgn } from 'pgn.js';
var writer = createWriteStream('output.pgn');
let ngames = 0;
Pgn.load('./pgn/big2.pgn', {onGame: (game, err) => {
ngames++;
if(err) {
game.tags.push({name: 'pgn.js', value: 'parsing error encountered'});
if(err.location) {
console.log('found:', err.found, ' at ', err.location, ' with movetext: ', err.movetext);
} else {
console.log(err);
}
}
writer.write(game.pgn() + '\n');
console.log(${ngames} parsed ${err?'- error!':''});`
}});
`ts`
type OnGame = (game: Game, error: Err) => void;
type OnFinish = () => void;
type Options = {
/**
* print error message
*/
verbose?: boolean | undefined;
/**
* called when a game is parsed
*/
onGame: OnGame;
onFinish: OnFinish;
};
`ts`
type Move = {
/**
* 'w', 'b'
*/
color: string;
/**
* square
*/
from: string;
/**
* square
*/
to: string;
/**
* 'n' - a non-capture
* 'b' - a pawn push of two squares
* 'e' - an en passant capture
* 'c' - a standard capture
* 'p' - a promotion
* 'k' - kingside castling
* 'q' - queenside castling
*/
flags: string;
/**
* p, b, n, r, q, k
*/
piece: string;
/**
* SAN notation
*/
san: string;
captured?: string | undefined;
/**
* extended by pgn.js
*/
promotion?: string | undefined;
/**
* move number, not ply
*/
num: number;
/**
* position fen
*/
fen: string;
/**
* uci long algerbraic notation
*/
uci: string;
comment: {
pre?: string | undefined;
before?: string | undefined;
after?: string | undefined;
};
nags: string[];
/**
* variations (RAV)
*/
vars: Move[][];
over: {
mate?: boolean | undefined;
draw?: string | undefined;
};
ply: number;
/**
* the line contaning this move
*/
line: Move[];
/**
* previous move
*/
prev: Move;
};
`ts`
type Tag = {
name: string;
valuee: string;
};
js
pgn.games = Game[];
`$3
`js
Pgn(pgn: string, opts: Options);pgn.count() : Number // # of games
pgn.game(idx: Number): Game // access a game
pgn.newgame(): Game // create a new game
pgn.pgn(): void; // pgn string
static async Pgn.load(path: string, opts: Options): Pgn
// load from file, stdin if path == ''
`Game
$3
`js
game.tags: Tag[];
game.moves: Move[];
game.gtm : string; // game termintion mark
`$3
`ts
game.setupFen(): string | undefined // setup fen string or undefined if none
game.setFen(fen: string): void; // set setup fen string (and SetUp)
game.delFen(): void; // delete setup fen string (and SetUp)
game.setTag(name: string, value: string): void; // set a tag
game.delTag(_name: any): void; // delete a taggame.add(san: string, prev?: Move | undefined, varmode?: {
replace: string;
main: string;
next: string;
last: string;
} | undefined): Move;
game.pgn(): void; // pgn string
``