tsshogi
Version:
TypeScript library for Shogi (Japanese chess)
1,260 lines • 92.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Record = exports.RecordMetadata = exports.RecordMetadataKey = void 0;
exports.getBlackPlayerName = getBlackPlayerName;
exports.getWhitePlayerName = getWhitePlayerName;
exports.getBlackPlayerNamePreferShort = getBlackPlayerNamePreferShort;
exports.getWhitePlayerNamePreferShort = getWhitePlayerNamePreferShort;
exports.getNextColorFromUSI = getNextColorFromUSI;
const time_1 = require("./helpers/time.cjs");
const color_1 = require("./color.cjs");
const errors_1 = require("./errors.cjs");
const move_1 = require("./move.cjs");
const position_1 = require("./position.cjs");
const text_1 = require("./text.cjs");
const piece_1 = require("./piece.cjs");
const square_1 = require("./square.cjs");
const usenHandTable = {
[piece_1.PieceType.PAWN]: 81 + 10,
[piece_1.PieceType.LANCE]: 81 + 11,
[piece_1.PieceType.KNIGHT]: 81 + 12,
[piece_1.PieceType.SILVER]: 81 + 13,
[piece_1.PieceType.GOLD]: 81 + 9,
[piece_1.PieceType.BISHOP]: 81 + 14,
[piece_1.PieceType.ROOK]: 81 + 15,
[piece_1.PieceType.KING]: 81 + 8,
[piece_1.PieceType.PROM_PAWN]: 81 + 2,
[piece_1.PieceType.PROM_LANCE]: 81 + 3,
[piece_1.PieceType.PROM_KNIGHT]: 81 + 4,
[piece_1.PieceType.PROM_SILVER]: 81 + 5,
[piece_1.PieceType.HORSE]: 81 + 6,
[piece_1.PieceType.DRAGON]: 81 + 7,
};
const usenHandReverseTable = {
[81 + 10]: piece_1.PieceType.PAWN,
[81 + 11]: piece_1.PieceType.LANCE,
[81 + 12]: piece_1.PieceType.KNIGHT,
[81 + 13]: piece_1.PieceType.SILVER,
[81 + 9]: piece_1.PieceType.GOLD,
[81 + 14]: piece_1.PieceType.BISHOP,
[81 + 15]: piece_1.PieceType.ROOK,
[81 + 8]: piece_1.PieceType.KING,
[81 + 2]: piece_1.PieceType.PROM_PAWN,
[81 + 3]: piece_1.PieceType.PROM_LANCE,
[81 + 4]: piece_1.PieceType.PROM_KNIGHT,
[81 + 5]: piece_1.PieceType.PROM_SILVER,
[81 + 6]: piece_1.PieceType.HORSE,
[81 + 7]: piece_1.PieceType.DRAGON,
};
var RecordMetadataKey;
(function (RecordMetadataKey) {
RecordMetadataKey["TITLE"] = "title";
RecordMetadataKey["BLACK_NAME"] = "blackName";
RecordMetadataKey["WHITE_NAME"] = "whiteName";
RecordMetadataKey["SHITATE_NAME"] = "shitateName";
RecordMetadataKey["UWATE_NAME"] = "uwateName";
RecordMetadataKey["BLACK_SHORT_NAME"] = "blackShortName";
RecordMetadataKey["WHITE_SHORT_NAME"] = "whiteShortName";
RecordMetadataKey["START_DATETIME"] = "startDatetime";
RecordMetadataKey["END_DATETIME"] = "endDatetime";
RecordMetadataKey["DATE"] = "date";
RecordMetadataKey["TOURNAMENT"] = "tournament";
RecordMetadataKey["STRATEGY"] = "strategy";
RecordMetadataKey["TIME_LIMIT"] = "timeLimit";
RecordMetadataKey["BLACK_TIME_LIMIT"] = "blackTimeLimit";
RecordMetadataKey["WHITE_TIME_LIMIT"] = "whiteTimeLimit";
RecordMetadataKey["BYOYOMI"] = "byoyomi";
RecordMetadataKey["TIME_SPENT"] = "timeSpent";
RecordMetadataKey["MAX_MOVES"] = "maxMoves";
RecordMetadataKey["JISHOGI"] = "jishogi";
RecordMetadataKey["PLACE"] = "place";
RecordMetadataKey["POSTED_ON"] = "postedOn";
RecordMetadataKey["NOTE"] = "note";
RecordMetadataKey["SCOREKEEPER"] = "scorekeeper";
// 詰将棋に関する項目
RecordMetadataKey["OPUS_NO"] = "opusNo";
RecordMetadataKey["OPUS_NAME"] = "opusName";
RecordMetadataKey["AUTHOR"] = "author";
RecordMetadataKey["PUBLISHED_BY"] = "publishedBy";
RecordMetadataKey["PUBLISHED_AT"] = "publishedAt";
RecordMetadataKey["SOURCE"] = "source";
RecordMetadataKey["LENGTH"] = "length";
RecordMetadataKey["INTEGRITY"] = "integrity";
RecordMetadataKey["CATEGORY"] = "category";
RecordMetadataKey["AWARD"] = "award";
})(RecordMetadataKey || (exports.RecordMetadataKey = RecordMetadataKey = {}));
/**
* 先手の対局者名をフルネーム優先で取得します。
* @param metadata
*/
function getBlackPlayerName(metadata) {
return (metadata.getStandardMetadata(RecordMetadataKey.BLACK_NAME) ||
metadata.getStandardMetadata(RecordMetadataKey.BLACK_SHORT_NAME) ||
metadata.getStandardMetadata(RecordMetadataKey.SHITATE_NAME));
}
/**
* 後手の対局者名をフルネーム優先で取得します。
* @param metadata
*/
function getWhitePlayerName(metadata) {
return (metadata.getStandardMetadata(RecordMetadataKey.WHITE_NAME) ||
metadata.getStandardMetadata(RecordMetadataKey.WHITE_SHORT_NAME) ||
metadata.getStandardMetadata(RecordMetadataKey.UWATE_NAME));
}
/**
* 先手の対局者名を省略名優先で取得します。
* @param metadata
*/
function getBlackPlayerNamePreferShort(metadata) {
return (metadata.getStandardMetadata(RecordMetadataKey.BLACK_SHORT_NAME) ||
metadata.getStandardMetadata(RecordMetadataKey.BLACK_NAME) ||
metadata.getStandardMetadata(RecordMetadataKey.SHITATE_NAME));
}
/**
* 後手の対局者名を省略名優先で取得します。
* @param metadata
*/
function getWhitePlayerNamePreferShort(metadata) {
return (metadata.getStandardMetadata(RecordMetadataKey.WHITE_SHORT_NAME) ||
metadata.getStandardMetadata(RecordMetadataKey.WHITE_NAME) ||
metadata.getStandardMetadata(RecordMetadataKey.UWATE_NAME));
}
/**
* 棋譜メタデータ
*/
class RecordMetadata {
standard = new Map();
custom = new Map();
/**
* 定義済みのメタデータのキーの一覧を取得します。
*/
get standardMetadataKeys() {
return this.standard.keys();
}
/**
* 定義済みのメタデータを取得します。
* @param key
*/
getStandardMetadata(key) {
return this.standard.get(key);
}
/**
* 定義済みのメタデータを設定します。
* @param key
* @param value
*/
setStandardMetadata(key, value) {
if (value) {
this.standard.set(key, value);
}
else {
this.standard.delete(key);
}
}
/**
* カスタムメタデータのキーの一覧を取得します。
*/
get customMetadataKeys() {
return this.custom.keys();
}
/**
* カスタムメタデータを取得します。
* @param key
*/
getCustomMetadata(key) {
return this.custom.get(key);
}
/**
* カスタムメタデータを設定します。
* @param key
* @param value
*/
setCustomMetadata(key, value) {
if (value) {
this.custom.set(key, value);
}
else {
this.custom.delete(key);
}
}
}
exports.RecordMetadata = RecordMetadata;
function copyNodeMetadata(source, target) {
target.comment = source.comment;
target.bookmark = source.bookmark;
target.customData = source.customData;
target.setElapsedMs(source.elapsedMs);
}
class NodeImpl {
ply;
prev;
branchIndex;
activeBranch;
nextColor;
move;
isCheck;
displayText;
sfen;
next = null;
branch = null;
comment = "";
customData;
elapsedMs = 0;
totalElapsedMs = 0;
bookmark = "";
constructor(ply, prev, branchIndex, activeBranch, nextColor, move, isCheck, displayText, sfen) {
this.ply = ply;
this.prev = prev;
this.branchIndex = branchIndex;
this.activeBranch = activeBranch;
this.nextColor = nextColor;
this.move = move;
this.isCheck = isCheck;
this.displayText = displayText;
this.sfen = sfen;
}
get timeText() {
const elapsed = (0, time_1.millisecondsToMSS)(this.elapsedMs);
const totalElapsed = (0, time_1.millisecondsToHHMMSS)(this.totalElapsedMs);
return `${elapsed} / ${totalElapsed}`;
}
get hasBranch() {
return !!this.prev && !!this.prev.next && !!this.prev.next.branch;
}
get isFirstBranch() {
return !this.prev || this.prev.next === this;
}
get isLastMove() {
if (!this.next) {
return true;
}
for (let p = this.next; p; p = p.branch) {
if (p.move instanceof move_1.Move) {
return false;
}
}
return true;
}
updateTotalElapsedMs() {
this.totalElapsedMs = this.elapsedMs;
if (this.prev && this.prev.prev) {
this.totalElapsedMs += this.prev.prev.totalElapsedMs;
}
}
setElapsedMs(elapsedMs) {
this.elapsedMs = elapsedMs;
this.updateTotalElapsedMs();
let p = this.next;
const stack = [];
while (p) {
p.updateTotalElapsedMs();
if (p.branch) {
stack.push(p.branch);
}
if (p.next) {
p = p.next;
}
else {
p = stack.pop() || null;
}
}
}
static newRootEntry(position) {
return new NodeImpl(0, // ply
null, // prev
0, // branchIndex
true, // activeBranch
position.color, // color
(0, move_1.specialMove)(move_1.SpecialMoveType.START), // move
false, // isCheck
"開始局面", // displayText
position.sfen);
}
}
/**
* 棋譜
*/
class Record {
metadata;
_initialPosition;
_position;
_first;
_current;
repetitionCounts = {};
repetitionStart = {};
onChangePosition = () => {
/* noop */
};
onClear = () => {
/* noop */
};
onAddNode = () => {
/* noop */
};
onRemoveNode = () => {
/* noop */
};
constructor(position) {
this.metadata = new RecordMetadata();
this._initialPosition = position ? position.clone() : new position_1.Position();
this._position = this.initialPosition.clone();
this._first = NodeImpl.newRootEntry(this._initialPosition);
this._current = this._first;
this.incrementRepetition();
}
/**
* 初期局面を返します。
*/
get initialPosition() {
return this._initialPosition;
}
/**
* 現在の局面を返します。
*/
get position() {
return this._position;
}
/**
* 初期局面のノードを返します。
* このノードには必ず SpecialMoveType.START が設定されます。
* first.next が1手目に該当します。
*/
get first() {
return this._first;
}
/**
* 現在の局面のノードを返します。
*/
get current() {
return this._current;
}
/**
* アクティブな経路の指し手の一覧を返します。
*/
get moves() {
const moves = this.movesBefore;
for (let p = this._current.next; p; p = p.next) {
while (!p.activeBranch) {
p = p.branch;
}
moves.push(p);
}
return moves;
}
/**
* 現在の局面までの指し手の一覧を返します。
*/
get movesBefore() {
return this._movesBefore;
}
get _movesBefore() {
const moves = new Array();
moves.unshift(this._current);
for (let p = this._current.prev; p; p = p.prev) {
moves.unshift(p);
}
return moves;
}
/**
* アクティブな経路の総手数を返します。
*/
get length() {
let len = this._current.ply;
for (let p = this._current.next; p; p = p.next) {
while (!p.activeBranch) {
p = p.branch;
}
len = p.ply;
}
return len;
}
/**
* 最初の兄弟ノードを返します。
*/
get branchBegin() {
return this._current.prev ? this._current.prev.next : this._current;
}
/**
* 指定した局面で棋譜を初期化します。
* @param position
*/
clear(position) {
this.metadata = new RecordMetadata();
if (position) {
this._initialPosition = position.clone();
}
this._position = this.initialPosition.clone();
this._first = NodeImpl.newRootEntry(this._initialPosition);
this._current = this._first;
this.repetitionCounts = {};
this.repetitionStart = {};
this.incrementRepetition();
this.onClear(this._initialPosition);
this.onChangePosition();
}
/**
* 1手前に戻ります。
*/
goBack() {
if (this._goBack()) {
this.onChangePosition();
return true;
}
return false;
}
_goBack() {
if (this._current.prev) {
if (this._current.move instanceof move_1.Move) {
this.decrementRepetition();
this._position.undoMove(this._current.move);
}
this._current = this._current.prev;
return true;
}
return false;
}
/**
* 1手先に進みます。
*/
goForward() {
if (this._goForward()) {
this.onChangePosition();
return true;
}
return false;
}
_goForward() {
if (this._current.next) {
this._current = this._current.next;
while (!this._current.activeBranch) {
this._current = this._current.branch;
}
if (this._current.move instanceof move_1.Move) {
this._position.doMove(this._current.move, {
ignoreValidation: true,
});
this.incrementRepetition();
}
return true;
}
return false;
}
/**
* アクティブな経路上で指定した手数まで移動します。
* @param ply
*/
goto(ply) {
const orgPly = this._current.ply;
this._goto(ply);
if (orgPly !== this._current.ply) {
this.onChangePosition();
}
}
gotoNode(node) {
const variation = [];
let first = node;
for (let p = node; p.prev; p = p.prev) {
variation.unshift(p);
first = p.prev;
}
if (this.first !== first) {
return false;
}
const orgNode = this._current;
this._goto(0);
for (const p of variation) {
this._goForward();
this._switchBranchByIndex(p.branchIndex);
}
if (orgNode !== this._current) {
this.onChangePosition();
}
return true;
}
_goto(ply) {
while (ply < this._current.ply) {
if (!this._goBack()) {
break;
}
}
while (ply > this._current.ply) {
if (!this._goForward()) {
break;
}
}
}
/**
* 全ての分岐選択を初期化して最初のノードをアクティブにします。
*/
resetAllBranchSelection() {
let confluence = this._current;
for (let node = this._current; node.prev; node = node.prev) {
if (!node.isFirstBranch) {
confluence = node.prev;
}
}
this._forEach((node) => {
node.activeBranch = node.isFirstBranch;
});
if (this._current !== confluence) {
while (this._current !== confluence) {
this._goBack();
}
this.onChangePosition();
}
}
/**
* インデクスを指定して兄弟ノードを選択します。
* @param index
*/
switchBranchByIndex(index) {
if (this.current.branchIndex === index) {
return true;
}
if (!this._switchBranchByIndex(index)) {
return false;
}
this.onChangePosition();
return true;
}
_switchBranchByIndex(index) {
if (this.current.branchIndex === index) {
return true;
}
if (!this._current.prev) {
return false;
}
let ok = false;
for (let p = this._current.prev.next; p; p = p.branch) {
if (p.branchIndex === index) {
p.activeBranch = true;
if (this._current.move instanceof move_1.Move) {
this.decrementRepetition();
this._position.undoMove(this._current.move);
}
this._current = p;
if (this._current.move instanceof move_1.Move) {
this._position.doMove(this._current.move, {
ignoreValidation: true,
});
this.incrementRepetition();
}
ok = true;
}
else {
p.activeBranch = false;
}
}
if (!ok) {
this._current.activeBranch = true;
}
return ok;
}
/**
* 指し手を追加して1手先に進みます。
* 現在のノードが特殊な指し手(ex. 投了)の場合は前のノードに戻ってから追加します。
* 既に同じ指し手が存在する場合はそのノードへ移動します。
*/
append(move, opt) {
if (this._append(move, opt)) {
this.onChangePosition();
return true;
}
return false;
}
_append(move, opt) {
// convert SpecialMoveType to SpecialMove
if (typeof move === "string") {
move = (0, move_1.specialMove)(move);
}
// 指し手を表す文字列を取得する。
const lastMove = this.current.move instanceof move_1.Move ? this.current.move : undefined;
const displayText = move instanceof move_1.Move
? (0, text_1.formatMove)(this.position, move, { lastMove })
: (0, text_1.formatSpecialMove)(move);
// 局面を動かす。
let isCheck = false;
if (move instanceof move_1.Move) {
if (!this._position.doMove(move, opt)) {
return false;
}
this.incrementRepetition();
isCheck = this.position.checked;
}
// 特殊な指し手のノードの場合は前のノードに戻る。
if (this._current !== this.first && !(this._current.move instanceof move_1.Move)) {
this._goBack();
}
// 最終ノードの場合は単に新しいノードを追加する。
if (!this._current.next) {
this._current.next = new NodeImpl(this._current.ply + 1, // number
this._current, // prev
0, // branchIndex
true, // activeBranch
this.position.color, // nextColor
move, isCheck, displayText, this.position.sfen);
this._current = this._current.next;
this._current.setElapsedMs(0);
this.onAddNode(this._current);
return true;
}
// 既存の兄弟ノードから選択を解除する。
let p;
for (p = this._current.next; p; p = p.branch) {
p.activeBranch = false;
}
// 同じ指し手が既に存在する場合はそのノードへ移動して終わる。
let lastBranch = this._current.next;
for (p = this._current.next; p; p = p.branch) {
if ((0, move_1.areSameMoves)(move, p.move)) {
this._current = p;
this._current.activeBranch = true;
return true;
}
lastBranch = p;
}
// 兄弟ノードを追加する。
this._current = new NodeImpl(this._current.ply + 1, // number
this._current, // prev
lastBranch.branchIndex + 1, // branchIndex
true, // activeBranch
this.position.color, // nextColor
move, isCheck, displayText, this.position.sfen);
this._current.setElapsedMs(0);
lastBranch.branch = this._current;
this.onAddNode(this._current);
return true;
}
/**
* 次の兄弟ノードと順序を入れ替えます。
*/
swapWithNextBranch() {
if (!this._current.branch) {
return false;
}
return Record.swapWithPreviousBranch(this._current.branch);
}
/**
* 前の兄弟ノードと順序を入れ替えます。
*/
swapWithPreviousBranch() {
return Record.swapWithPreviousBranch(this._current);
}
static swapWithPreviousBranch(target) {
const prev = target.prev;
if (!prev || !prev.next || prev.next == target) {
return false;
}
if (prev.next.branch === target) {
const pair = prev.next;
pair.branch = target.branch;
target.branch = pair;
prev.next = target;
[target.branchIndex, pair.branchIndex] = [pair.branchIndex, target.branchIndex];
return true;
}
for (let p = prev.next; p.branch; p = p.branch) {
if (p.branch.branch === target) {
const pair = p.branch;
pair.branch = target.branch;
target.branch = pair;
p.branch = target;
[target.branchIndex, pair.branchIndex] = [pair.branchIndex, target.branchIndex];
return true;
}
}
return false;
}
/**
* 現在の指し手を削除します。
*/
removeCurrentMove() {
const target = this._current;
if (!this.goBack()) {
return this.removeNextMove();
}
this.onRemoveSubTree(target);
if (this._current.next === target) {
this._current.next = target.branch;
}
else {
for (let p = this._current.next; p; p = p.branch) {
if (p.branch === target) {
p.branch = target.branch;
break;
}
}
}
let branchIndex = 0;
for (let p = this._current.next; p; p = p.branch) {
p.branchIndex = branchIndex;
branchIndex += 1;
}
if (this._current.next) {
this._current.next.activeBranch = true;
}
this.onChangePosition();
return true;
}
/**
* 後続の手を全て削除します。
*/
removeNextMove() {
if (this._current.next) {
for (let p = this.current.next; p; p = p.branch) {
this.onRemoveSubTree(p);
}
this._current.next = null;
return true;
}
return false;
}
onRemoveSubTree(root) {
let p = root;
while (p) {
if (p.next) {
p = p.next;
continue;
}
this.onRemoveNode(p);
if (p === root) {
return;
}
while (!p.branch) {
if (!p.prev) {
return;
}
p = p.prev;
this.onRemoveNode(p);
if (p === root) {
return;
}
}
p = p.branch;
}
}
/**
* 棋譜をマージします。
* 経過時間やコメント、しおりが両方にある場合は自分の側を優先します。
* 初期局面が異なる場合はマージできません。
* @param record
*/
merge(record) {
// 初期局面が異なる場合はマージできない。
if (this.initialPosition.sfen !== record.initialPosition.sfen) {
return false;
}
// 元居た局面までのパスを記憶する。
const path = this.movesBefore;
// 開始局面に戻してマージを実行する。
this._goto(0);
this.mergeIntoCurrentPosition(record);
// 元居た局面まで戻す。
for (let i = 1; i < path.length; i++) {
this._append(path[i].move, { ignoreValidation: true });
}
return true;
}
/**
* 棋譜を現在の局面からのサブツリーとしてマージします。
* 経過時間やコメント、しおりが両方にある場合は自分の側を優先します。
* 開始局面が一致していなくてもマージできますが、指し手が挿入不能な場合その子ノードは無視されます。
* @param record
*/
mergeIntoCurrentPosition(record, option) {
const begin = this._current.ply;
let errorPly = null;
let successCount = 0;
let skipCount = 0;
// 指し手をマージする。
record.forEach((node) => {
if (node.ply === 0) {
return;
}
const ply = begin + node.ply - 1;
if (errorPly !== null && ply > errorPly) {
skipCount++;
return;
}
this._goto(ply);
if (!this._append(node.move, option)) {
errorPly = ply;
skipCount++;
return;
}
errorPly = null;
successCount++;
if (node.elapsedMs && !this.current.elapsedMs) {
this.current.setElapsedMs(node.elapsedMs);
}
if (node.comment && !this.current.comment) {
this.current.comment = node.comment;
}
if (node.bookmark && !this.current.bookmark) {
this.current.bookmark = node.bookmark;
}
if (node.customData && !this.current.customData) {
this.current.customData = node.customData;
}
});
// 元居た局面まで戻す。
this._goto(begin);
return { successCount, skipCount };
}
/**
* 指定したしおりがある局面まで移動します。
* @param bookmark
*/
jumpToBookmark(bookmark) {
// 既に該当する局面にいる場合は何もしない。
if (this._current.bookmark === bookmark) {
return true;
}
// 一致するブックマークを探す。
const node = this.find((node) => node.bookmark === bookmark);
if (!node) {
return false;
}
// ブックマークのある局面までの経路を配列に書き出す。
const route = [];
for (let p = node; p; p = p.prev) {
route[p.ply] = p;
}
// 合流するところまで局面を戻す。
while (this._current !== route[this._current.ply]) {
this.goBack();
}
// ブックマークのある局面まで指し手を進める。
while (route.length > this._current.ply + 1) {
this.append(route[this._current.ply + 1].move);
}
this.onChangePosition();
return true;
}
incrementRepetition() {
const sfen = this.position.sfen;
if (this.repetitionCounts[sfen]) {
this.repetitionCounts[sfen] += 1;
}
else {
this.repetitionCounts[sfen] = 1;
this.repetitionStart[sfen] = this.current.ply;
}
}
decrementRepetition() {
const sfen = this.position.sfen;
this.repetitionCounts[sfen] -= 1;
if (this.repetitionCounts[sfen] === 0) {
delete this.repetitionCounts[sfen];
delete this.repetitionStart[sfen];
}
}
/**
* 千日手かどうかを判定します。
* 現在の局面が4回目以上の同一局面である場合に true を返します。
*/
get repetition() {
return this.repetitionCounts[this.position.sfen] >= 4;
}
/**
* 現在の局面まで(Record.current着手後を含む)に指定された局面が何回現れたかを返します。
* @param position
*/
getRepetitionCount(position) {
return this.repetitionCounts[position.sfen] || 0;
}
/**
* 連続王手の千日手かどうかを判定します。
* 現在の局面が4回目以上の同一局面であり、かつ同一局面が最初に出現したときから一方の王手が連続している場合に true を返します。
*/
get perpetualCheck() {
if (!this.repetition) {
return null;
}
const sfen = this.position.sfen;
const since = this.repetitionStart[sfen];
let black = true;
let white = true;
let color = this.position.color;
for (let p = this.current; p.ply >= since; p = p.prev) {
color = (0, color_1.reverseColor)(color);
if (p.isCheck) {
continue;
}
if (color === color_1.Color.BLACK) {
black = false;
}
else {
white = false;
}
}
return black ? color_1.Color.BLACK : white ? color_1.Color.WHITE : null;
}
/**
* getUSI をオプション無しで呼び出した場合と同じ値を返します。
*/
get usi() {
return this.getUSI();
}
/**
* USI形式の文字列を返します。
* @param opts
*/
getUSI(opts) {
const sfen = this.initialPosition.sfen;
const useStartpos = opts?.startpos !== false && sfen === position_1.InitialPositionSFEN.STANDARD;
const position = "position " + (useStartpos ? "startpos" : "sfen " + this.initialPosition.sfen);
const moves = [];
for (let p = this.first;; p = p.next) {
while (!p.activeBranch) {
p = p.branch;
}
if (p.move instanceof move_1.Move) {
moves.push(p.move.usi);
}
else if (opts?.resign && p.move.type === move_1.SpecialMoveType.RESIGN) {
moves.push("resign");
}
if (!p.next || (!opts?.allMoves && p === this.current)) {
break;
}
}
if (moves.length === 0) {
return position;
}
return [position, "moves"].concat(moves).join(" ");
}
/**
* 現在の局面のSFEN形式の文字列を返します。
*/
get sfen() {
return this.position.getSFEN(this._current.ply + 1);
}
/**
* USEN (Url Safe sfen-Extended Notation) 形式の文字列を返します。
* https://www.slideshare.net/slideshow/scalajs-web/92707205#15
* @returns [usen, branchIndex]
*/
get usen() {
const sfen = this.initialPosition.sfen;
let usen = sfen === position_1.InitialPositionSFEN.STANDARD
? ""
: sfen.replace(/ 1$/, "").replace(/\//g, "_").replace(/ /g, ".").replace(/\+/g, "z");
let moves = "0.";
let special = "";
let lastPly = 0;
let bi = 0;
let branchIndex = 0;
this.forEach((node) => {
if (node.ply === 0) {
// root node
return;
}
const move = node.move;
if (lastPly + 1 !== node.ply) {
usen += `~${moves}.${special}`;
moves = `${node.ply - 1}.`;
bi++;
}
if (this.current === node) {
branchIndex = bi;
}
if (!(move instanceof move_1.Move)) {
switch (move.type) {
case move_1.SpecialMoveType.RESIGN:
special = "r";
break;
case move_1.SpecialMoveType.TIMEOUT:
special = "t";
break;
case move_1.SpecialMoveType.MAX_MOVES:
case move_1.SpecialMoveType.IMPASS:
case move_1.SpecialMoveType.DRAW:
special = "j";
break;
default:
// 未定義のものは全て中断として扱う。
special = "p";
break;
}
return;
}
const from = move.from instanceof square_1.Square
? (move.from.rank - 1) * 9 + (move.from.file - 1)
: usenHandTable[move.from];
const to = (move.to.rank - 1) * 9 + (move.to.file - 1);
const m = (from * 81 + to) * 2 + (move.promote ? 1 : 0);
moves += m.toString(36).padStart(3, "0");
lastPly = node.ply;
});
usen += `~${moves}.${special}`;
return [usen, branchIndex];
}
/**
* しおりの一覧を返します。
*/
get bookmarks() {
const bookmarks = [];
const existed = {};
this.forEach((node) => {
if (node.bookmark && !existed[node.bookmark]) {
bookmarks.push(node.bookmark);
existed[node.bookmark] = true;
}
});
return bookmarks;
}
// 深さ優先で全てのノードを訪問します。
forEach(handler) {
this._forEach(handler);
}
_forEach(handler) {
this.find((node) => {
handler(node);
return false;
});
}
find(handler) {
let p = this._first;
// eslint-disable-next-line no-constant-condition
while (true) {
if (handler(p)) {
return p;
}
if (p.next) {
p = p.next;
continue;
}
while (!p.branch) {
if (!p.prev) {
return null;
}
p = p.prev;
}
p = p.branch;
}
}
getSubtree() {
// Create a new Record instance with the initial position.
const subtree = new Record(this.position);
// Copy the metadata from the current record to the subtree.
for (const key of Object.values(RecordMetadataKey)) {
const value = this.metadata.getStandardMetadata(key);
if (value) {
subtree.metadata.setStandardMetadata(key, value);
}
}
for (const key of this.metadata.customMetadataKeys) {
const value = this.metadata.getCustomMetadata(key);
if (value) {
subtree.metadata.setCustomMetadata(key, value);
}
}
// Copy the nodes from the current record to the subtree.
let p = this.current;
copyNodeMetadata(p, subtree.current);
if (!p.next) {
return subtree;
}
p = p.next;
// eslint-disable-next-line no-constant-condition
while (true) {
subtree.append(p.move, { ignoreValidation: true });
copyNodeMetadata(p, subtree.current);
if (p.next) {
p = p.next;
continue;
}
while (!p.branch) {
if (!p.prev || p.prev === this.current) {
subtree.goto(0);
return subtree;
}
subtree.goBack();
p = p.prev;
}
subtree.goBack();
p = p.branch;
}
}
on(event, handler) {
switch (event) {
case "changePosition":
this.onChangePosition = handler;
break;
case "clear":
this.onClear = handler;
break;
case "addNode":
this.onAddNode = handler;
break;
case "removeNode":
this.onRemoveNode = handler;
break;
}
}
/**
* USI形式の文字列から棋譜を読み込みます。
* @param data
*/
static newByUSI(data) {
const positionStartpos = "position startpos";
const startpos = "startpos";
const prefixPositionStartpos = "position startpos ";
const prefixPositionSFEN = "position sfen ";
const prefixStartpos = "startpos ";
const prefixSFEN = "sfen ";
const prefixMoves = "moves ";
if (data === positionStartpos || data === startpos) {
return new Record();
}
else if (data.startsWith(prefixPositionStartpos)) {
return Record.newByUSIFromMoves(data.slice(prefixPositionStartpos.length));
}
else if (data.startsWith(prefixPositionSFEN)) {
return Record.newByUSIFromSFEN(data.slice(prefixPositionSFEN.length));
}
else if (data.startsWith(prefixStartpos)) {
return Record.newByUSIFromMoves(data.slice(prefixStartpos.length));
}
else if (data.startsWith(prefixSFEN)) {
return Record.newByUSIFromSFEN(data.slice(prefixSFEN.length));
}
else if (data.startsWith(prefixMoves)) {
return Record.newByUSIFromMoves(data);
}
else {
return new errors_1.InvalidUSIError(data);
}
}
static newByUSIFromSFEN(data) {
const sections = data.split(" ");
if (sections.length < 3) {
return new errors_1.InvalidUSIError(data);
}
const movesIndex = sections.length === 3 || sections[3] === "moves" ? 3 : 4;
const position = position_1.Position.newBySFEN(sections.slice(0, movesIndex).join(" "));
if (!position) {
return new errors_1.InvalidUSIError(data);
}
return Record.newByUSIFromMoves(sections.slice(movesIndex).join(" "), position);
}
static newByUSIFromMoves(data, position) {
const record = new Record(position);
if (data.length === 0) {
return record;
}
const sections = data.split(" ");
if (sections[0] !== "moves") {
return new errors_1.InvalidUSIError(data);
}
for (let i = 1; i < sections.length; i++) {
if (sections[i] === "resign") {
record.append(move_1.SpecialMoveType.RESIGN);
break;
}
const parsed = (0, move_1.parseUSIMove)(sections[i]);
if (!parsed) {
break;
}
let move = record.position.createMove(parsed.from, parsed.to);
if (!move) {
return new errors_1.InvalidMoveError(sections[i]);
}
if (parsed.promote) {
move = move.withPromote();
}
record.append(move, { ignoreValidation: true });
}
return record;
}
/**
* USEN (Url Safe sfen-Extended Notation) 形式の文字列から棋譜を読み込みます。
* https://www.slideshare.net/slideshow/scalajs-web/92707205#15
*/
static newByUSEN(usen, branchIndex, ply) {
const sections = usen.split("~");
if (sections.length < 2) {
return new Error("USEN must have at least 2 sections.");
}
const sfen = sections[0].replace(/_/g, "/").replace(/\./g, " ").replace(/z/g, "+");
const position = sfen === "" ? new position_1.Position() : position_1.Position.newBySFEN(sfen + " 1");
if (!position) {
return new Error("Invalid SFEN in USEN.");
}
const record = new Record(position);
let activeNode = record.first;
for (let si = 1; si < sections.length; si++) {
const [n, moves, special] = sections[si].split(".");
if (!/[0-9]+/.test(n)) {
return new Error("Invalid USEN ply format.");
}
record.goto(parseInt(n));
for (let i = 0; i < moves.length; i += 3) {
const m = parseInt(moves.slice(i, i + 3), 36);
const f = Math.floor(m / 162);
const from = f < 81 ? new square_1.Square((f % 9) + 1, Math.floor(f / 9) + 1) : usenHandReverseTable[f];
const t = Math.floor((m % 162) / 2);
const to = new square_1.Square((t % 9) + 1, Math.floor(t / 9) + 1);
const promote = m % 2 === 1;
const move = record.position.createMove(from, to);
if (!move) {
return new Error("Invalid move in USEN.");
}
record.append(promote ? move.withPromote() : move, { ignoreValidation: true });
if (si - 1 === branchIndex && record.current.ply === ply) {
activeNode = record.current;
}
}
if (special === "r") {
record.append((0, move_1.specialMove)(move_1.SpecialMoveType.RESIGN));
}
else if (special === "t") {
record.append((0, move_1.specialMove)(move_1.SpecialMoveType.TIMEOUT));
}
else if (special === "j") {
record.append((0, move_1.specialMove)(move_1.SpecialMoveType.IMPASS));
}
else if (special === "p") {
record.append((0, move_1.specialMove)(move_1.SpecialMoveType.INTERRUPT));
}
if (si - 1 === branchIndex && record.current.ply === ply) {
activeNode = record.current;
}
}
if (activeNode === record.first) {
record.goto(0);
}
else {
const route = [];
for (let p = activeNode; p; p = p.prev) {
route[p.ply] = p;
}
while (record._current !== route[record._current.ply]) {
record.goBack();
}
while (route.length > record._current.ply + 1) {
record.append(route[record._current.ply + 1].move);
}
}
return record;
}
}
exports.Record = Record;
/**
* USI形式の文字列から次の手番を取得します。
* @param usi
*/
function getNextColorFromUSI(usi) {
const sections = usi.trim().split(" ");
const baseColor = sections[1] === "startpos" || sections[3] === "b" ? color_1.Color.BLACK : color_1.Color.WHITE;
const firstMoveIndex = sections[1] === "startpos"
? sections[2] === "moves"
? 3
: 2
: sections[6] === "moves"
? 7
: 6;
return (sections.length - firstMoveIndex) % 2 === 0 ? baseColor : (0, color_1.reverseColor)(baseColor);
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVjb3JkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3JlY29yZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFvSEEsZ0RBTUM7QUFNRCxnREFNQztBQU1ELHNFQVFDO0FBTUQsc0VBUUM7QUE4dkNELGtEQVlDO0FBNTZDRCx5Q0FBeUU7QUFDekUsbUNBQThDO0FBQzlDLHFDQUE2RDtBQUM3RCxpQ0FPZ0I7QUFDaEIseUNBQTRGO0FBQzVGLGlDQUF1RDtBQUN2RCxtQ0FBb0M7QUFDcEMscUNBQWtDO0FBRWxDLE1BQU0sYUFBYSxHQUFHO0lBQ3BCLENBQUMsaUJBQVMsQ0FBQyxJQUFJLENBQUMsRUFBRSxFQUFFLEdBQUcsRUFBRTtJQUN6QixDQUFDLGlCQUFTLENBQUMsS0FBSyxDQUFDLEVBQUUsRUFBRSxHQUFHLEVBQUU7SUFDMUIsQ0FBQyxpQkFBUyxDQUFDLE1BQU0sQ0FBQyxFQUFFLEVBQUUsR0FBRyxFQUFFO0lBQzNCLENBQUMsaUJBQVMsQ0FBQyxNQUFNLENBQUMsRUFBRSxFQUFFLEdBQUcsRUFBRTtJQUMzQixDQUFDLGlCQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsRUFBRSxHQUFHLENBQUM7SUFDeEIsQ0FBQyxpQkFBUyxDQUFDLE1BQU0sQ0FBQyxFQUFFLEVBQUUsR0FBRyxFQUFFO0lBQzNCLENBQUMsaUJBQVMsQ0FBQyxJQUFJLENBQUMsRUFBRSxFQUFFLEdBQUcsRUFBRTtJQUN6QixDQUFDLGlCQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsRUFBRSxHQUFHLENBQUM7SUFDeEIsQ0FBQyxpQkFBUyxDQUFDLFNBQVMsQ0FBQyxFQUFFLEVBQUUsR0FBRyxDQUFDO0lBQzdCLENBQUMsaUJBQVMsQ0FBQyxVQUFVLENBQUMsRUFBRSxFQUFFLEdBQUcsQ0FBQztJQUM5QixDQUFDLGlCQUFTLENBQUMsV0FBVyxDQUFDLEVBQUUsRUFBRSxHQUFHLENBQUM7SUFDL0IsQ0FBQyxpQkFBUyxDQUFDLFdBQVcsQ0FBQyxFQUFF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