lichess-pgn-viewer
Version:
PGN viewer widget, designed to be embedded in content pages.
127 lines • 5.05 kB
JavaScript
import { makeUci } from 'chessops';
import { scalachessCharPair } from 'chessops/compat';
import { makeFen } from 'chessops/fen';
import { parsePgn, parseComment, startingPosition, transform } from 'chessops/pgn';
import { makeSanAndPlay, parseSan } from 'chessops/san';
import { Game } from './game';
import { Path } from './path';
class State {
constructor(pos, path, clocks) {
this.pos = pos;
this.path = path;
this.clocks = clocks;
this.clone = () => new State(this.pos.clone(), this.path, { ...this.clocks });
}
}
export const parseComments = (strings) => {
const comments = strings.map(parseComment);
const reduceTimes = (times) => times.reduce((last, time) => (typeof time == undefined ? last : time), undefined);
return {
texts: comments.map(c => c.text).filter(t => !!t),
shapes: comments.flatMap(c => c.shapes),
clock: reduceTimes(comments.map(c => c.clock)),
emt: reduceTimes(comments.map(c => c.emt)),
};
};
export const makeGame = (pgn, lichess = false) => {
var _a, _b;
const game = parsePgn(pgn)[0] || parsePgn('*')[0];
const start = startingPosition(game.headers).unwrap();
const fen = makeFen(start.toSetup());
const comments = parseComments(game.comments || []);
const headers = new Map(Array.from(game.headers, ([key, value]) => [key.toLowerCase(), value]));
const metadata = makeMetadata(headers, lichess);
const initial = {
fen,
turn: start.turn,
check: start.isCheck(),
pos: start.clone(),
comments: comments.texts,
shapes: comments.shapes,
clocks: {
white: ((_a = metadata.timeControl) === null || _a === void 0 ? void 0 : _a.initial) || comments.clock,
black: ((_b = metadata.timeControl) === null || _b === void 0 ? void 0 : _b.initial) || comments.clock,
},
};
const moves = makeMoves(start, game.moves, metadata);
const players = makePlayers(headers, metadata);
return new Game(initial, moves, players, metadata);
};
const makeMoves = (start, moves, metadata) => transform(moves, new State(start, Path.root, {}), (state, node, _index) => {
const move = parseSan(state.pos, node.san);
if (!move)
return undefined;
const moveId = scalachessCharPair(move);
const path = state.path.append(moveId);
const san = makeSanAndPlay(state.pos, move);
state.path = path;
const setup = state.pos.toSetup();
const comments = parseComments(node.comments || []);
const startingComments = parseComments(node.startingComments || []);
const shapes = [...comments.shapes, ...startingComments.shapes];
const ply = (setup.fullmoves - 1) * 2 + (state.pos.turn === 'white' ? 0 : 1);
let clocks = (state.clocks = makeClocks(state.clocks, state.pos.turn, comments.clock));
if (ply < 2 && metadata.timeControl)
clocks = {
white: metadata.timeControl.initial,
black: metadata.timeControl.initial,
...clocks,
};
const moveNode = {
path,
ply,
move,
san,
uci: makeUci(move),
fen: makeFen(state.pos.toSetup()),
turn: state.pos.turn,
check: state.pos.isCheck(),
comments: comments.texts,
startingComments: startingComments.texts,
nags: node.nags || [],
shapes,
clocks,
emt: comments.emt,
};
return moveNode;
});
const makeClocks = (prev, turn, clk) => turn == 'white' ? { ...prev, black: clk } : { ...prev, white: clk };
function makePlayers(headers, metadata) {
const get = (color, field) => {
const raw = headers.get(`${color}${field}`);
return raw == '?' || raw == '' ? undefined : raw;
};
const makePlayer = (color) => {
const name = get(color, '');
return {
name,
title: get(color, 'title'),
rating: parseInt(get(color, 'elo') || '') || undefined,
isLichessUser: metadata.isLichess && !!(name === null || name === void 0 ? void 0 : name.match(/^[a-z0-9][a-z0-9_-]{0,28}[a-z0-9]$/i)),
};
};
return {
white: makePlayer('white'),
black: makePlayer('black'),
};
}
function makeMetadata(headers, lichess) {
var _a;
const site = headers.get('source') || headers.get('site');
const tcs = (_a = headers
.get('timecontrol')) === null || _a === void 0 ? void 0 : _a.split('+').map(x => parseInt(x));
const timeControl = tcs && tcs[0]
? {
initial: tcs[0],
increment: tcs[1] || 0,
}
: undefined;
const orientation = headers.get('orientation');
return {
externalLink: site && site.match(/^https?:\/\//) ? site : undefined,
isLichess: !!(lichess && (site === null || site === void 0 ? void 0 : site.startsWith(lichess))),
timeControl,
orientation: orientation === 'white' || orientation === 'black' ? orientation : undefined,
};
}
//# sourceMappingURL=pgn.js.map