kokopu
Version:
A JavaScript/TypeScript library implementing the chess game rules and providing tools to read/write the standard chess file formats.
940 lines • 35.7 kB
JavaScript
"use strict";
/*!
* -------------------------------------------------------------------------- *
* *
* Kokopu - A JavaScript/TypeScript chess library. *
* <https://www.npmjs.com/package/kokopu> *
* Copyright (C) 2018-2025 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.MoveTreeRoot = void 0;
const exception_1 = require("../exception");
const helper_1 = require("../helper");
const i18n_1 = require("../i18n");
const node_variation_1 = require("../node_variation");
const position_1 = require("../position");
const common_1 = require("./common");
const pojo_util_1 = require("./pojo_util");
const base_types_impl_1 = require("../private_position/base_types_impl");
/**
* Container for the data at the root of the `NodeData` / `VariationData` tree.
*/
class MoveTreeRoot {
constructor() {
this._position = new position_1.Position();
this._fullMoveNumber = 1;
this._mainVariationData = createVariationData(this, true);
}
clearTree() {
this._mainVariationData = createVariationData(this, true);
}
mainVariation() {
return new VariationImpl(this._mainVariationData, this._position);
}
findById(id, allowAliases) {
const tokens = id.split('-');
if (tokens.length % 2 !== 1) {
return undefined;
}
const position = new position_1.Position(this._position);
// Find the parent variation of the target node.
let variationData = this._mainVariationData;
for (let i = 0; i + 1 < tokens.length; i += 2) {
const nodeData = findNode(variationData, tokens[i], position);
if (nodeData === undefined) {
return undefined;
}
const match = /^v(\d+)$/.exec(tokens[i + 1]);
if (!match) {
return undefined;
}
const variationIndex = parseInt(match[1]);
if (variationIndex >= nodeData.variations.length) {
return undefined;
}
variationData = nodeData.variations[variationIndex];
}
// Find the target node within its parent variation, or return the variation itself
// if the ID is a variation ID (i.e. if it ends with 'start').
const lastToken = tokens[tokens.length - 1];
if (lastToken === 'start') {
return new VariationImpl(variationData, position);
}
else if (allowAliases && lastToken === 'end') {
if (variationData.child === undefined) {
return new VariationImpl(variationData, position);
}
else {
let nodeData = variationData.child;
while (nodeData.child !== undefined) {
applyMoveDescriptor(position, nodeData);
nodeData = nodeData.child;
}
return new NodeImpl(nodeData, position);
}
}
else {
const nodeData = findNode(variationData, lastToken, position);
return nodeData === undefined ? undefined : new NodeImpl(nodeData, position);
}
}
getPojo(pojo) {
// Encode the game variant and initial position, if necessary.
const variant = this._position.variant();
if (variant !== 'regular') {
pojo.variant = variant;
}
const isCanonicalStartPosition = (0, helper_1.variantWithCanonicalStartPosition)(variant) && position_1.Position.isEqual(this._position, new position_1.Position(variant)) && this._fullMoveNumber === 1;
if (!isCanonicalStartPosition) {
pojo.initialPosition = this._position.fen({ fullMoveNumber: this._fullMoveNumber });
}
// Encode the moves.
const mainVariationPOJO = getVariationPOJO(new position_1.Position(this._position), this._mainVariationData, true);
if (!Array.isArray(mainVariationPOJO) || mainVariationPOJO.length > 0) {
pojo.mainVariation = mainVariationPOJO;
}
}
setPojo(pojo, exceptionBuilder) {
// Decode the game variant and initial position, if any.
let variant = 'regular';
let initialPositionDefined = false;
(0, pojo_util_1.decodeStringField)(pojo, 'variant', exceptionBuilder, value => {
if ((0, base_types_impl_1.variantFromString)(value) < 0) {
throw exceptionBuilder.build(i18n_1.i18n.INVALID_VARIANT_IN_POJO);
}
variant = value;
});
(0, pojo_util_1.decodeStringField)(pojo, 'initialPosition', exceptionBuilder, value => {
this._position = new position_1.Position(variant, 'empty');
try {
const { fullMoveNumber } = this._position.fen(value);
this._fullMoveNumber = fullMoveNumber;
initialPositionDefined = true;
}
catch (error) {
// istanbul ignore else
if (error instanceof exception_1.InvalidFEN) {
throw exceptionBuilder.build(i18n_1.i18n.INVALID_FEN_IN_POJO, error.message);
}
else {
throw error;
}
}
});
if (variant !== 'regular' && !initialPositionDefined) {
if (!(0, helper_1.variantWithCanonicalStartPosition)(variant)) {
exceptionBuilder.push('initialPosition'); // no-pop
throw exceptionBuilder.build(i18n_1.i18n.MISSING_INITIAL_POSITION_IN_POJO, variant);
}
this._position = new position_1.Position(variant);
this._fullMoveNumber = 1;
}
// Decode the moves.
if ('mainVariation' in pojo && pojo.mainVariation !== undefined) {
exceptionBuilder.push('mainVariation');
this._mainVariationData = setVariationPOJO(pojo.mainVariation, this, this._position, 0, this._fullMoveNumber, true, exceptionBuilder);
exceptionBuilder.pop();
}
else {
this.clearTree();
}
}
}
exports.MoveTreeRoot = MoveTreeRoot;
function findNode(variationData, nodeIdToken, position) {
let nodeData = variationData.child;
while (nodeData !== undefined) {
if (nodeIdToken === nodeData.fullMoveNumber + nodeData.moveColor) {
return nodeData;
}
applyMoveDescriptor(position, nodeData);
nodeData = nodeData.child;
}
return undefined;
}
function getVariationPOJO(position, variationData, isLongVariationByDefault) {
const nodePOJOs = [];
let nodeData = variationData.child;
while (nodeData !== undefined) {
nodePOJOs.push(getNodePOJO(position, nodeData));
applyMoveDescriptor(position, nodeData);
nodeData = nodeData.child;
}
const pojo = {
nodes: nodePOJOs,
};
let pojoIsTrivial = true;
if (variationData.isLongVariation !== isLongVariationByDefault) {
pojo.isLongVariation = variationData.isLongVariation;
pojoIsTrivial = false;
}
if (appendAnnotationFields(pojo, variationData)) {
pojoIsTrivial = false;
}
return pojoIsTrivial ? pojo.nodes : pojo;
}
function getNodePOJO(position, nodeData) {
const pojo = {
notation: nodeData.moveDescriptor === null ? '--' : position.notation(nodeData.moveDescriptor),
};
let pojoIsTrivial = true;
if (nodeData.variations.length > 0) {
pojo.variations = nodeData.variations.map(variation => getVariationPOJO(new position_1.Position(position), variation, false));
pojoIsTrivial = false;
}
if (appendAnnotationFields(pojo, nodeData)) {
pojoIsTrivial = false;
}
return pojoIsTrivial ? pojo.notation : pojo;
}
function appendAnnotationFields(pojo, data) {
let atLeastOneAnnotation = false;
if (data.comment !== undefined) {
pojo.comment = data.comment;
if (data.isLongComment) {
pojo.isLongComment = true;
}
atLeastOneAnnotation = true;
}
if (data.nags.size > 0) {
pojo.nags = getNags(data);
atLeastOneAnnotation = true;
}
if (data.tags.size > 0) {
pojo.tags = getTagRecords(data);
atLeastOneAnnotation = true;
}
return atLeastOneAnnotation;
}
function setVariationPOJO(variationPOJO, parent, position, fiftyMoveClock, fullMoveNumber, isLongVariationByDefault, exceptionBuilder) {
let result;
let nodes;
// Initialize the VariationData object.
if (Array.isArray(variationPOJO)) {
result = createVariationData(parent, isLongVariationByDefault);
nodes = variationPOJO;
}
else if (typeof variationPOJO === 'object' && variationPOJO !== null) {
// Decode everything but the nodes.
let longVariation = isLongVariationByDefault;
(0, pojo_util_1.decodeBooleanField)(variationPOJO, 'isLongVariation', exceptionBuilder, value => { longVariation = value; });
result = createVariationData(parent, longVariation);
decodeAnnotationFields(variationPOJO, result, exceptionBuilder);
// Decode the node array.
exceptionBuilder.push('nodes');
if (!('nodes' in variationPOJO) || !Array.isArray(variationPOJO.nodes)) {
throw exceptionBuilder.build(i18n_1.i18n.INVALID_OR_MISSING_NODE_ARRAY);
}
nodes = variationPOJO.nodes;
}
else {
throw exceptionBuilder.build(i18n_1.i18n.NOT_A_VARIATION_POJO);
}
// Decode the nodes.
let insertionPoint = result;
position = new position_1.Position(position);
for (let i = 0; i < nodes.length; ++i) {
exceptionBuilder.push(i);
insertionPoint.child = setNodePOJO(nodes[i], result, position, fiftyMoveClock, fullMoveNumber, exceptionBuilder);
exceptionBuilder.pop();
insertionPoint = insertionPoint.child;
applyMoveDescriptor(position, insertionPoint);
fiftyMoveClock = computeNextFiftyMoveClock(insertionPoint);
if (position.turn() === 'w') {
++fullMoveNumber;
}
}
// Un-push the "nodes" path component if necessary.
if (!Array.isArray(variationPOJO)) {
exceptionBuilder.pop();
}
return result;
}
function setNodePOJO(nodePOJO, parentVariation, position, fiftyMoveClock, fullMoveNumber, exceptionBuilder) {
// Decode the notation.
let moveDescriptor;
if (typeof nodePOJO === 'string') {
moveDescriptor = decodeMoveDescriptorField(position, nodePOJO, exceptionBuilder);
}
else if (typeof nodePOJO === 'object' && nodePOJO !== null) {
exceptionBuilder.push('notation');
if (!('notation' in nodePOJO) || typeof nodePOJO.notation !== 'string') {
throw exceptionBuilder.build(i18n_1.i18n.INVALID_OR_MISSING_NOTATION_FIELD);
}
moveDescriptor = decodeMoveDescriptorField(position, nodePOJO.notation, exceptionBuilder);
exceptionBuilder.pop();
}
else {
throw exceptionBuilder.build(i18n_1.i18n.NOT_A_NODE_POJO);
}
const result = createNodeData(parentVariation, position.turn(), fiftyMoveClock, fullMoveNumber, moveDescriptor);
if (typeof nodePOJO === 'object' && nodePOJO !== null) {
// Decode the annotations.
decodeAnnotationFields(nodePOJO, result, exceptionBuilder);
// Decode the variations.
(0, pojo_util_1.decodeArrayField)(nodePOJO, 'variations', exceptionBuilder, value => {
for (let i = 0; i < value.length; ++i) {
exceptionBuilder.push(i);
result.variations.push(setVariationPOJO(value[i], result, position, fiftyMoveClock, fullMoveNumber, false, exceptionBuilder));
exceptionBuilder.pop();
}
});
}
return result;
}
function decodeMoveDescriptorField(position, move, exceptionBuilder) {
try {
return computeMoveDescriptor(position, move);
}
catch (error) {
// istanbul ignore else
if (error instanceof exception_1.InvalidNotation) {
throw exceptionBuilder.build(i18n_1.i18n.INVALID_MOVE_IN_POJO, move, error.message);
}
else {
throw error;
}
}
}
function decodeAnnotationFields(abstractNodePOJO, data, exceptionBuilder) {
(0, pojo_util_1.decodeStringField)(abstractNodePOJO, 'comment', exceptionBuilder, value => { data.comment = value; });
(0, pojo_util_1.decodeBooleanField)(abstractNodePOJO, 'isLongComment', exceptionBuilder, value => { data.isLongComment = value; });
(0, pojo_util_1.decodeArrayField)(abstractNodePOJO, 'nags', exceptionBuilder, value => {
for (let i = 0; i < value.length; ++i) {
const nag = value[i];
if (nag === undefined) {
continue;
}
else if (!(0, common_1.isValidNag)(nag)) {
exceptionBuilder.push(i); // no-pop
throw exceptionBuilder.build(i18n_1.i18n.INVALID_NAG_IN_POJO, nag);
}
data.nags.add(nag);
}
});
(0, pojo_util_1.decodeObjectField)(abstractNodePOJO, 'tags', exceptionBuilder, value => {
for (const tagKey in value) {
if (!isValidTagKey(tagKey)) {
exceptionBuilder.push(`[${tagKey}]`); // no-pop
throw exceptionBuilder.build(i18n_1.i18n.INVALID_TAG_IN_POJO, tagKey);
}
const tagValue = value[tagKey];
if (tagValue === undefined) {
continue;
}
else if (typeof tagValue !== 'string') {
exceptionBuilder.push(tagKey); // no-pop
throw exceptionBuilder.build(i18n_1.i18n.INVALID_TAG_IN_POJO, tagKey);
}
data.tags.set(tagKey, tagValue);
}
});
}
function createNodeData(parentVariation, moveColor, fiftyMoveClock, fullMoveNumber, moveDescriptor) {
return {
parentVariation: parentVariation,
child: undefined,
variations: [],
moveColor: moveColor,
fiftyMoveClock: fiftyMoveClock,
fullMoveNumber: fullMoveNumber,
moveDescriptor: moveDescriptor,
nags: new Set(),
tags: new Map(),
comment: undefined,
isLongComment: false,
};
}
function createVariationData(parent, longVariation) {
return {
parent: parent,
child: undefined,
isLongVariation: longVariation,
nags: new Set(),
tags: new Map(),
comment: undefined,
isLongComment: false,
};
}
/**
* Compute the move descriptor associated to the given SAN notation, assuming the given position.
*/
function computeMoveDescriptor(position, move) {
if (move === '--') {
if (!position.isNullMoveLegal()) {
throw new exception_1.InvalidNotation(position.fen(), '--', i18n_1.i18n.ILLEGAL_NULL_MOVE);
}
return null;
}
else {
return position.notation(move);
}
}
/**
* Play the move descriptor encoded in the given node data structure, or play null-move if no move descriptor is defined.
*/
function applyMoveDescriptor(position, nodeData) {
if (nodeData.moveDescriptor === null) {
position.playNullMove();
}
else {
position.play(nodeData.moveDescriptor);
}
}
/**
* Compute the new value of the fifty-move clock after the move described by the given node data structure.
*/
function computeNextFiftyMoveClock(nodeData) {
if (nodeData.moveDescriptor === null) {
return nodeData.fiftyMoveClock;
}
else if (nodeData.moveDescriptor.isCapture() || nodeData.moveDescriptor.movingPiece() === 'p') {
return 0;
}
else {
return nodeData.fiftyMoveClock + 1;
}
}
/**
* Compute the new value of the full-move number after the move described by the given node data structure.
*/
function computeNextFullMoveNumber(nodeData) {
return nodeData.moveColor === 'w' ? nodeData.fullMoveNumber : nodeData.fullMoveNumber + 1;
}
/**
* Return the initial position of the given variation.
*/
function rebuildVariationPosition(variationData) {
if (variationData.parent instanceof MoveTreeRoot) {
return new position_1.Position(variationData.parent._position);
}
else {
let current = variationData.parent.parentVariation.child;
const position = rebuildVariationPosition(variationData.parent.parentVariation);
while (current !== variationData.parent) {
applyMoveDescriptor(position, current);
current = current.child;
}
return position;
}
}
/**
* Compute the ID of the given node.
*/
function buildNodeId(nodeData) {
return buildVariationIdPrefix(nodeData.parentVariation) + nodeData.fullMoveNumber + nodeData.moveColor;
}
/**
* Compute the ID of the given variation, without the final `'start'` token.
*/
function buildVariationIdPrefix(variationData) {
if (variationData.parent instanceof MoveTreeRoot) {
return '';
}
else {
const parentNodeId = buildNodeId(variationData.parent);
const variationIndex = variationData.parent.variations.indexOf(variationData);
return `${parentNodeId}-v${variationIndex}-`;
}
}
/**
* Whether the variation corresponding to the given descriptor is a "long variation",
* i.e. whether it is a flagged as "isLongVariation" AND SO ARE ALL IT'S PARENTS.
*/
function isLongVariation(variationData) {
while (true) {
if (!variationData.isLongVariation) {
return false;
}
if (variationData.parent instanceof MoveTreeRoot) {
return true;
}
variationData = variationData.parent.parentVariation;
}
}
/**
* Whether the given number is a valid variation index for the given node.
*/
function isValidVariationIndex(variationIndex, nodeData) {
return Number.isInteger(variationIndex) && variationIndex >= 0 && variationIndex < nodeData.variations.length;
}
/**
* Return all the NAGs of the given abstract node.
*/
function getNags(data) {
const result = [];
for (const nag of data.nags) {
result.push(nag);
}
return result.sort((a, b) => a - b);
}
/**
* Keep only the NAGs that are asserted by the given filter.
*/
function filterNags(data, filter) {
const result = new Set();
for (const nag of data.nags) {
if (filter(nag)) {
result.add(nag);
}
}
data.nags = result;
}
/**
* Whether the given valid is a valid tag key or not.
*/
function isValidTagKey(tagKey) {
return typeof tagKey === 'string' && /^\w+$/.test(tagKey);
}
/**
* Return all the tag keys of the given abstract node.
*/
function getTagKeys(data) {
const result = [];
for (const tag of data.tags.keys()) {
result.push(tag);
}
return result.sort();
}
/**
* Keep only the tags that are asserted by the given filter.
*/
function filterTags(data, filter) {
const result = new Map();
for (const [tagKey, tagValue] of data.tags.entries()) {
if (filter(tagKey, tagValue)) {
result.set(tagKey, tagValue);
}
}
data.tags = result;
}
/**
* Return all the pairs tag key + tag value of the given abstract node.
*/
function getTagRecords(data) {
const result = {};
for (const [tagKey, tagValue] of data.tags.entries()) {
result[tagKey] = tagValue;
}
return result;
}
/**
* Implementation class for `Node`.
*/
class NodeImpl extends node_variation_1.Node {
constructor(data, positionBefore) {
super();
this._data = data;
this._positionBefore = positionBefore;
}
id() {
return buildNodeId(this._data);
}
nags() {
return getNags(this._data);
}
hasNag(nag) {
if (!(0, common_1.isValidNag)(nag)) {
throw new exception_1.IllegalArgument('Node.hasNag()');
}
return this._data.nags.has(nag);
}
addNag(nag) {
if (!(0, common_1.isValidNag)(nag)) {
throw new exception_1.IllegalArgument('Node.addNag()');
}
return this._data.nags.add(nag);
}
removeNag(nag) {
if (!(0, common_1.isValidNag)(nag)) {
throw new exception_1.IllegalArgument('Node.removeNag()');
}
return this._data.nags.delete(nag);
}
clearNags() {
this._data.nags.clear();
}
filterNags(filter) {
filterNags(this._data, filter);
}
tags() {
return getTagKeys(this._data);
}
tag(tagKey, value) {
if (!isValidTagKey(tagKey)) {
throw new exception_1.IllegalArgument('Node.tag()');
}
if (arguments.length === 1) {
return this._data.tags.get(tagKey);
}
else {
if (value === undefined || value === null) {
this._data.tags.delete(tagKey);
}
else {
this._data.tags.set(tagKey, String(value));
}
}
}
clearTags() {
this._data.tags.clear();
}
filterTags(filter) {
filterTags(this._data, filter);
}
comment(value, isLongComment) {
if (arguments.length === 0) {
return this._data.comment;
}
else if (value === undefined || value === null) {
this._data.comment = undefined;
this._data.isLongComment = false;
}
else {
this._data.comment = String(value);
this._data.isLongComment = Boolean(isLongComment);
}
}
isLongComment() {
return this._data.isLongComment && isLongVariation(this._data.parentVariation);
}
parentVariation() {
const initialPosition = rebuildVariationPosition(this._data.parentVariation);
return new VariationImpl(this._data.parentVariation, initialPosition);
}
previous() {
let current = this._data.parentVariation.child;
if (current === this._data) {
return undefined;
}
const position = rebuildVariationPosition(this._data.parentVariation);
while (current.child !== this._data) {
applyMoveDescriptor(position, current);
current = current.child;
}
return new NodeImpl(current, position);
}
next() {
if (this._data.child === undefined) {
return undefined;
}
const nextPositionBefore = new position_1.Position(this._positionBefore);
applyMoveDescriptor(nextPositionBefore, this._data);
return new NodeImpl(this._data.child, nextPositionBefore);
}
notation() {
return this._data.moveDescriptor === null ? '--' : this._positionBefore.notation(this._data.moveDescriptor);
}
figurineNotation() {
return this._data.moveDescriptor === null ? '--' : this._positionBefore.figurineNotation(this._data.moveDescriptor);
}
positionBefore() {
return new position_1.Position(this._positionBefore);
}
position() {
const position = new position_1.Position(this._positionBefore);
applyMoveDescriptor(position, this._data);
return position;
}
fen() {
return this.position().fen({
fiftyMoveClock: computeNextFiftyMoveClock(this._data),
fullMoveNumber: computeNextFullMoveNumber(this._data),
});
}
fiftyMoveClock() {
return computeNextFiftyMoveClock(this._data);
}
fullMoveNumber() {
return this._data.fullMoveNumber;
}
moveColor() {
return this._data.moveColor;
}
variations() {
return this._data.variations.map(variation => new VariationImpl(variation, this._positionBefore));
}
play(move) {
const nextPositionBefore = new position_1.Position(this._positionBefore);
applyMoveDescriptor(nextPositionBefore, this._data);
const nextMoveColor = nextPositionBefore.turn();
const nextFiftyMoveClock = computeNextFiftyMoveClock(this._data);
const nextFullMoveNumber = computeNextFullMoveNumber(this._data);
this._data.child = createNodeData(this._data.parentVariation, nextMoveColor, nextFiftyMoveClock, nextFullMoveNumber, computeMoveDescriptor(nextPositionBefore, move));
return new NodeImpl(this._data.child, nextPositionBefore);
}
removePrecedingMoves() {
const moveTreeRoot = findRoot(this._data);
// Reset the initial position and full-move number, and rebuild a new main variation (so that the annotations get cleared).
moveTreeRoot._position = this._positionBefore;
moveTreeRoot._fullMoveNumber = this._data.fullMoveNumber;
moveTreeRoot._mainVariationData = createVariationData(moveTreeRoot, true);
// Replug the nodes.
moveTreeRoot._mainVariationData.child = this._data;
resetParentVariationRecursively(this._data, moveTreeRoot._mainVariationData);
resetFiftyMoveClockRecursively(this._data, 0);
}
removeFollowingMoves() {
this._data.child = undefined;
}
addVariation(longVariation) {
const result = createVariationData(this._data, longVariation ?? false);
this._data.variations.push(result);
return new VariationImpl(result, this._positionBefore);
}
removeVariation(variationIndex) {
if (!isValidVariationIndex(variationIndex, this._data)) {
throw new exception_1.IllegalArgument('Node.removeVariation()');
}
this._data.variations = this._data.variations.slice(0, variationIndex).concat(this._data.variations.slice(variationIndex + 1));
}
swapVariations(variationIndex1, variationIndex2) {
if (!isValidVariationIndex(variationIndex1, this._data) || !isValidVariationIndex(variationIndex2, this._data)) {
throw new exception_1.IllegalArgument('Node.swapVariations()');
}
const variationTmp = this._data.variations[variationIndex1];
this._data.variations[variationIndex1] = this._data.variations[variationIndex2];
this._data.variations[variationIndex2] = variationTmp;
}
promoteVariation(variationIndex) {
if (!isValidVariationIndex(variationIndex, this._data) || this._data.variations[variationIndex].child === undefined) {
throw new exception_1.IllegalArgument('Node.promoteVariation()');
}
const oldMainLine = this._data;
const newMainLine = this._data.variations[variationIndex].child;
// Detach the array containing the variations from the current node.
const variations = oldMainLine.variations;
oldMainLine.variations = [];
// Create a new variation with the old main line.
variations[variationIndex] = createVariationData(newMainLine, false);
variations[variationIndex].child = oldMainLine;
// Create a new main line with the promoted variation, and re-attach the variations.
this._data = newMainLine;
newMainLine.variations = variations.concat(newMainLine.variations);
// Re-map the parents.
findParent(oldMainLine).child = newMainLine;
resetParentVariationRecursively(newMainLine, oldMainLine.parentVariation);
resetParentVariationRecursively(oldMainLine, variations[variationIndex]);
for (const variation of newMainLine.variations) {
variation.parent = newMainLine;
}
}
}
function findRoot(node) {
let candidate = node;
while (!(candidate instanceof MoveTreeRoot)) {
candidate = candidate.parentVariation.parent;
}
return candidate;
}
function findParent(oldMainLine) {
let candidate = oldMainLine.parentVariation;
while (candidate.child !== oldMainLine) {
candidate = candidate.child;
}
return candidate;
}
function resetParentVariationRecursively(root, newParentVariation) {
let current = root;
while (current !== undefined) {
current.parentVariation = newParentVariation;
current = current.child;
}
}
function resetFiftyMoveClockRecursively(root, fiftyMoveClock) {
let current = root;
while (current !== undefined) {
// Update the current node.
current.fiftyMoveClock = fiftyMoveClock;
// Update its variations.
for (const variation of current.variations) {
resetFiftyMoveClockRecursively(variation.child, fiftyMoveClock);
}
fiftyMoveClock = computeNextFiftyMoveClock(current);
current = current.child;
}
}
/**
* Implementation class for `Variation`.
*/
class VariationImpl extends node_variation_1.Variation {
constructor(data, initialPosition) {
super();
this._data = data;
this._initialPosition = initialPosition;
}
id() {
return buildVariationIdPrefix(this._data) + 'start';
}
nags() {
return getNags(this._data);
}
hasNag(nag) {
if (!(0, common_1.isValidNag)(nag)) {
throw new exception_1.IllegalArgument('Variation.hasNag()');
}
return this._data.nags.has(nag);
}
addNag(nag) {
if (!(0, common_1.isValidNag)(nag)) {
throw new exception_1.IllegalArgument('Variation.addNag()');
}
return this._data.nags.add(nag);
}
removeNag(nag) {
if (!(0, common_1.isValidNag)(nag)) {
throw new exception_1.IllegalArgument('Variation.removeNag()');
}
return this._data.nags.delete(nag);
}
clearNags() {
this._data.nags.clear();
}
filterNags(filter) {
filterNags(this._data, filter);
}
tags() {
return getTagKeys(this._data);
}
tag(tagKey, value) {
if (!isValidTagKey(tagKey)) {
throw new exception_1.IllegalArgument('Variation.tag()');
}
if (arguments.length === 1) {
return this._data.tags.get(tagKey);
}
else {
if (value === undefined || value === null) {
this._data.tags.delete(tagKey);
}
else {
this._data.tags.set(tagKey, String(value));
}
}
}
clearTags() {
this._data.tags.clear();
}
filterTags(filter) {
filterTags(this._data, filter);
}
comment(value, isLongComment) {
if (arguments.length === 0) {
return this._data.comment;
}
else if (value === undefined || value === null) {
this._data.comment = undefined;
this._data.isLongComment = false;
}
else {
this._data.comment = String(value);
this._data.isLongComment = Boolean(isLongComment);
}
}
isLongComment() {
return this._data.isLongComment && isLongVariation(this._data);
}
parentNode() {
return this._data.parent instanceof MoveTreeRoot ? undefined : new NodeImpl(this._data.parent, this._initialPosition);
}
first() {
return this._data.child === undefined ? undefined : new NodeImpl(this._data.child, this._initialPosition);
}
nodes() {
const result = [];
let currentNodeData = this._data.child;
let previousNodeData = undefined;
let previousPositionBefore = this._initialPosition;
while (currentNodeData !== undefined) {
// Compute the "position-before" attribute the current node.
previousPositionBefore = new position_1.Position(previousPositionBefore);
if (previousNodeData !== undefined) {
applyMoveDescriptor(previousPositionBefore, previousNodeData);
}
// Push the current node.
result.push(new NodeImpl(currentNodeData, previousPositionBefore));
// Increment the counters.
previousNodeData = currentNodeData;
currentNodeData = currentNodeData.child;
}
return result;
}
plyCount() {
let result = 0;
let currentNodeData = this._data.child;
while (currentNodeData !== undefined) {
++result;
currentNodeData = currentNodeData.child;
}
return result;
}
isLongVariation() {
return isLongVariation(this._data);
}
initialPosition() {
return new position_1.Position(this._initialPosition);
}
initialFEN() {
return this._initialPosition.fen({
fiftyMoveClock: this.initialFiftyMoveClock(),
fullMoveNumber: this.initialFullMoveNumber(),
});
}
initialFiftyMoveClock() {
return this._data.parent instanceof MoveTreeRoot ? 0 : this._data.parent.fiftyMoveClock;
}
initialFullMoveNumber() {
return this._data.parent instanceof MoveTreeRoot ? this._data.parent._fullMoveNumber : this._data.parent.fullMoveNumber;
}
finalPosition() {
const result = new position_1.Position(this._initialPosition);
for (let nodeData = this._data.child; nodeData !== undefined; nodeData = nodeData.child) {
applyMoveDescriptor(result, nodeData);
}
return result;
}
finalFEN() {
if (this._data.child === undefined) {
return this.initialFEN();
}
const position = new position_1.Position(this._initialPosition);
let nodeData = this._data.child;
while (true) {
applyMoveDescriptor(position, nodeData);
if (nodeData.child === undefined) {
break;
}
nodeData = nodeData.child;
}
return position.fen({
fiftyMoveClock: computeNextFiftyMoveClock(nodeData),
fullMoveNumber: computeNextFullMoveNumber(nodeData),
});
}
play(move) {
const moveColor = this._initialPosition.turn();
const fiftyMoveClock = this.initialFiftyMoveClock();
const fullMoveNumber = this.initialFullMoveNumber();
this._data.child = createNodeData(this._data, moveColor, fiftyMoveClock, fullMoveNumber, computeMoveDescriptor(this._initialPosition, move));
return new NodeImpl(this._data.child, this._initialPosition);
}
clearMoves() {
this._data.child = undefined;
}
}
//# sourceMappingURL=node_variation_impl.js.map