UNPKG

xiang.js

Version:

A simple Javascript Xiangqi library

558 lines (525 loc) 18.7 kB
const fs = require("fs"); const https = require('https'); var Xiang = function () { // prettier-ignore const X = { "Grids": { "Clear": [ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], }, "FENs": { "Empty": "9/9/9/9/9/9/9/9/9/9 w - - 0 1", "Start": "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1", }, "ChiToEng": { "將": "k", "将": "k", "帥": "k", "帅": "k", "仕": "a", "士": "a", "相": "b", "象": "b", "傌": "n", "馬": "n", "马": "n", "俥": "r", "車": "r", "硨": "r", "车": "r", "炮": "c", "砲": "c", "包": "c", "兵": "p", "卒": "p", "前": "1", "中": "2", "后": "2", "後": "2", "一": "1", "1": "1", "1": "1", "二": "2", "2": "2", "2": "2", "三": "3", "3": "3", "3": "3", "四": "4", "4": "4", "4": "4", "五": "5", "5": "5", "5": "5", "六": "6", "6": "6", "6": "6", "七": "7", "7": "7", "7": "7", "八": "8", "8": "8", "8": "8", "九": "9", "9": "9", "9": "9", "平": '=', "進": '+', "进": '+', "退": '-', }, "EngToChi": { "r": "紅", "b": "黑", "w": "黑", }, "SideDigit": { "r": { // 紅 "k": 16, "a": 17, "b": 18, "e": 18, "n": 19, "h": 19, "r": 20, "c": 21, "p": 22, }, "b": { // 黑 "k": 8, "a": 9, "b": 10, "e": 10, "n": 11, "h": 19, "r": 12, "c": 13, "p": 14, }, }, "SideCol": { "r": [0, 8, 7, 6, 5, 4, 3, 2, 1, 0], "b": [0, 0, 1, 2, 3, 4, 5, 6, 7, 8], }, "PieceDigit": { "k": 8, "a": 9, "b": 10, "e": 10, "n": 11, "h": 19, "r": 12, "c": 13, "p": 14, "K": 16, "A": 17, "B": 18, "E": 18, "N": 19, "H": 19, "R": 20, "C": 21, "P": 22, }, "Relative": { "前": 1, "中": 2, "后": 2, "後": 2, "二": 2, "三": 3, "四": 4, "五": 5, }, "Position": { "一": 1, "1": 1, "1": 1, "二": 2, "2": 2, "2": 2, "三": 3, "3": 3, "3": 3, "四": 4, "4": 4, "4": 4, "五": 5, "5": 5, "5": 5, "六": 6, "6": 6, "6": 6, "七": 7, "7": 7, "7": 7, "八": 8, "8": 8, "8": 8, "九": 9, "9": 9, "9": 9, }, "Direction": { "平": '=', "進": '+', "进": '+', "退": '-', }, "MoveIndexAB": { "a4-5": [168, 183], "a4+5": [200, 183], "a5-4": [183, 200], "a5+4": [183, 168], "a5-6": [183, 198], "a5+6": [183, 166], "a6-5": [166, 183], "a6+5": [198, 183], "b1-3": [171, 201], "b1+3": [171, 137], "b3-1": [137, 171], "b3+1": [201, 171], "b3-5": [137, 167], "b3+5": [201, 167], "b5-3": [167, 201], "b5+3": [167, 137], "b5-7": [167, 197], "b5+7": [167, 133], "b7-5": [133, 167], "b7+5": [197, 167], "b7-9": [133, 163], "b7+9": [197, 163], "b9-7": [163, 197], "b9+7": [163, 133], }, "Space": ' ', "ChiNames": [ '・', '・', '・', '・', '・', '・', '・', '・', '將', '士', '象', '馬', '車', '砲', '卒', '・', // k:8, a:9 ... p:14 '帥', '仕', '相', '傌', '俥', '炮', '兵', '・', // K:16, A:17 ... P:22 ], "EngNames": [ '.', '.', '.', '.', '.', '.', '.', '.', 'k', 'a', 'b', 'n', 'r', 'c', 'p', '.', // k:8, a:9 ... p:14 'K', 'A', 'B', 'N', 'R', 'C', 'P', '.', // K:16, A:17 ... P:22 ], "Asciis": [ '・','-','-','-','-','-','-','-','・', '|','・','・','・','・','・','・','・','|', '|','+','・','・','・','・','・','+','|', '+','・','+','・','+','・','+','・','+', '|','-','-','-','-','-','-','-','|', '|','-','-','-','-','-','-','-','|', '+','・','+','・','+','・','+','・','+', '|','+','・','・','・','・','・','+','|', '|','・','・','・','・','・','・','・','|', '・','-','-','-','-','-','-','-','・', ], }; const Version = "0.1.1"; var xiang = { board: [...X.Grids.Clear], infos: { FEN: X.FENs.Empty }, count: -1, index: -1, moves: [{}], turn: "r", remark: "", result: "", }; function isEmpty(value) { return Object.keys(value).length === 0 && value.constructor === Object; }; function clear() { xiang = { board: [...X.Grids.Clear], infos: { FEN: X.FENs.Empty }, count: -1, index: -1, moves: [{}], turn: "r", remark: "", result: "", }; }; function parse(text) { clear(); let regex = /(?<setup>\[.+\])|(?<note>{[\S\s]*?})|(?<result>\*|\d-\d|\d\/\d-\d\/\d)|(?<moves>.+)/gm; let results = text.matchAll(regex); let turn = "r"; for (let result of results) { if (result.groups.setup !== undefined) { info = result.groups.setup.replace(/\[|\]/g, ""); chunks = info.split(/\s+/); key = chunks.shift(); value = chunks.join(" "); xiang.infos[key] = value.replace(/^"|"$/g, ""); if (key.toUpperCase() === "FEN") { fenToBoard(value); turn = xiang.turn; } } else if (result.groups.note !== undefined) { note = result.groups.note.replace(/\{|\}/g, ""); if (xiang.index < 0) { xiang.remark += xiang.remark.length > 0 ? "\n" : ""; xiang.remark += note; } else { xiang.moves[xiang.index]["note"] = note; } } else if (result.groups.result !== undefined) { xiang.result = result.groups.result; } else if (result.groups.moves !== undefined) { let fours = result.groups.moves .trim() .replace(/(\d+\.?\s+)/g, ""); let split = fours.split(/\s+/g); for (let four of split) { if (four.length == 4) { xiang.index += 1; xiang.moves[xiang.index] = { chi: four, eng: chiToEng(four), turn: turn, }; turn = turn === "b" ? "r" : "b"; } } } } } function open(url) { let data = ''; https.get(url, response => { response.on('data', chunk => { data += chunk; }); response.on('end', () => { parse(data); }) }).on('error', error => { console.log(error.message); }); } function load(filename) { let text = fs.readFileSync(filename, "utf8"); parse(text); } function ascii() { const Digits = "123456789"; const Soujis = "九八七六五四三二一"; const Reset = "\x1b[0m"; const Dimmer = "\x1b[2m"; const Normal = "\x1b[0m"; const Bright = "\x1b[1m"; const Red = "\x1b[31m"; const White = "\x1b[37m"; var header = Digits; var footer = Soujis; var ascii = "\n"; for (let row = 3; row < 3 + 10; row++) { for (let col = 3; col < 3 + 9; col++) { let offset = (row - 3) * 9 + col - 3; value = xiang.board[row * 16 + col]; if (value > 1) { ascii += (value < 16 ? White : Red); ascii += X.ChiNames[value]; ascii += White; } else { ascii += Dimmer; ascii += X.Asciis[offset] ascii += Normal; } } ascii += "\n"; } return Dimmer + header + Normal + ascii + Dimmer + footer + Normal } function digit() { let digit = ""; for (let row = 3; row < 3 + 10; row++) { for (let col = 3; col < 3 + 9; col++) { value = xiang.board[row * 16 + col]; digit += value.toString().padStart(4, " "); } digit += "\n"; } return digit; } function index() { let index = ""; for (let row = 3; row < 3 + 10; row++) { for (let col = 3; col < 3 + 9; col++) { value = row * 16 + col; index += value.toString().padStart(4, " "); } index += "\n"; } return index; } function moves() { return xiang.moves; } function grids() { let grids = []; let index = 0; for (let row = 3; row < 3 + 10; row++) { for (let col = 3; col < 3 + 9; col++) { let value = xiang.board[row * 16 + col]; grids.push({ "index": index, "value": value, "chi": X.ChiNames[value], "eng": X.EngNames[value], }) index += 1; } } return grids; } function fen() { return xiang.infos.FEN; } function turn() { return xiang.turn; } function flip(index) { return 254 - index; } function make(move) { let piece = -1; let from = -1; let to = -1; let fromTo = X.MoveIndexAB[move.eng]; // 仕象 if (fromTo) { from = (move.turn === "r") ? fromTo[0] : flip(fromTo[0]); to = (move.turn === "r") ? fromTo[1] : flip(fromTo[1]); piece = xiang.board[from]; } else { let fromRow = -1; let fromCol = -1; let toRow = -1; let toCol = -1; let index = -1; if (X.Relative[move.chi[0]]) { // 前中後二三四五 piece = move.eng[1]; let match = X.Relative[move.chi[0]]; let count = 0; let number = X.SideDigit[move.turn][piece]; if (move.turn === "r") { Red: for (fromCol = 0; fromCol < 10; fromCol++) { for (fromRow = 0; fromRow < 10; fromRow++) { // from top to bottom let offset = offsetFrom(fromRow, fromCol); if (xiang.board[offset] === number) { count += 1; // found one if (count === match) { from = offset; index = 9 - fromCol; break Red; } } } } } else { Black: for (fromCol = 0; fromCol < 10; fromCol++) { for (fromRow = 9; fromRow <= 0; fromRow--) { // from bottom to top let offset = offsetFrom(fromRow, fromCol); if (xiang.board[offset] === number) { count += 1; // found one if (count === match) { from = offset; index = fromCol + 1; break Black; } } } } } } else { piece = move.eng[0]; index = X.Position[move.chi[1]]; let number = X.SideDigit[move.turn][piece]; fromCol = X.SideCol[move.turn][index]; for (fromRow = 0; fromRow < 10; fromRow++) { let offset = offsetFrom(fromRow, fromCol); if (xiang.board[offset] === number) { from = offsetFrom(fromRow, fromCol); break; } } } let direction = move.eng[2]; let value = X.Position[move.chi[3]]; if (piece === "n") { let diff = (Math.abs(index - value) === 1) ? 2 : 1; if (direction === "+") { toRow = fromRow + ((move.turn === "r") ? -diff : diff); } else { toRow = fromRow - ((move.turn === "r") ? -diff : diff); } toCol = (move.turn === "r") ? 9 - value : value - 1; } else { if (direction == '=') { // 平 toRow = fromRow; toCol = ((move.turn === "r") ? 9 - value : value - 1); } else if (direction == '+') { // 進 toRow = fromRow += ((move.turn === "r") ? -value : value); toCol = fromCol; } else if (direction == '-') { // 退 toRow = fromRow -= ((move.turn === "r") ? -value : value); toCol = fromCol; } } to = offsetFrom(toRow, toCol); } if (from !== -1 && to !== -1) { xiang.board[to] = xiang.board[from]; xiang.board[from] = 1; } } function next() { if (xiang.count < xiang.index) { xiang.count += 1; make(xiang.moves[xiang.count]); } } function prev() { if (xiang.count >= 0) { xiang.count -= 1; } } function isInGrid(row, col) { return row >= 0 && row < 10 && col >= 0 && col < 9; } function offsetFrom(row, col) { return (row + 3) * 16 + 3 + col; } function chiToEng(chi) { let eng = chi.split('').map((one) => X.ChiToEng[one]).join(''); return eng; } function fenToBoard(fen) { let parts = fen.split(" "); let row = 0; let col = 0; if (parts.length > 0) { let lines = parts[0].split("/"); if (lines.length === 10) { for (let line of lines) { col = 0; let chars = line.split(""); for (let one of chars) { if (one.match(/\d/)) { col += Number(one); } else { let offset = offsetFrom(row, col); let digit = X.PieceDigit[one]; if (digit > 0) { xiang.board[offset] = digit; col += 1; } } } row += 1; } } } if (parts.length > 1) { xiang.turn = parts[1] === "b" ? "b" : "r"; } } return { version: function () { return Version; }, start: function () { fenToBoard(X.FENs.Start); }, empty: function () { fenToBoard(X.FENs.Empty); }, open: function (url) { return open(url); }, load: function (filename) { return load(filename); }, moves: function () { return moves(); }, grids: function () { return grids(); }, fen: function () { return fen(); }, turn: function () { return turn(); }, info: function () { return { fen: xiang.infos.FEN, count: xiang.count, index: xiang.index, chi: xiang.count >= 0 ? xiang.moves[xiang.count].chi : "", eng: xiang.count >= 0 ? xiang.moves[xiang.count].eng : "", turn: xiang.count >= 0 ? xiang.moves[xiang.count].turn : "", note: xiang.count >= 0 ? xiang.moves[xiang.count].note : "", }; }, next: function () { return next(); }, prev: function () { return prev(); }, dump: function () { return xiang; }, ascii: function () { return ascii(); }, digit: function () { return digit(); }, index: function () { return index(); }, }; }; if (typeof exports !== "undefined") exports.Xiang = Xiang; if (typeof define !== "undefined") define(function () { return Xiang; });