tsshogi
Version:
TypeScript library for Shogi (Japanese chess)
915 lines • 72.9 kB
JavaScript
"use strict";
// KIF file format (.kif or .kifu)
// See http://kakinoki.o.oo7.jp/kif_format.html
Object.defineProperty(exports, "__esModule", { value: true });
exports.KakinokiFormatType = void 0;
exports.kakinokiToMetadataKey = kakinokiToMetadataKey;
exports.metadataKeyToKakinoki = metadataKeyToKakinoki;
exports.importKIF = importKIF;
exports.importKI2 = importKI2;
exports.formatKIFMove = formatKIFMove;
exports.exportKIF = exportKIF;
exports.exportKI2 = exportKI2;
exports.exportBOD = exportBOD;
const string_1 = require("./helpers/string.cjs");
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 piece_1 = require("./piece.cjs");
const position_1 = require("./position.cjs");
const record_1 = require("./record.cjs");
const square_1 = require("./square.cjs");
const text_1 = require("./text.cjs");
var KakinokiFormatType;
(function (KakinokiFormatType) {
KakinokiFormatType["KIF"] = "KIF";
KakinokiFormatType["KI2"] = "KI2";
})(KakinokiFormatType || (exports.KakinokiFormatType = KakinokiFormatType = {}));
const metadataKeyMap = {
先手: record_1.RecordMetadataKey.BLACK_NAME,
後手: record_1.RecordMetadataKey.WHITE_NAME,
下手: record_1.RecordMetadataKey.SHITATE_NAME,
上手: record_1.RecordMetadataKey.UWATE_NAME,
開始日時: record_1.RecordMetadataKey.START_DATETIME,
終了日時: record_1.RecordMetadataKey.END_DATETIME,
対局日: record_1.RecordMetadataKey.DATE,
棋戦: record_1.RecordMetadataKey.TOURNAMENT,
戦型: record_1.RecordMetadataKey.STRATEGY,
表題: record_1.RecordMetadataKey.TITLE,
持ち時間: record_1.RecordMetadataKey.TIME_LIMIT,
秒読み: record_1.RecordMetadataKey.BYOYOMI,
消費時間: record_1.RecordMetadataKey.TIME_SPENT,
場所: record_1.RecordMetadataKey.PLACE,
掲載: record_1.RecordMetadataKey.POSTED_ON,
備考: record_1.RecordMetadataKey.NOTE,
先手省略名: record_1.RecordMetadataKey.BLACK_SHORT_NAME,
後手省略名: record_1.RecordMetadataKey.WHITE_SHORT_NAME,
記録係: record_1.RecordMetadataKey.SCOREKEEPER,
作品番号: record_1.RecordMetadataKey.OPUS_NO,
作品名: record_1.RecordMetadataKey.OPUS_NAME,
作者: record_1.RecordMetadataKey.AUTHOR,
発表誌: record_1.RecordMetadataKey.PUBLISHED_BY,
発表年月: record_1.RecordMetadataKey.PUBLISHED_AT,
出典: record_1.RecordMetadataKey.SOURCE,
手数: record_1.RecordMetadataKey.LENGTH,
完全性: record_1.RecordMetadataKey.INTEGRITY,
分類: record_1.RecordMetadataKey.CATEGORY,
受賞: record_1.RecordMetadataKey.AWARD,
// CSA 形式で規定されている項目
先手持ち時間: record_1.RecordMetadataKey.BLACK_TIME_LIMIT,
後手持ち時間: record_1.RecordMetadataKey.WHITE_TIME_LIMIT,
最大手数: record_1.RecordMetadataKey.MAX_MOVES,
持将棋: record_1.RecordMetadataKey.JISHOGI,
};
/**
* 柿木形式のメタデータのキー名を RecordMetadataKey へ変換します。
* @param key
*/
function kakinokiToMetadataKey(key) {
return metadataKeyMap[key];
}
const metadataNameMap = {
[record_1.RecordMetadataKey.BLACK_NAME]: "先手",
[record_1.RecordMetadataKey.WHITE_NAME]: "後手",
[record_1.RecordMetadataKey.SHITATE_NAME]: "下手",
[record_1.RecordMetadataKey.UWATE_NAME]: "上手",
[record_1.RecordMetadataKey.START_DATETIME]: "開始日時",
[record_1.RecordMetadataKey.END_DATETIME]: "終了日時",
[record_1.RecordMetadataKey.DATE]: "対局日",
[record_1.RecordMetadataKey.TOURNAMENT]: "棋戦",
[record_1.RecordMetadataKey.STRATEGY]: "戦型",
[record_1.RecordMetadataKey.TITLE]: "表題",
[record_1.RecordMetadataKey.TIME_LIMIT]: "持ち時間",
[record_1.RecordMetadataKey.BYOYOMI]: "秒読み",
[record_1.RecordMetadataKey.TIME_SPENT]: "消費時間",
[record_1.RecordMetadataKey.PLACE]: "場所",
[record_1.RecordMetadataKey.POSTED_ON]: "掲載",
[record_1.RecordMetadataKey.NOTE]: "備考",
[record_1.RecordMetadataKey.BLACK_SHORT_NAME]: "先手省略名",
[record_1.RecordMetadataKey.WHITE_SHORT_NAME]: "後手省略名",
[record_1.RecordMetadataKey.SCOREKEEPER]: "記録係",
[record_1.RecordMetadataKey.OPUS_NO]: "作品番号",
[record_1.RecordMetadataKey.OPUS_NAME]: "作品名",
[record_1.RecordMetadataKey.AUTHOR]: "作者",
[record_1.RecordMetadataKey.PUBLISHED_BY]: "発表誌",
[record_1.RecordMetadataKey.PUBLISHED_AT]: "発表年月",
[record_1.RecordMetadataKey.SOURCE]: "出典",
[record_1.RecordMetadataKey.LENGTH]: "手数",
[record_1.RecordMetadataKey.INTEGRITY]: "完全性",
[record_1.RecordMetadataKey.CATEGORY]: "分類",
[record_1.RecordMetadataKey.AWARD]: "受賞",
// CSA 形式で規定されている項目
[record_1.RecordMetadataKey.BLACK_TIME_LIMIT]: "先手持ち時間",
[record_1.RecordMetadataKey.WHITE_TIME_LIMIT]: "後手持ち時間",
[record_1.RecordMetadataKey.MAX_MOVES]: "最大手数",
[record_1.RecordMetadataKey.JISHOGI]: "持将棋",
};
/**
* RecordMetadataKey を柿木形式のメタデータのキー名へ変換します。
* @param key
*/
function metadataKeyToKakinoki(key) {
return metadataNameMap[key];
}
var LineType;
(function (LineType) {
LineType[LineType["PROGRAM_COMMENT"] = 0] = "PROGRAM_COMMENT";
LineType[LineType["METADATA"] = 1] = "METADATA";
LineType[LineType["HANDICAP"] = 2] = "HANDICAP";
LineType[LineType["BLACK_HAND"] = 3] = "BLACK_HAND";
LineType[LineType["WHITE_HAND"] = 4] = "WHITE_HAND";
LineType[LineType["BOARD"] = 5] = "BOARD";
LineType[LineType["BLACK_TURN"] = 6] = "BLACK_TURN";
LineType[LineType["WHITE_TURN"] = 7] = "WHITE_TURN";
LineType[LineType["MOVE"] = 8] = "MOVE";
LineType[LineType["MOVE2"] = 9] = "MOVE2";
LineType[LineType["BRANCH"] = 10] = "BRANCH";
LineType[LineType["COMMENT"] = 11] = "COMMENT";
LineType[LineType["BOOKMARK"] = 12] = "BOOKMARK";
LineType[LineType["END_OF_GAME"] = 13] = "END_OF_GAME";
LineType[LineType["UNKNOWN"] = 14] = "UNKNOWN";
})(LineType || (LineType = {}));
const linePatterns = [
{
prefix: /^#/,
type: LineType.PROGRAM_COMMENT,
removePrefix: false,
isPosition: false,
},
{
prefix: /^手合割[::]/,
type: LineType.HANDICAP,
removePrefix: true,
isPosition: true,
},
{
prefix: /^(先|下)手の持駒[::]/,
type: LineType.BLACK_HAND,
removePrefix: true,
isPosition: true,
},
{
prefix: /^(後|上)手の持駒[::]/,
type: LineType.WHITE_HAND,
removePrefix: true,
isPosition: true,
},
{
prefix: /^\|/,
type: LineType.BOARD,
removePrefix: false,
isPosition: true,
},
{
prefix: /^(先|下)手番/,
type: LineType.BLACK_TURN,
removePrefix: false,
isPosition: true,
},
{
prefix: /^(後|上)手番/,
type: LineType.WHITE_TURN,
removePrefix: false,
isPosition: true,
},
{
prefix: /^ *[0-9]+ +/,
type: LineType.MOVE,
removePrefix: false,
isPosition: false,
},
{
prefix: /^[ \u3000]*[▲△▼▽☗☖]/,
type: LineType.MOVE2,
removePrefix: false,
isPosition: false,
},
{
prefix: /^[ \u3000]*変化[::][ \u3000]*/,
type: LineType.BRANCH,
removePrefix: true,
isPosition: false,
},
{
prefix: /^\*/,
type: LineType.COMMENT,
removePrefix: true,
isPosition: false,
},
{
prefix: /^&/,
type: LineType.BOOKMARK,
removePrefix: true,
isPosition: false,
},
{
prefix: /^まで、?([0-9]+手で)?/,
type: LineType.END_OF_GAME,
removePrefix: true,
isPosition: false,
},
];
function parseLine(line) {
for (let i = 0; i < linePatterns.length; i++) {
const pattern = linePatterns[i];
const matched = line.match(pattern.prefix);
if (matched) {
const begin = pattern.removePrefix ? matched[0].length : 0;
return {
type: pattern.type,
data: line.substring(begin),
isPosition: pattern.isPosition,
metadataKey: "",
};
}
}
const metadataPrefix = line.match(/^[^ ::]+[::]/);
if (metadataPrefix) {
const prefix = metadataPrefix[0];
return {
type: LineType.METADATA,
data: line.substring(prefix.length),
isPosition: false,
metadataKey: prefix.substring(0, prefix.length - 1),
};
}
return {
type: LineType.UNKNOWN,
data: line,
isPosition: false,
metadataKey: "",
};
}
function readHandicap(position, data) {
switch (data.trim()) {
case "平手":
position.resetBySFEN(position_1.InitialPositionSFEN.STANDARD);
return;
case "香落ち":
position.resetBySFEN(position_1.InitialPositionSFEN.HANDICAP_LANCE);
return;
case "右香落ち":
position.resetBySFEN(position_1.InitialPositionSFEN.HANDICAP_RIGHT_LANCE);
return;
case "角落ち":
position.resetBySFEN(position_1.InitialPositionSFEN.HANDICAP_BISHOP);
return;
case "飛車落ち":
position.resetBySFEN(position_1.InitialPositionSFEN.HANDICAP_ROOK);
return;
case "飛香落ち":
position.resetBySFEN(position_1.InitialPositionSFEN.HANDICAP_ROOK_LANCE);
return;
case "二枚落ち":
position.resetBySFEN(position_1.InitialPositionSFEN.HANDICAP_2PIECES);
return;
case "四枚落ち":
position.resetBySFEN(position_1.InitialPositionSFEN.HANDICAP_4PIECES);
return;
case "六枚落ち":
position.resetBySFEN(position_1.InitialPositionSFEN.HANDICAP_6PIECES);
return;
case "八枚落ち":
position.resetBySFEN(position_1.InitialPositionSFEN.HANDICAP_8PIECES);
return;
case "十枚落ち":
position.resetBySFEN(position_1.InitialPositionSFEN.HANDICAP_10PIECES);
return;
case "その他":
position.resetBySFEN(position_1.InitialPositionSFEN.EMPTY);
return;
}
// マイナビ系のソフトウェアは「手合割:詰将棋」を使用する場合がある。
// それを含め柿木将棋で規定していない値が使われるケースがしばしばあり、
// それらに対して適切な処理を判断しようがなく、
// エラーを返すわけにも行かないためここでは何もしない。
}
const stringToSpecialMoveType = {
中断: move_1.SpecialMoveType.INTERRUPT,
投了: move_1.SpecialMoveType.RESIGN,
持将棋: move_1.SpecialMoveType.IMPASS,
千日手: move_1.SpecialMoveType.REPETITION_DRAW,
詰み: move_1.SpecialMoveType.MATE,
詰: move_1.SpecialMoveType.MATE,
不詰: move_1.SpecialMoveType.NO_MATE,
切れ負け: move_1.SpecialMoveType.TIMEOUT,
反則勝ち: move_1.SpecialMoveType.FOUL_WIN,
反則負け: move_1.SpecialMoveType.FOUL_LOSE,
入玉勝ち: move_1.SpecialMoveType.ENTERING_OF_KING,
不戦勝: move_1.SpecialMoveType.WIN_BY_DEFAULT,
不戦敗: move_1.SpecialMoveType.LOSE_BY_DEFAULT,
};
const moveRegExp = /^ *([0-9]+) +[▲△▼▽]?([123456789][一二三四五六七八九]|同\u3000*)(王|玉|飛|龍|竜|角|馬|金|銀|成銀|全|桂|成桂|圭|香|成香|杏|歩|と)\u3000*(成?)(打|\([1-9][1-9]\)) *([^ ].*|$)/;
const timeRegExp = /\( *([0-9]+):([0-9]+)\/[0-9: ]*\)/;
const specialMoveRegExp = /^ *([0-9]+) +([^ \u3000]+) *([^ ].*|$)/;
const branchRegExp = /^ *([0-9]+)/;
function readBoard(board, data) {
if (data.length < 21) {
return new errors_1.InvalidBoardError(data);
}
const rankStr = data[20];
const rank = (0, text_1.stringToNumber)(rankStr);
if (!rank) {
return new errors_1.InvalidBoardError(data);
}
for (let x = 0; x < 9; x += 1) {
const file = 9 - x;
const square = new square_1.Square(file, rank);
const index = x * 2 + 1;
const pieceStr = data[index + 1];
const pieceType = (0, text_1.stringToPieceType)(pieceStr);
if (!pieceType) {
board.remove(square);
continue;
}
const color = data[index] !== "v" ? color_1.Color.BLACK : color_1.Color.WHITE;
board.set(square, new piece_1.Piece(color, pieceType));
}
}
function readHand(hand, data) {
// NOTE:
// スペースで区切られていないものでも Kifu for Windows や ShogiGUI は読み込める。
// See: https://github.com/sunfish-shogi/shogihome/issues/572
const sections = data.split(/[ ]/);
for (const section of sections) {
if (!section || section === "なし") {
continue;
}
const pieceStr = section[0];
const numberStr = section.substring(1);
const pieceType = (0, text_1.stringToPieceType)(pieceStr);
const n = (0, text_1.stringToNumber)(numberStr) || 1;
if (!pieceType) {
return new errors_1.InvalidHandPieceError(section);
}
hand.add(pieceType, n);
}
return;
}
function readMoveTime(record, data) {
const timeResult = timeRegExp.exec(data);
if (timeResult) {
const minutes = timeResult[1];
const seconds = timeResult[2];
const s = Number.parseInt(minutes) * 60 + Number.parseInt(seconds);
record.current.setElapsedMs(s * 1e3);
}
}
function readMove(record, data) {
const result = readRegularMove(record, data);
if (result instanceof Error) {
return result;
}
else if (result) {
return;
}
if (readSpecialMove(record, data)) {
return;
}
return new errors_1.InvalidMoveError(data);
}
function readRegularMove(record, data) {
const result = moveRegExp.exec(data);
if (!result) {
return false;
}
const num = Number(result[1]);
const toStr = result[2];
const pieceTypeStr = result[3];
const promStr = result[4];
const fromStr = result[5];
const time = result[6];
if (num === 0) {
return new errors_1.InvalidMoveNumberError(data);
}
record.goto(num - 1);
let to;
let from;
if (toStr.startsWith("同")) {
if (!(record.current.move instanceof move_1.Move)) {
return new errors_1.InvalidDestinationError(data);
}
to = record.current.move.to;
}
else {
const file = (0, text_1.stringToNumber)(toStr[0]);
const rank = (0, text_1.stringToNumber)(toStr[1]);
to = new square_1.Square(file, rank);
}
if (fromStr === "打") {
from = (0, text_1.stringToPieceType)(pieceTypeStr);
}
else {
const file = (0, text_1.stringToNumber)(fromStr[1]);
const rank = (0, text_1.stringToNumber)(fromStr[2]);
from = new square_1.Square(file, rank);
}
let move = record.position.createMove(from, to);
if (!move) {
return new errors_1.InvalidMoveError(data);
}
if (promStr === "成") {
move = move.withPromote();
}
record.append(move, {
ignoreValidation: true,
});
readMoveTime(record, time);
return true;
}
function readSpecialMove(record, data) {
const result = specialMoveRegExp.exec(data);
if (!result) {
return false;
}
const num = Number(result[1]);
const type = stringToSpecialMoveType[result[2]];
const time = result[3];
record.goto(num - 1);
let move;
if (type) {
move = (0, move_1.specialMove)(type);
}
else {
move = (0, move_1.anySpecialMove)(result[2]);
}
record.append(move, {
ignoreValidation: true,
});
readMoveTime(record, time);
return true;
}
function readMove2(record, data) {
const lastMove = record.current.move instanceof move_1.Move ? record.current.move : undefined;
const [moves, e] = (0, text_1.parseMoves)(record.position, data, lastMove);
if (e) {
return e;
}
for (const move of moves) {
record.append(move, { ignoreValidation: true });
}
}
function readBranch(record, data) {
const result = branchRegExp.exec(data);
if (!result) {
return new errors_1.InvalidMoveNumberError(data);
}
const num = Number(result[1]);
if (num === 0 || num > record.current.ply + 1) {
return new errors_1.InvalidMoveNumberError(data);
}
record.goto(num - 1);
}
function readEndOfGame(record, data) {
const clean = data.replaceAll(/[\s\u3000]/g, "");
// NOTE: 末尾 "勝ち" は "時間切れにより...の勝ち" "反則勝ち" ともマッチするので最後に判定する必要がある。
if (clean.startsWith("時間切れ")) {
record.append((0, move_1.specialMove)(move_1.SpecialMoveType.TIMEOUT));
}
else if (clean.endsWith("反則勝ち")) {
record.append((0, move_1.specialMove)(move_1.SpecialMoveType.FOUL_WIN));
}
else if (clean.endsWith("反則負け")) {
record.append((0, move_1.specialMove)(move_1.SpecialMoveType.FOUL_LOSE));
}
else if (clean.endsWith("入玉勝ち")) {
record.append((0, move_1.specialMove)(move_1.SpecialMoveType.ENTERING_OF_KING));
}
else if (clean.endsWith("勝ち")) {
record.append((0, move_1.specialMove)(move_1.SpecialMoveType.RESIGN));
}
else {
const type = stringToSpecialMoveType[clean];
if (type) {
record.append((0, move_1.specialMove)(type));
}
else {
record.append((0, move_1.anySpecialMove)(clean));
}
}
}
/**
* KIF 形式の文字列を読み込みます。
* @param data
*/
function importKIF(data) {
return importKakinoki(data, KakinokiFormatType.KIF);
}
/**
* KI2 形式の文字列を読み込みます。
* @param data
*/
function importKI2(data) {
return importKakinoki(data, KakinokiFormatType.KI2);
}
function importKakinoki(data, formatType) {
const metadata = new record_1.RecordMetadata();
const record = new record_1.Record();
const lines = data.split(/\r?\n/);
const position = new position_1.Position();
let preMoveComment = "";
let preMoveBookmark = "";
let isMoveSection = false;
const startMoveSectionIfNot = () => {
if (isMoveSection) {
return;
}
record.clear(position);
record.first.comment = preMoveComment;
record.first.bookmark = preMoveBookmark;
isMoveSection = true;
};
for (const line of lines) {
if (line === "") {
continue;
}
const parsed = parseLine(line);
if (isMoveSection && parsed.isPosition) {
return new errors_1.InvalidLineError(line);
}
let e;
switch (parsed.type) {
case LineType.METADATA: {
const standardKey = metadataKeyMap[parsed.metadataKey];
if (standardKey) {
metadata.setStandardMetadata(standardKey, parsed.data);
}
else {
metadata.setCustomMetadata(parsed.metadataKey, parsed.data);
}
break;
}
case LineType.HANDICAP:
readHandicap(position, parsed.data);
break;
case LineType.BLACK_HAND:
e = readHand(position.blackHand, parsed.data);
break;
case LineType.WHITE_HAND:
e = readHand(position.whiteHand, parsed.data);
break;
case LineType.BOARD:
e = readBoard(position.board, parsed.data);
break;
case LineType.BLACK_TURN:
position.setColor(color_1.Color.BLACK);
break;
case LineType.WHITE_TURN:
position.setColor(color_1.Color.WHITE);
break;
case LineType.MOVE:
if (formatType !== KakinokiFormatType.KIF) {
return new errors_1.InvalidLineError(line);
}
startMoveSectionIfNot();
e = readMove(record, parsed.data);
break;
case LineType.MOVE2:
if (formatType !== KakinokiFormatType.KI2) {
return new errors_1.InvalidLineError(line);
}
startMoveSectionIfNot();
e = readMove2(record, parsed.data);
break;
case LineType.BRANCH:
// NOTE:
// KIF では指し手の先頭に手数が付与されるので必要ない。
// https://github.com/sunfish-shogi/shogihome/issues/570
// の不具合により、ヘッダー部に "変化:" で始まる行が存在する場合がある。
// 指し手が始まるより前に "変化:" が出現しても無視しなければならない。
if (isMoveSection && formatType === KakinokiFormatType.KI2) {
e = readBranch(record, parsed.data);
}
break;
case LineType.COMMENT:
if (isMoveSection) {
record.current.comment = (0, string_1.appendLine)(record.current.comment, parsed.data);
}
else {
preMoveComment = (0, string_1.appendLine)(preMoveComment, parsed.data);
}
break;
case LineType.BOOKMARK:
if (isMoveSection) {
record.current.bookmark = parsed.data;
}
else {
preMoveBookmark = parsed.data;
}
break;
case LineType.END_OF_GAME:
// NOTE:
// KI2 では "までn手で" で始まる行から終局理由を読み取らなければいけない。
// KIF では指し手の一つとしても記載されるので必要ない。
if (formatType === KakinokiFormatType.KI2) {
startMoveSectionIfNot();
readEndOfGame(record, parsed.data);
}
break;
case LineType.PROGRAM_COMMENT:
break;
case LineType.UNKNOWN:
break;
}
if (e) {
return e;
}
}
startMoveSectionIfNot();
record.goto(0);
record.resetAllBranchSelection();
record.metadata = metadata;
return record;
}
const specialMoveToString = {
[move_1.SpecialMoveType.START]: "",
[move_1.SpecialMoveType.RESIGN]: "投了",
[move_1.SpecialMoveType.INTERRUPT]: "中断",
[move_1.SpecialMoveType.MAX_MOVES]: "持将棋",
[move_1.SpecialMoveType.IMPASS]: "持将棋",
[move_1.SpecialMoveType.DRAW]: "持将棋",
[move_1.SpecialMoveType.REPETITION_DRAW]: "千日手",
[move_1.SpecialMoveType.MATE]: "詰み",
[move_1.SpecialMoveType.NO_MATE]: "不詰",
[move_1.SpecialMoveType.TIMEOUT]: "切れ負け",
[move_1.SpecialMoveType.FOUL_WIN]: "反則勝ち",
[move_1.SpecialMoveType.FOUL_LOSE]: "反則負け",
[move_1.SpecialMoveType.ENTERING_OF_KING]: "入玉勝ち",
[move_1.SpecialMoveType.WIN_BY_DEFAULT]: "不戦勝",
[move_1.SpecialMoveType.LOSE_BY_DEFAULT]: "不戦敗",
[move_1.SpecialMoveType.TRY]: "トライ",
};
function formatMetadata(metadata, options) {
let ret = "";
const returnCode = options?.returnCode || "\n";
for (const key of metadata.standardMetadataKeys) {
ret += metadataNameMap[key] + ":" + metadata.getStandardMetadata(key) + returnCode;
}
for (const key of metadata.customMetadataKeys) {
ret += key + ":" + metadata.getCustomMetadata(key) + returnCode;
}
return ret;
}
function formatPosition(position, options) {
const returnCode = options?.returnCode || "\n";
switch (position.sfen) {
case position_1.InitialPositionSFEN.STANDARD:
return "手合割:平手" + returnCode;
case position_1.InitialPositionSFEN.HANDICAP_LANCE:
return "手合割:香落ち" + returnCode;
case position_1.InitialPositionSFEN.HANDICAP_RIGHT_LANCE:
return "手合割:右香落ち" + returnCode;
case position_1.InitialPositionSFEN.HANDICAP_BISHOP:
return "手合割:角落ち" + returnCode;
case position_1.InitialPositionSFEN.HANDICAP_ROOK:
return "手合割:飛車落ち" + returnCode;
case position_1.InitialPositionSFEN.HANDICAP_ROOK_LANCE:
return "手合割:飛香落ち" + returnCode;
case position_1.InitialPositionSFEN.HANDICAP_2PIECES:
return "手合割:二枚落ち" + returnCode;
case position_1.InitialPositionSFEN.HANDICAP_4PIECES:
return "手合割:四枚落ち" + returnCode;
case position_1.InitialPositionSFEN.HANDICAP_6PIECES:
return "手合割:六枚落ち" + returnCode;
case position_1.InitialPositionSFEN.HANDICAP_8PIECES:
return "手合割:八枚落ち" + returnCode;
case position_1.InitialPositionSFEN.HANDICAP_10PIECES:
return "手合割:十枚落ち" + returnCode;
}
return formatBOD(position, options);
}
function formatBOD(position, options) {
const returnCode = options?.returnCode || "\n";
let ret = "";
ret += "後手の持駒:" + formatHand(position.whiteHand) + returnCode;
ret += " 9 8 7 6 5 4 3 2 1" + returnCode;
ret += "+---------------------------+" + returnCode;
for (let y = 0; y < 9; y++) {
ret += "|";
for (let x = 0; x < 9; x++) {
const square = square_1.Square.newByXY(x, y);
const piece = position.board.at(square);
if (!piece) {
ret += " ・";
}
else if (piece.color === color_1.Color.BLACK) {
ret += " " + (0, text_1.pieceTypeToStringForBoard)(piece.type);
}
else {
ret += "v" + (0, text_1.pieceTypeToStringForBoard)(piece.type);
}
}
ret += "|" + (0, text_1.rankToKanji)(y + 1) + returnCode;
}
ret += "+---------------------------+" + returnCode;
ret += "先手の持駒:" + formatHand(position.blackHand) + returnCode;
if (position.color === color_1.Color.BLACK) {
ret += "先手番" + returnCode;
}
else {
ret += "後手番" + returnCode;
}
return ret;
}
/**
* KIF 形式の指し手を出力します。
* @param move 対象の指し手
* @param prev 直前の指し手 ("同"の判定に使用)
*/
function formatKIFMove(move, options) {
let ret = "";
if (options?.prev && move.to.equals(options.prev.to)) {
ret += "同\u3000";
}
else {
ret += (0, text_1.fileToMultiByteChar)(move.to.file);
ret += (0, text_1.rankToKanji)(move.to.rank);
}
ret += (0, text_1.pieceTypeToStringForMove)(move.pieceType);
if (move.promote) {
ret += "成";
}
if (move.from instanceof square_1.Square) {
ret += "(" + move.from.file + move.from.rank + ")";
ret += ret.length === 7 && options?.padding ? " " : "";
}
else {
ret += "打";
ret += options?.padding ? " " : "";
}
return ret;
}
function formatHand(hand) {
let ret = "";
hand.forEach((pieceType, n) => {
if (n >= 1) {
ret += (0, text_1.pieceTypeToStringForBoard)(pieceType);
if (n >= 2) {
ret += (0, text_1.numberToKanji)(n);
}
ret += " ";
}
});
if (ret === "") {
ret = "なし";
}
return ret;
}
/**
* KIF形式の文字列を出力します。
* @param record
* @param options
*/
function exportKIF(record, options) {
let ret = "";
const returnCode = options?.returnCode || "\n";
if (options?.comment) {
for (const line of options.comment.split("\n")) {
ret += "#" + line + returnCode;
}
}
ret += formatMetadata(record.metadata, options);
ret += formatPosition(record.initialPosition, options);
ret += "手数----指手---------消費時間--" + returnCode;
record.forEach((node) => {
if (node.ply !== 0) {
if (!node.isFirstBranch) {
ret += returnCode;
ret += "変化:" + node.ply + "手" + returnCode;
}
ret += String(node.ply).padStart(4, " ") + " ";
if (node.move instanceof move_1.Move) {
const prev = node.prev?.move instanceof move_1.Move ? node.prev.move : undefined;
ret += formatKIFMove(node.move, { prev, padding: true });
}
else if ((0, move_1.isKnownSpecialMove)(node.move)) {
const s = specialMoveToString[node.move.type];
ret += s + " ".repeat(Math.max(12 - s.length * 2, 0));
}
else {
ret += node.move.name + " ".repeat(Math.max(12 - node.move.name.length * 2, 0));
}
const elapsed = (0, time_1.millisecondsToMSS)(node.elapsedMs);
const totalElapsed = (0, time_1.millisecondsToHHMMSS)(node.totalElapsedMs);
ret += ` (${elapsed}/${totalElapsed})`;
if (node.branch) {
// Kifu for Windows にならい、後続の分岐が存在する場合は行末に "+" を付ける。
ret += "+";
}
ret += returnCode;
}
if (node.comment.length !== 0) {
const comment = node.comment.endsWith("\n") ? node.comment.slice(0, -1) : node.comment;
ret += "*" + comment.replaceAll("\n", returnCode + "*") + returnCode;
}
if (node.bookmark.length !== 0) {
ret += "&" + node.bookmark + returnCode;
}
});
return ret;
}
/**
* KI2形式の文字列を出力します。
* @param record
* @param options
*/
function exportKI2(record, options) {
let ret = "";
let moveCountInLine = 0;
let lastMoveLength = 0;
const returnCode = options?.returnCode ? options.returnCode : "\n";
ret += formatMetadata(record.metadata, options);
ret += formatPosition(record.initialPosition, options);
record.forEach((node) => {
if (node.prev) {
if (!node.isFirstBranch) {
if (!ret.endsWith(returnCode)) {
ret += returnCode;
}
ret += returnCode;
ret += "変化:" + node.ply + "手" + returnCode;
}
if (node.move instanceof move_1.Move) {
const pos = position_1.Position.newBySFEN(node.prev.sfen);
if (!pos) {
throw new Error("Invalid SFEN");
}
const str = (0, text_1.formatMove)(pos, node.move, {
lastMove: node.prev?.move instanceof move_1.Move ? node.prev.move : undefined,
compatible: true,
});
if (ret.endsWith(returnCode)) {
moveCountInLine = 0;
}
else {
ret += " ".repeat(Math.max(12 - lastMoveLength * 2, 0));
}
ret += str;
lastMoveLength = str.length;
moveCountInLine++;
if (moveCountInLine >= 6) {
ret += returnCode;
}
}
else {
if (!ret.endsWith(returnCode)) {
ret += returnCode;
}
ret += `まで${node.ply - 1}手で`;
if ((0, move_1.isKnownSpecialMove)(node.move)) {
const [next, last] = node.nextColor === color_1.Color.BLACK ? ["先手", "後手"] : ["後手", "先手"];
switch (node.move.type) {
case move_1.SpecialMoveType.RESIGN:
ret += `${last}の勝ち`;
break;
case move_1.SpecialMoveType.TIMEOUT:
ret += `時間切れにより${last}の勝ち`;
break;
case move_1.SpecialMoveType.ENTERING_OF_KING:
ret += `${next}の入玉勝ち`;
break;
case move_1.SpecialMoveType.FOUL_WIN:
ret += `${next}の反則勝ち`;
break;
case move_1.SpecialMoveType.FOUL_LOSE:
ret += `${next}の反則負け`;
break;
default:
ret += specialMoveToString[node.move.type];
break;
}
}
else {
ret += node.move.name;
}
ret += returnCode;
}
}
if (node.comment.length !== 0) {
if (!ret.endsWith(returnCode)) {
ret += returnCode;
}
const comment = node.comment.endsWith("\n") ? node.comment.slice(0, -1) : node.comment;
ret += "*" + comment.replaceAll("\n", returnCode + "*") + returnCode;
}
if (node.bookmark.length !== 0) {
if (!ret.endsWith(returnCode)) {
ret += returnCode;
}
ret += "&" + node.bookmark + returnCode;
}
});
return ret;
}
function exportBOD(record, options) {
let ret = "";
const returnCode = options?.returnCode || "\n";
ret += formatBOD(record.position, options);
const ply = record.current.ply;
const lastMove = record.current.move instanceof move_1.Move ? record.current.move : undefined;
const lastMoveStr = lastMove ? formatKIFMove(lastMove) : "";
ret += `手数=${ply} ${lastMoveStr} まで` + returnCode;
return ret;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoia2FraW5va2kuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMva2FraW5va2kudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLGtDQUFrQztBQUNsQywrQ0FBK0M7OztBQTRGL0Msc0RBRUM7QUE4Q0Qsc0RBRUM7QUFzWkQsOEJBRUM7QUFNRCw4QkFFQztBQStORCxzQ0FvQkM7QUF3QkQsOEJBNkNDO0FBV0QsOEJBb0ZDO0FBRUQsOEJBU0M7QUE5OEJELDZDQUE4QztBQUM5Qyx5Q0FBeUU7QUFFekUsbUNBQWdDO0FBQ2hDLHFDQU9rQjtBQUVsQixpQ0FPZ0I7QUFDaEIsbUNBQTJDO0FBQzNDLHlDQUE4RTtBQUM5RSxxQ0FNa0I7QUFDbEIscUNBQWtDO0FBQ2xDLGlDQVVnQjtBQUVoQixJQUFZLGtCQUdYO0FBSEQsV0FBWSxrQkFBa0I7SUFDNUIsaUNBQVcsQ0FBQTtJQUNYLGlDQUFXLENBQUE7QUFDYixDQUFDLEVBSFcsa0JBQWtCLGtDQUFsQixrQkFBa0IsUUFHN0I7QUFFRCxNQUFNLGNBQWMsR0FBcUQ7SUFDdkUsRUFBRSxFQUFFLDBCQUFpQixDQUFDLFVBQVU7SUFDaEMsRUFBRSxFQUFFLDBCQUFpQixDQUFDLFVBQVU7SUFDaEMsRUFBRSxFQUFFLDBCQUFpQixDQUFDLFlBQVk7SUFDbEMsRUFBRSxFQUFFLDBCQUFpQixDQUFDLFVBQVU7SUFDaEMsSUFBSSxFQUFFLDBCQUFpQixDQUFDLGNBQWM7SUFDdEMsSUFBSSxFQUFFLDBCQUFpQixDQUFDLFlBQVk7SUFDcEMsR0FBRyxFQUFFLDBCQUFpQixDQUFDLElBQUk7SUFDM0IsRUFBRSxFQUFFLDBCQUFpQixDQUFDLFVBQVU7SUFDaEMsRUFBRSxFQUFFLDBCQUFpQixDQUFDLFFBQVE7SUFDOUIsRUFBRSxFQUFFLDBCQUFpQixDQUFDLEtBQUs7SUFDM0IsSUFBSSxFQUFFLDBCQUFpQixDQUFDLFVBQVU7SUFDbEMsR0FBRyxFQUFFLDBCQUFpQixDQUFDLE9BQU87SUFDOUIsSUFB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