From b8a60aa206f40f36e569438657a33958bf4808a2 Mon Sep 17 00:00:00 2001 From: Alexander Rossmanith Date: Sat, 12 Sep 2026 16:30:35 +0530 Subject: [PATCH] first commit --- background.js | 6 ++ content.js | 163 ++++++++++++++++++++++++++++++++++++++++++++++++ manifest.json | 16 +++++ shikenbisha.txt | 1 + 4 files changed, 186 insertions(+) create mode 100644 background.js create mode 100644 content.js create mode 100644 manifest.json create mode 100644 shikenbisha.txt diff --git a/background.js b/background.js new file mode 100644 index 0000000..49972de --- /dev/null +++ b/background.js @@ -0,0 +1,6 @@ +chrome.action.onClicked.addListener((tab) => { + chrome.scripting.executeScript({ + target: { tabId: tab.id }, + files: ["content.js"] + }); +}); diff --git a/content.js b/content.js new file mode 100644 index 0000000..ddacb86 --- /dev/null +++ b/content.js @@ -0,0 +1,163 @@ +// ## 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}!`); + }); +})(); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..f55034b --- /dev/null +++ b/manifest.json @@ -0,0 +1,16 @@ +{ + "manifest_version": 3, + "name": "ShogiPlayground Plugin", + "version": "1.0", + "description": "Copies all branches' moves from Shogi Playground into the clipboard.", + "permissions": [ + "scripting", + "activeTab" + ], + "background": { + "service_worker": "background.js" + }, + "action": { + "default_title": "Copy All Branches" + } +} diff --git a/shikenbisha.txt b/shikenbisha.txt new file mode 100644 index 0000000..1b9f908 --- /dev/null +++ b/shikenbisha.txt @@ -0,0 +1 @@ +https://www.reddit.com/r/shogi/comments/9ngjw8/book_lines_only_based_on_shikenbisha_joseki/