kokopu
Version:
A JavaScript/TypeScript library implementing the chess game rules and providing tools to read/write the standard chess file formats.
691 lines • 30.6 kB
JavaScript
"use strict";
/*!
* -------------------------------------------------------------------------- *
* *
* Kokopu - A JavaScript/TypeScript chess library. *
* <https://www.npmjs.com/package/kokopu> *
* Copyright (C) 2018-2026 Yoann Le Montagner <yo35 -at- melix.net> *
* *
* Kokopu is free software: you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public License *
* as published by the Free Software Foundation, either version 3 of *
* the License, or (at your option) any later version. *
* *
* Kokopu is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General *
* Public License along with this program. If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* -------------------------------------------------------------------------- */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Game = void 0;
const date_value_1 = require("./date_value");
const exception_1 = require("./exception");
const helper_1 = require("./helper");
const i18n_1 = require("./i18n");
const position_1 = require("./position");
const common_1 = require("./private_game/common");
const node_variation_impl_1 = require("./private_game/node_variation_impl");
const pojo_util_1 = require("./private_game/pojo_util");
const base_types_impl_1 = require("./private_position/base_types_impl");
/**
* Chess game, with the move history, the position at each step of the game, the comments and annotations (if any),
* the result of the game, and some meta-data such as the name of the players, the date of the game, the name of the tournament, etc...
*/
class Game {
constructor() {
this._playerName = [undefined, undefined];
this._playerElo = [undefined, undefined];
this._playerTitle = [undefined, undefined];
this._round = [undefined, undefined, undefined];
this._result = 3 /* GameResultImpl.LINE */;
this._moveTreeRoot = new node_variation_impl_1.MoveTreeRoot();
}
/**
* Clear all the headers (player names, elos, titles, event name, date, etc...).
*
* The {@link Game.result} header is reseted to its default value.
* The initial position and moves are not modified.
*/
clearHeaders() {
this._playerName = [undefined, undefined];
this._playerElo = [undefined, undefined];
this._playerTitle = [undefined, undefined];
this._event = undefined;
this._round = [undefined, undefined, undefined];
this._date = undefined;
this._site = undefined;
this._annotator = undefined;
this._eco = undefined;
this._opening = undefined;
this._openingVariation = undefined;
this._openingSubVariation = undefined;
this._termination = undefined;
this._result = 3 /* GameResultImpl.LINE */;
}
playerName(color, value) {
const colorCode = (0, base_types_impl_1.colorFromString)(color);
if (colorCode < 0) {
throw new exception_1.IllegalArgument('Game.playerName()');
}
if (arguments.length === 1) {
return this._playerName[colorCode];
}
else {
this._playerName[colorCode] = sanitizeStringHeader(value);
}
}
playerElo(color, value) {
const colorCode = (0, base_types_impl_1.colorFromString)(color);
if (colorCode < 0) {
throw new exception_1.IllegalArgument('Game.playerElo()');
}
if (arguments.length === 1) {
return this._playerElo[colorCode];
}
else {
value = sanitizeNumberHeader(value);
if (value === undefined || (0, common_1.isPositiveInteger)(value)) {
this._playerElo[colorCode] = value;
}
else {
throw new exception_1.IllegalArgument('Game.playerElo()');
}
}
}
playerTitle(color, value) {
const colorCode = (0, base_types_impl_1.colorFromString)(color);
if (colorCode < 0) {
throw new exception_1.IllegalArgument('Game.playerTitle()');
}
if (arguments.length === 1) {
return this._playerTitle[colorCode];
}
else {
this._playerTitle[colorCode] = sanitizeStringHeader(value);
}
}
event(value) {
if (arguments.length === 0) {
return this._event;
}
else {
this._event = sanitizeStringHeader(value);
}
}
round(value) {
if (arguments.length === 0) {
return this._round[0 /* RoundPart.ROUND */];
}
else {
this._setRoundPart(0 /* RoundPart.ROUND */, value, 'Game.round()');
}
}
subRound(value) {
if (arguments.length === 0) {
return this._round[1 /* RoundPart.SUB_ROUND */];
}
else {
this._setRoundPart(1 /* RoundPart.SUB_ROUND */, value, 'Game.subRound()');
}
}
subSubRound(value) {
if (arguments.length === 0) {
return this._round[2 /* RoundPart.SUB_SUB_ROUND */];
}
else {
this._setRoundPart(2 /* RoundPart.SUB_SUB_ROUND */, value, 'Game.subSubRound()');
}
}
_setRoundPart(roundPart, value, methodName) {
value = sanitizeNumberHeader(value);
if (value === undefined || (0, common_1.isPositiveInteger)(value)) {
this._round[roundPart] = value;
}
else {
throw new exception_1.IllegalArgument(methodName);
}
}
/**
* Get the round, sub-round and sub-sub-round as a human-readable string, the 3 components being separated by dot characters.
*/
fullRound() {
return formatFullRound(this._round[0 /* RoundPart.ROUND */], this._round[1 /* RoundPart.SUB_ROUND */], this._round[2 /* RoundPart.SUB_SUB_ROUND */], '?');
}
date(valueOrYear, month, day) {
switch (arguments.length) {
case 0:
return this._date;
case 1:
if (valueOrYear === undefined || valueOrYear === null) {
this._date = undefined;
}
else if (valueOrYear instanceof date_value_1.DateValue) {
this._date = valueOrYear;
}
else if (valueOrYear instanceof Date) {
this._date = new date_value_1.DateValue(valueOrYear);
}
else if (date_value_1.DateValue.isValid(valueOrYear)) {
this._date = new date_value_1.DateValue(valueOrYear);
}
else {
throw new exception_1.IllegalArgument('Game.date()');
}
break;
default:
if (date_value_1.DateValue.isValid(valueOrYear, month, day)) {
this._date = new date_value_1.DateValue(valueOrYear, month, day);
}
else {
throw new exception_1.IllegalArgument('Game.date()');
}
break;
}
}
/**
* Get the date of the game as a standard JavaScript `Date` object.
*
* If the day of month is undefined for the current game, the returned `Date` object points at the first day of the corresponding month.
* If neither the day of month nor the month are undefined for the current game, the returned `Date` object points at the first day of the corresponding year.
*/
dateAsDate() {
return this._date === undefined ? undefined : this._date.toDate();
}
/**
* Get the date of the game as a human-readable string (e.g. `'November 1955'`, `'September 4, 2021'`).
*
* @param locales - Locales to use to generate the result. If undefined, the default locale of the execution environment is used.
* See [Intl documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation)
* for more details.
*/
dateAsString(locales) {
return this._date === undefined ? undefined : this._date.toHumanReadableString(locales);
}
site(value) {
if (arguments.length === 0) {
return this._site;
}
else {
this._site = sanitizeStringHeader(value);
}
}
annotator(value) {
if (arguments.length === 0) {
return this._annotator;
}
else {
this._annotator = sanitizeStringHeader(value);
}
}
eco(value) {
if (arguments.length === 0) {
return this._eco;
}
else {
value = sanitizeStringHeader(value);
if (value !== undefined && !(0, helper_1.isValidECO)(value)) {
throw new exception_1.IllegalArgument('Game.eco()');
}
this._eco = value;
}
}
opening(value) {
if (arguments.length === 0) {
return this._opening;
}
else {
this._opening = sanitizeStringHeader(value);
}
}
openingVariation(value) {
if (arguments.length === 0) {
return this._openingVariation;
}
else {
this._openingVariation = sanitizeStringHeader(value);
}
}
openingSubVariation(value) {
if (arguments.length === 0) {
return this._openingSubVariation;
}
else {
this._openingSubVariation = sanitizeStringHeader(value);
}
}
termination(value) {
if (arguments.length === 0) {
return this._termination;
}
else {
this._termination = sanitizeStringHeader(value);
}
}
result(value) {
if (arguments.length === 0) {
return (0, base_types_impl_1.resultToString)(this._result);
}
else {
const resultCode = (0, base_types_impl_1.resultFromString)(value);
if (resultCode < 0) {
throw new exception_1.IllegalArgument('Game.result()');
}
this._result = resultCode;
}
}
/**
* Get the chess game variant of the game.
*/
variant() {
return this._moveTreeRoot._position.variant();
}
initialPosition(initialPosition, fullMoveNumber) {
if (arguments.length === 0) {
return new position_1.Position(this._moveTreeRoot._position);
}
else {
if (!(initialPosition instanceof position_1.Position)) {
throw new exception_1.IllegalArgument('Game.initialPosition()');
}
if (arguments.length >= 2) {
if (!Number.isInteger(fullMoveNumber)) {
throw new exception_1.IllegalArgument('Game.initialPosition()');
}
this._moveTreeRoot._fullMoveNumber = fullMoveNumber;
}
else {
this._moveTreeRoot._fullMoveNumber = 1;
}
this._moveTreeRoot._position = new position_1.Position(initialPosition);
this._moveTreeRoot.clearTree();
}
}
/**
* [FEN](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation) representation of the chess position at the beginning of the game.
*
* The fifty-move clock and full-move number are set according to the move history in the string returned by this method.
*/
initialFEN() {
return this._moveTreeRoot._position.fen({
fiftyMoveClock: 0,
fullMoveNumber: this._moveTreeRoot._fullMoveNumber,
});
}
/**
* Full-move number at which the game starts.
*/
initialFullMoveNumber() {
return this._moveTreeRoot._fullMoveNumber;
}
/**
* Chess position at the end of the game.
*/
finalPosition() {
return this._moveTreeRoot.mainVariation().finalPosition();
}
/**
* [FEN](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation) representation of the chess position at the end of the game.
*
* The fifty-move clock and full-move number are set according to the move history in the string returned by this method.
*/
finalFEN() {
return this._moveTreeRoot.mainVariation().finalFEN();
}
/**
* The main variation of the game.
*/
mainVariation() {
return this._moveTreeRoot.mainVariation();
}
/**
* Return the nodes corresponding to the moves of the main variation.
*
* @param withSubVariations - If `true`, the nodes of the sub-variations are also included in the result.
*/
nodes(withSubVariations = false) {
if (!withSubVariations) {
return this.mainVariation().nodes();
}
const result = [];
function processVariation(variation) {
for (const currentNode of variation.nodes()) {
for (const nextVariation of currentNode.variations()) {
processVariation(nextVariation);
}
result.push(currentNode);
}
}
processVariation(this.mainVariation());
return result;
}
/**
* Number of half-moves in the main variation.
*
* For instance, after `1.e4 e5 2.Nf3`, the number of half-moves if 3 (2 white moves + 1 black move).
*/
plyCount() {
return this._moveTreeRoot.mainVariation().plyCount();
}
/**
* Return the node or variation corresponding to the given ID (see {@link Node.id | Node.id} and {@link Variation.id | Variation.id}
* to retrieve the ID of a node or variation).
*
* For the main variation, IDs are built as follows:
* - `'start'` is the ID of the main variation,
* - `'3w'` is for instance the ID of the node whose {@link Node.fullMoveNumber} is 3 and {@link Node.moveColor} is white
* (i.e. the 3rd white move if the game starts from the usual initial position),
* - `'end'` is an alias corresponding to the last node in the main variation (or the main variation itself if it is empty).
*
* For sub-variations, IDs are built as in the following examples:
* - `'2b-v0-start'` is the ID of the sub-variation at index 0 on node `'2b'` (in the main variation),
* - `'5w-v3-11b'` is the ID of the node whose {@link Node.fullMoveNumber} is 11 and {@link Node.moveColor} is black within
* the sub-variation at index 3 on node `'5w'` (in the main variation),
* - `'5w-v3-end'` is an alias corresponding to the last node in this sub-variation (or the sub-variation itself if it is empty).
*
* @param allowAliases - If `true`, search `id` among both IDs and ID aliases. If `false`, search among IDs only.
* @returns `undefined` if the given ID does not correspond to an existing {@link Node} and {@link Variation}.
*/
findById(id, allowAliases = true) {
return this._moveTreeRoot.findById(id, allowAliases);
}
/**
* Return the [POJO](https://en.wikipedia.org/wiki/Plain_old_Java_object) representation of the current {@link Game}.
* To be used for JSON serialization, deep cloning, etc...
*/
pojo() {
const pojo = {};
function isPlayerPOJOEmpty(game, color) {
return game._playerName[color] === undefined && game._playerElo[color] === undefined && game._playerTitle[color] === undefined;
}
function getPlayerPOJO(game, color) {
const playerPOJO = {};
if (game._playerName[color] !== undefined) {
playerPOJO.name = game._playerName[color];
}
if (game._playerElo[color] !== undefined) {
playerPOJO.elo = game._playerElo[color];
}
if (game._playerTitle[color] !== undefined) {
playerPOJO.title = game._playerTitle[color];
}
return playerPOJO;
}
// Headers
if (!isPlayerPOJOEmpty(this, 0 /* ColorImpl.WHITE */)) {
pojo.white = getPlayerPOJO(this, 0 /* ColorImpl.WHITE */);
}
if (!isPlayerPOJOEmpty(this, 1 /* ColorImpl.BLACK */)) {
pojo.black = getPlayerPOJO(this, 1 /* ColorImpl.BLACK */);
}
if (this._event !== undefined) {
pojo.event = this._event;
}
if (this._round[0 /* RoundPart.ROUND */] !== undefined) {
pojo.round = this._round[0 /* RoundPart.ROUND */];
}
if (this._round[1 /* RoundPart.SUB_ROUND */] !== undefined) {
pojo.subRound = this._round[1 /* RoundPart.SUB_ROUND */];
}
if (this._round[2 /* RoundPart.SUB_SUB_ROUND */] !== undefined) {
pojo.subSubRound = this._round[2 /* RoundPart.SUB_SUB_ROUND */];
}
if (this._date !== undefined) {
pojo.date = this._date.toString();
}
if (this._site !== undefined) {
pojo.site = this._site;
}
if (this._annotator !== undefined) {
pojo.annotator = this._annotator;
}
if (this._eco !== undefined) {
pojo.eco = this._eco;
}
if (this._opening !== undefined) {
pojo.opening = this._opening;
}
if (this._openingVariation !== undefined) {
pojo.openingVariation = this._openingVariation;
}
if (this._openingSubVariation !== undefined) {
pojo.openingSubVariation = this._openingSubVariation;
}
if (this._termination !== undefined) {
pojo.termination = this._termination;
}
if (this._result !== 3 /* GameResultImpl.LINE */) {
pojo.result = this.result();
}
// Moves
this._moveTreeRoot.getPojo(pojo);
return pojo;
}
/**
* Decode the [POJO](https://en.wikipedia.org/wiki/Plain_old_Java_object) passed in argument, assuming it follows the schema defined by {@link GamePOJO}.
*
* @throws {@link exception.InvalidPOJO} if the given object cannot be decoded, either because it does not follow the schema defined by {@link GamePOJO},
* or because it would result in an inconsistent game (e.g. if it contains some invalid moves).
*/
static fromPOJO(pojo) {
if (typeof pojo !== 'object' || pojo === null) {
throw new exception_1.InvalidPOJO(pojo, '', i18n_1.i18n.POJO_MUST_BE_AN_OBJECT);
}
const game = new Game();
const exceptionBuilder = new pojo_util_1.POJOExceptionBuilder(pojo);
function processPlayerPOJO(playerPOJO, color) {
(0, pojo_util_1.decodeStringField)(playerPOJO, 'name', exceptionBuilder, value => { game._playerName[color] = value; });
(0, pojo_util_1.decodeNumberField)(playerPOJO, 'elo', exceptionBuilder, value => {
if (!(0, common_1.isPositiveInteger)(value)) {
throw exceptionBuilder.build(i18n_1.i18n.INVALID_ELO_IN_POJO);
}
game._playerElo[color] = value;
});
(0, pojo_util_1.decodeStringField)(playerPOJO, 'title', exceptionBuilder, value => { game._playerTitle[color] = value; });
}
function processRoundPart(value, roundPart) {
if (!(0, common_1.isPositiveInteger)(value)) {
throw exceptionBuilder.build(i18n_1.i18n.INVALID_ROUND_IN_POJO);
}
game._round[roundPart] = value;
}
// Headers
(0, pojo_util_1.decodeObjectField)(pojo, 'white', exceptionBuilder, value => { processPlayerPOJO(value, 0 /* ColorImpl.WHITE */); });
(0, pojo_util_1.decodeObjectField)(pojo, 'black', exceptionBuilder, value => { processPlayerPOJO(value, 1 /* ColorImpl.BLACK */); });
(0, pojo_util_1.decodeStringField)(pojo, 'event', exceptionBuilder, value => { game._event = value; });
(0, pojo_util_1.decodeNumberField)(pojo, 'round', exceptionBuilder, value => { processRoundPart(value, 0 /* RoundPart.ROUND */); });
(0, pojo_util_1.decodeNumberField)(pojo, 'subRound', exceptionBuilder, value => { processRoundPart(value, 1 /* RoundPart.SUB_ROUND */); });
(0, pojo_util_1.decodeNumberField)(pojo, 'subSubRound', exceptionBuilder, value => { processRoundPart(value, 2 /* RoundPart.SUB_SUB_ROUND */); });
(0, pojo_util_1.decodeStringField)(pojo, 'date', exceptionBuilder, value => {
const date = date_value_1.DateValue.fromString(value);
if (date === undefined) {
throw exceptionBuilder.build(i18n_1.i18n.INVALID_DATE_IN_POJO);
}
game._date = date;
});
(0, pojo_util_1.decodeStringField)(pojo, 'site', exceptionBuilder, value => { game._site = value; });
(0, pojo_util_1.decodeStringField)(pojo, 'annotator', exceptionBuilder, value => { game._annotator = value; });
(0, pojo_util_1.decodeStringField)(pojo, 'eco', exceptionBuilder, value => {
if (!(0, helper_1.isValidECO)(value)) {
throw exceptionBuilder.build(i18n_1.i18n.INVALID_ECO_CODE_IN_POJO);
}
game._eco = value;
});
(0, pojo_util_1.decodeStringField)(pojo, 'opening', exceptionBuilder, value => { game._opening = value; });
(0, pojo_util_1.decodeStringField)(pojo, 'openingVariation', exceptionBuilder, value => { game._openingVariation = value; });
(0, pojo_util_1.decodeStringField)(pojo, 'openingSubVariation', exceptionBuilder, value => { game._openingSubVariation = value; });
(0, pojo_util_1.decodeStringField)(pojo, 'termination', exceptionBuilder, value => { game._termination = value; });
(0, pojo_util_1.decodeStringField)(pojo, 'result', exceptionBuilder, value => {
const resultCode = (0, base_types_impl_1.resultFromString)(value);
if (resultCode < 0) {
throw exceptionBuilder.build(i18n_1.i18n.INVALID_RESULT_IN_POJO);
}
game._result = resultCode;
});
// Moves
game._moveTreeRoot.setPojo(pojo, exceptionBuilder);
return game;
}
/**
* Return a human-readable string representing the game. This string is multi-line,
* and is intended to be displayed in a fixed-width font (similarly to an ASCII-art picture).
*/
ascii() {
const lines = [];
function pushIfDefined(header) {
if (header !== undefined) {
lines.push(header);
}
}
// Headers
pushIfDefined(formatEventAndRound(this._event, this._round[0 /* RoundPart.ROUND */], this._round[1 /* RoundPart.SUB_ROUND */], this._round[2 /* RoundPart.SUB_SUB_ROUND */]));
pushIfDefined(formatSimpleHeader('Site', this._site));
pushIfDefined(formatSimpleHeader('Date', this.dateAsString('en-us')));
pushIfDefined(formatPlayer('White', this._playerName[0 /* ColorImpl.WHITE */], this._playerElo[0 /* ColorImpl.WHITE */], this._playerTitle[0 /* ColorImpl.WHITE */]));
pushIfDefined(formatPlayer('Black', this._playerName[1 /* ColorImpl.BLACK */], this._playerElo[1 /* ColorImpl.BLACK */], this._playerTitle[1 /* ColorImpl.BLACK */]));
pushIfDefined(formatSimpleHeader('Annotator', this._annotator));
pushIfDefined(formatSimpleHeader('ECO', this._eco));
pushIfDefined(formatOpening(this._opening, this._openingVariation, this._openingSubVariation));
pushIfDefined(formatSimpleHeader('Termination', this._termination));
// Variant & initial position
const variant = this._moveTreeRoot._position.variant();
if (variant !== 'regular') {
lines.push('Variant: ' + variant);
}
if (!(0, helper_1.variantWithCanonicalStartPosition)(variant) || !position_1.Position.isEqual(this._moveTreeRoot._position, new position_1.Position(variant))) {
lines.push(this._moveTreeRoot._position.ascii());
}
// Moves & result
function isNonEmptyVariation(variation) {
return variation.first() !== undefined || variation.nags().length > 0 || variation.tags().length > 0 || variation.comment() !== undefined;
}
function dumpNode(node, indent, hasSomethingAfter) {
// Describe the move
const move = indent + node.fullMoveNumber() + (node.moveColor() === 'w' ? '.' : '...') + node.notation();
const moveAnnotations = formatAnnotations(node);
lines.push(moveAnnotations.length === 0 ? move : move + ' ' + moveAnnotations.join(' '));
// Print the sub-variations
let atLeastOneNonEmptyVariation = false;
for (const variation of node.variations()) {
if (isNonEmptyVariation(variation)) {
lines.push(indent + ' |');
dumpVariation(variation, indent + (hasSomethingAfter ? ' | ' : ' '), indent + ' +- ', false);
atLeastOneNonEmptyVariation = true;
}
}
return atLeastOneNonEmptyVariation;
}
function dumpVariation(variation, indent, indentFirst, hasSomethingAfter) {
// Variation annotations
const variationAnnotations = formatAnnotations(variation);
if (variationAnnotations.length > 0) {
lines.push(indentFirst + variationAnnotations.join(' '));
}
// List of moves
let node = variation.first();
let atLeastOneVariationInPreviousNode = false;
let isFirstNode = true;
while (node !== undefined) {
if (atLeastOneVariationInPreviousNode) {
lines.push(indent + ' |');
}
const nextNode = node.next();
atLeastOneVariationInPreviousNode = dumpNode(node, isFirstNode && variationAnnotations.length === 0 ? indentFirst : indent, hasSomethingAfter || nextNode !== undefined);
isFirstNode = false;
node = nextNode;
}
}
dumpVariation(this._moveTreeRoot.mainVariation(), '', '', false);
lines.push((0, base_types_impl_1.resultToString)(this._result));
return lines.join('\n');
}
}
exports.Game = Game;
function sanitizeStringHeader(value) {
return value === undefined || value === null ? undefined : String(value);
}
function sanitizeNumberHeader(value) {
return value === undefined || value === null ? undefined : Number(value);
}
function trimCollapseAndMarkEmpty(text) {
text = (0, common_1.trimAndCollapseSpaces)(text);
return text === '' ? '<empty>' : text;
}
function formatSimpleHeader(key, header) {
return header === undefined ? undefined : `${key}: ${trimCollapseAndMarkEmpty(header)}`;
}
function formatEventAndRound(event, round, subRound, subSubRound) {
if (event === undefined && round === undefined && subRound === undefined && subSubRound === undefined) {
return undefined;
}
let result = event === undefined ? 'Event: <undefined>' : `Event: ${trimCollapseAndMarkEmpty(event)}`;
const fullRound = formatFullRound(round, subRound, subSubRound, '*');
if (fullRound !== undefined) {
result += ` (${fullRound})`;
}
return result;
}
function formatFullRound(round, subRound, subSubRound, undefinedToken) {
if (round === undefined && subRound === undefined && subSubRound === undefined) {
return undefined;
}
let result = round === undefined ? undefinedToken : String(round);
if (subRound !== undefined || subSubRound !== undefined) {
result += '.' + (subRound ?? undefinedToken);
}
if (subSubRound !== undefined) {
result += '.' + subSubRound;
}
return result;
}
function formatPlayer(key, playerName, playerElo, playerTitle) {
if (playerName === undefined && playerElo === undefined && playerTitle === undefined) {
return undefined;
}
let result = playerName === undefined ? `${key}: <undefined>` : `${key}: ${trimCollapseAndMarkEmpty(playerName)}`;
if (playerElo !== undefined && playerTitle !== undefined) {
result += ` (${trimCollapseAndMarkEmpty(playerTitle)} ${playerElo})`;
}
else if (playerElo !== undefined) {
result += ` (${playerElo})`;
}
else if (playerTitle !== undefined) {
result += ` (${trimCollapseAndMarkEmpty(playerTitle)})`;
}
return result;
}
function formatOpening(opening, openingVariation, openingSubVariation) {
if (opening === undefined && openingVariation === undefined && openingSubVariation === undefined) {
return undefined;
}
let result = opening === undefined ? 'Opening: <undefined>' : `Opening: ${trimCollapseAndMarkEmpty(opening)}`;
if (openingSubVariation !== undefined) {
result += ` (${openingVariation === undefined ? '<undefined>' : trimCollapseAndMarkEmpty(openingVariation)}, ${trimCollapseAndMarkEmpty(openingSubVariation)})`;
}
else if (openingVariation !== undefined) {
result += ` (${trimCollapseAndMarkEmpty(openingVariation)})`;
}
return result;
}
function formatAnnotations(node) {
const result = [];
// NAGs
for (const nag of node.nags()) {
result.push((0, helper_1.nagSymbol)(nag));
}
// Tags
for (const tagKey of node.tags()) {
result.push(`${tagKey}={${(0, common_1.trimAndCollapseSpaces)(node.tag(tagKey))}}`);
}
// Comment
const comment = node.comment();
if (comment !== undefined) {
result.push(trimCollapseAndMarkEmpty(comment));
}
return result;
}
//# sourceMappingURL=game.js.map