164 lines
4.2 KiB
JavaScript
164 lines
4.2 KiB
JavaScript
// ## kif.js
|
|
|
|
// KIF is the most popular file format used to represent full shogi games,
|
|
// studies of games, tsume problems and other types of puzzles.
|
|
// For more information, see https://lishogi.org/page/kif and
|
|
// https://deepwiki.com/gunyarakun/python-shogi/3.1-kif-format
|
|
|
|
const pieceMap = {
|
|
P: "歩",
|
|
L: "香",
|
|
N: "桂",
|
|
S: "銀",
|
|
G: "金",
|
|
B: "角",
|
|
R: "飛",
|
|
K: "玉",
|
|
"+P": "と",
|
|
"+L": "成香",
|
|
"+N": "成桂",
|
|
"+S": "成銀",
|
|
"+B": "馬",
|
|
"+R": "竜"
|
|
};
|
|
|
|
const japaneseNumberMap = {
|
|
1: "一",
|
|
2: "二",
|
|
3: "三",
|
|
4: "四",
|
|
5: "五",
|
|
6: "六",
|
|
7: "七",
|
|
8: "八",
|
|
9: "九"
|
|
};
|
|
|
|
function kifCoord(square) {
|
|
const file = square[0];
|
|
const rank = square[1];
|
|
return `${file}${japaneseNumberMap[rank]}`;
|
|
}
|
|
|
|
// A struct to hold the source or destination square of a move
|
|
// file and rank are numbers from 1-9
|
|
const Square = (file, rank) => ({ file, rank });
|
|
|
|
function parseSquare(square) {
|
|
// square is a string like "7g"
|
|
// first character is the file (1-9), second character is the rank (a-i)
|
|
const file = parseInt(square[0], 10);
|
|
const rank = "abcdefghi".indexOf(square[1]) + 1;
|
|
return Square(file, rank);
|
|
}
|
|
|
|
function toKIF(line, prevDest) {
|
|
// move number
|
|
const move = line.replace(/^\d+:\s*/, "").trim();
|
|
|
|
// gote/sente player indicator
|
|
const player = move[0] === "☗" ? "▲" : "△";
|
|
var body = move.slice(1);
|
|
|
|
// piece
|
|
if (move[0] === "+") { // promoted piece
|
|
// first two characters are the piece, then the destination
|
|
const piece = body.slice(0, 2);
|
|
body = body.slice(2);
|
|
}
|
|
else {
|
|
// first character is the piece, then the destination
|
|
const piece = body[0];
|
|
body = body.slice(1);
|
|
}
|
|
|
|
// source square (optional)
|
|
if (body[0] >= "1" && body[0] <= "9") {
|
|
const sourceSquare = body.slice(0, 2);
|
|
body = body.slice(2);
|
|
}
|
|
|
|
// move type
|
|
moveType = body[0]; // either '-' for a normal move, 'x' for a capture, or '*' for a drop
|
|
body = body.slice(1);
|
|
|
|
// destination square
|
|
destSquare = body.slice(0, 2);
|
|
body = body.slice(2);
|
|
|
|
if (body.length > 0) {
|
|
if (body[0] === "+" || body[0] === "=") {
|
|
const promoFlag = body[0];
|
|
body = body.slice(1);
|
|
}
|
|
}
|
|
|
|
// to KIF format
|
|
var kifMove = `${player}${kifCoord(destSquare)}${pieceMap[piece]}${moveType === "*" ? "打" : ""}`;
|
|
if (promoFlag) {
|
|
kifMove += promoFlag === "+" ? "成" : "不成";
|
|
}
|
|
return kifMove;
|
|
}
|
|
|
|
// ## content.js
|
|
|
|
// This module provides functions to convert the shogiplayground
|
|
// (https://play.mogproject.com/) format to KIF.
|
|
|
|
(() => {
|
|
const sidebar = document.querySelector("div.sidebar-left-content");
|
|
if (!sidebar) {
|
|
alert("Sidebar not found!");
|
|
return;
|
|
}
|
|
|
|
const branchSelect = sidebar.querySelector("select");
|
|
const copyButton = sidebar.querySelector("button");
|
|
if (!branchSelect || !copyButton) {
|
|
alert("Branch selector or copy button not found!");
|
|
return;
|
|
}
|
|
|
|
const results = [];
|
|
|
|
const collectMoves = async () => {
|
|
for (let i = 0; i < branchSelect.options.length; i++) {
|
|
branchSelect.selectedIndex = i;
|
|
branchSelect.dispatchEvent(new Event("change"));
|
|
await new Promise(r => setTimeout(r, 200));
|
|
|
|
const text = copyButton.getAttribute("data-clipboard-text") || "";
|
|
let prevDest = null;
|
|
const lines = text.split("\n").map(l => {
|
|
const kif = toKIF(l, prevDest);
|
|
if (kif) {
|
|
const match = kif.match(/^[▲△](同|\d[一二三四五六七八九])/);
|
|
if (match) prevDest = match[1] === "同" ? prevDest : match[0].slice(1);
|
|
}
|
|
return kif;
|
|
}).filter(Boolean);
|
|
|
|
const label = branchSelect.options[i].textContent.trim();
|
|
results.push(`${label}:\n${lines.join("\n")}`);
|
|
}
|
|
};
|
|
|
|
const default_filename = "shogiplayground-plugin.kif";
|
|
|
|
collectMoves().then(() => {
|
|
const finalText = results.join("\n\n");
|
|
const blob = new Blob([finalText], { type: "text/plain" });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = default_filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
|
|
alert(`All branches exported to ${default_filename}!`);
|
|
});
|
|
})();
|