shogiground
Version:
lishogi.org shogi ui
1,311 lines (1,300 loc) • 358 kB
JavaScript
"use strict";
var Shogiground = (() => {
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
default: () => index_default
});
// src/constants.ts
var colors = ["sente", "gote"];
var files = [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16"
];
var ranks = [
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p"
];
var allKeys = Array.prototype.concat(
...ranks.map((r) => files.map((f) => f + r))
);
// src/util.ts
var pos2key = (pos) => allKeys[pos[0] + 16 * pos[1]];
var key2pos = (k) => {
if (k.length > 2) return [k.charCodeAt(1) - 39, k.charCodeAt(2) - 97];
else return [k.charCodeAt(0) - 49, k.charCodeAt(1) - 97];
};
function memo(f) {
let v;
const ret = () => {
if (v === void 0) v = f();
return v;
};
ret.clear = () => {
v = void 0;
};
return ret;
}
function callUserFunction(f, ...args) {
if (f) setTimeout(() => f(...args), 1);
}
var opposite = (c) => c === "sente" ? "gote" : "sente";
var sentePov = (o) => o === "sente";
var distanceSq = (pos1, pos2) => {
const dx = pos1[0] - pos2[0], dy = pos1[1] - pos2[1];
return dx * dx + dy * dy;
};
var samePiece = (p1, p2) => p1.role === p2.role && p1.color === p2.color;
var posToTranslateBase = (pos, dims, asSente, xFactor, yFactor) => [
(asSente ? dims.files - 1 - pos[0] : pos[0]) * xFactor,
(asSente ? pos[1] : dims.ranks - 1 - pos[1]) * yFactor
];
var posToTranslateAbs = (dims, bounds) => {
const xFactor = bounds.width / dims.files, yFactor = bounds.height / dims.ranks;
return (pos, asSente) => posToTranslateBase(pos, dims, asSente, xFactor, yFactor);
};
var posToTranslateRel = (dims) => (pos, asSente) => posToTranslateBase(pos, dims, asSente, 100, 100);
var translateAbs = (el, pos, scale) => {
el.style.transform = `translate(${pos[0]}px,${pos[1]}px) scale(${scale}`;
};
var translateRel = (el, percents, scaleFactor, scale) => {
el.style.transform = `translate(${percents[0] * scaleFactor}%,${percents[1] * scaleFactor}%) scale(${scale || scaleFactor})`;
};
var setDisplay = (el, v) => {
el.style.display = v ? "" : "none";
};
var eventPosition = (e) => {
var _a;
if (e.clientX || e.clientX === 0) return [e.clientX, e.clientY];
if ((_a = e.targetTouches) == null ? void 0 : _a[0]) return [e.targetTouches[0].clientX, e.targetTouches[0].clientY];
return;
};
var isRightButton = (e) => e.buttons === 2 || e.button === 2;
var isMiddleButton = (e) => e.buttons === 4 || e.button === 1;
var createEl = (tagName, className) => {
const el = document.createElement(tagName);
if (className) el.className = className;
return el;
};
function pieceNameOf(piece) {
return `${piece.color} ${piece.role}`;
}
function isPieceNode(el) {
return el.tagName === "PIECE";
}
function isSquareNode(el) {
return el.tagName === "SQ";
}
function computeSquareCenter(key, asSente, dims, bounds) {
const pos = key2pos(key);
if (asSente) {
pos[0] = dims.files - 1 - pos[0];
pos[1] = dims.ranks - 1 - pos[1];
}
return [
bounds.left + bounds.width * pos[0] / dims.files + bounds.width / (dims.files * 2),
bounds.top + bounds.height * (dims.ranks - 1 - pos[1]) / dims.ranks + bounds.height / (dims.ranks * 2)
];
}
function domSquareIndexOfKey(key, asSente, dims) {
const pos = key2pos(key);
let index = dims.files - 1 - pos[0] + pos[1] * dims.files;
if (!asSente) index = dims.files * dims.ranks - 1 - index;
return index;
}
function isInsideRect(rect, pos) {
return rect.left <= pos[0] && rect.top <= pos[1] && rect.left + rect.width > pos[0] && rect.top + rect.height > pos[1];
}
function getKeyAtDomPos(pos, asSente, dims, bounds) {
let file = Math.floor(dims.files * (pos[0] - bounds.left) / bounds.width);
if (asSente) file = dims.files - 1 - file;
let rank = Math.floor(dims.ranks * (pos[1] - bounds.top) / bounds.height);
if (!asSente) rank = dims.ranks - 1 - rank;
return file >= 0 && file < dims.files && rank >= 0 && rank < dims.ranks ? pos2key([file, rank]) : void 0;
}
function getHandPieceAtDomPos(pos, roles, bounds) {
for (const color of colors) {
for (const role of roles) {
const piece = { color, role }, pieceRect = bounds.get(pieceNameOf(piece));
if (pieceRect && isInsideRect(pieceRect, pos)) return piece;
}
}
return;
}
function posOfOutsideEl(left, top, asSente, dims, boardBounds) {
const sqW = boardBounds.width / dims.files, sqH = boardBounds.height / dims.ranks;
if (!sqW || !sqH) return;
let xOff = (left - boardBounds.left) / sqW;
if (asSente) xOff = dims.files - 1 - xOff;
let yOff = (top - boardBounds.top) / sqH;
if (!asSente) yOff = dims.ranks - 1 - yOff;
return [xOff, yOff];
}
// src/hands.ts
function addToHand(s, piece, cnt = 1) {
const hand = s.hands.handMap.get(piece.color), role = (s.hands.roles.includes(piece.role) ? piece.role : s.promotion.unpromotesTo(piece.role)) || piece.role;
if (hand && s.hands.roles.includes(role)) hand.set(role, (hand.get(role) || 0) + cnt);
}
function removeFromHand(s, piece, cnt = 1) {
const hand = s.hands.handMap.get(piece.color), role = (s.hands.roles.includes(piece.role) ? piece.role : s.promotion.unpromotesTo(piece.role)) || piece.role, num = hand == null ? void 0 : hand.get(role);
if (hand && num) hand.set(role, Math.max(num - cnt, 0));
}
function renderHand(s, handEl) {
var _a;
handEl.classList.toggle("promotion", !!s.promotion.current);
let wrapEl = handEl.firstElementChild;
while (wrapEl) {
const pieceEl = wrapEl.firstElementChild, piece = { role: pieceEl.sgRole, color: pieceEl.sgColor }, num = ((_a = s.hands.handMap.get(piece.color)) == null ? void 0 : _a.get(piece.role)) || 0, isSelected = !!s.selectedPiece && samePiece(piece, s.selectedPiece) && !s.droppable.spare;
wrapEl.classList.toggle(
"selected",
(s.activeColor === "both" || s.activeColor === s.turnColor) && isSelected
);
wrapEl.classList.toggle(
"preselected",
s.activeColor !== "both" && s.activeColor !== s.turnColor && isSelected
);
wrapEl.classList.toggle(
"last-dest",
s.highlight.lastDests && !!s.lastPiece && samePiece(piece, s.lastPiece)
);
wrapEl.classList.toggle("drawing", !!s.drawable.piece && samePiece(s.drawable.piece, piece));
wrapEl.classList.toggle(
"current-pre",
!!s.predroppable.current && samePiece(s.predroppable.current.piece, piece)
);
wrapEl.dataset.nb = num.toString();
wrapEl = wrapEl.nextElementSibling;
}
}
// src/board.ts
function toggleOrientation(state) {
state.orientation = opposite(state.orientation);
state.animation.current = state.draggable.current = state.promotion.current = state.hovered = state.selected = state.selectedPiece = void 0;
}
function reset(state) {
unselect(state);
unsetPremove(state);
unsetPredrop(state);
cancelPromotion(state);
state.animation.current = state.draggable.current = state.hovered = void 0;
}
function setPieces(state, pieces) {
for (const [key, piece] of pieces) {
if (piece) state.pieces.set(key, piece);
else state.pieces.delete(key);
}
}
function setChecks(state, checksValue) {
if (Array.isArray(checksValue)) {
state.checks = checksValue;
} else {
if (checksValue === true) checksValue = state.turnColor;
if (checksValue) {
const checks = [];
for (const [k, p] of state.pieces) {
if (state.highlight.checkRoles.includes(p.role) && p.color === checksValue) checks.push(k);
}
state.checks = checks;
} else state.checks = void 0;
}
}
function setPremove(state, orig, dest, prom) {
unsetPredrop(state);
state.premovable.current = { orig, dest, prom };
callUserFunction(state.premovable.events.set, orig, dest, prom);
}
function unsetPremove(state) {
if (state.premovable.current) {
state.premovable.current = void 0;
callUserFunction(state.premovable.events.unset);
}
}
function setPredrop(state, piece, key, prom) {
unsetPremove(state);
state.predroppable.current = { piece, key, prom };
callUserFunction(state.predroppable.events.set, piece, key, prom);
}
function unsetPredrop(state) {
if (state.predroppable.current) {
state.predroppable.current = void 0;
callUserFunction(state.predroppable.events.unset);
}
}
function baseMove(state, orig, dest, prom) {
const origPiece = state.pieces.get(orig), destPiece = state.pieces.get(dest);
if (orig === dest || !origPiece) return false;
const captured = destPiece && destPiece.color !== origPiece.color ? destPiece : void 0, promPiece = prom && promotePiece(state, origPiece);
if (dest === state.selected || orig === state.selected) unselect(state);
state.pieces.set(dest, promPiece || origPiece);
state.pieces.delete(orig);
state.lastDests = [orig, dest];
state.lastPiece = void 0;
state.checks = void 0;
callUserFunction(state.events.move, orig, dest, prom, captured);
callUserFunction(state.events.change);
return captured || true;
}
function baseDrop(state, piece, key, prom) {
var _a;
const pieceCount = ((_a = state.hands.handMap.get(piece.color)) == null ? void 0 : _a.get(piece.role)) || 0;
if (!pieceCount && !state.droppable.spare) return false;
const promPiece = prom && promotePiece(state, piece);
if (key === state.selected || !state.droppable.spare && pieceCount === 1 && state.selectedPiece && samePiece(state.selectedPiece, piece))
unselect(state);
state.pieces.set(key, promPiece || piece);
state.lastDests = [key];
state.lastPiece = piece;
state.checks = void 0;
if (!state.droppable.spare) removeFromHand(state, piece);
callUserFunction(state.events.drop, piece, key, prom);
callUserFunction(state.events.change);
return true;
}
function baseUserMove(state, orig, dest, prom) {
const result = baseMove(state, orig, dest, prom);
if (result) {
state.movable.dests = void 0;
state.droppable.dests = void 0;
state.turnColor = opposite(state.turnColor);
state.animation.current = void 0;
}
return result;
}
function baseUserDrop(state, piece, key, prom) {
const result = baseDrop(state, piece, key, prom);
if (result) {
state.movable.dests = void 0;
state.droppable.dests = void 0;
state.turnColor = opposite(state.turnColor);
state.animation.current = void 0;
}
return result;
}
function userDrop(state, piece, key, prom) {
const realProm = prom || state.promotion.forceDropPromotion(piece, key);
if (canDrop(state, piece, key)) {
const result = baseUserDrop(state, piece, key, realProm);
if (result) {
unselect(state);
callUserFunction(state.droppable.events.after, piece, key, realProm, { premade: false });
return true;
}
} else if (canPredrop(state, piece, key)) {
setPredrop(state, piece, key, realProm);
unselect(state);
return true;
}
unselect(state);
return false;
}
function userMove(state, orig, dest, prom) {
const realProm = prom || state.promotion.forceMovePromotion(orig, dest);
if (canMove(state, orig, dest)) {
const result = baseUserMove(state, orig, dest, realProm);
if (result) {
unselect(state);
const metadata = { premade: false };
if (result !== true) metadata.captured = result;
callUserFunction(state.movable.events.after, orig, dest, realProm, metadata);
return true;
}
} else if (canPremove(state, orig, dest)) {
setPremove(state, orig, dest, realProm);
unselect(state);
return true;
}
unselect(state);
return false;
}
function basePromotionDialog(state, piece, key) {
const promotedPiece = promotePiece(state, piece);
if (state.viewOnly || state.promotion.current || !promotedPiece) return false;
state.promotion.current = { piece, promotedPiece, key, dragged: !!state.draggable.current };
state.hovered = key;
return true;
}
function promotionDialogDrop(state, piece, key) {
if (canDropPromote(state, piece, key) && (canDrop(state, piece, key) || canPredrop(state, piece, key))) {
if (basePromotionDialog(state, piece, key)) {
callUserFunction(state.promotion.events.initiated);
return true;
}
}
return false;
}
function promotionDialogMove(state, orig, dest) {
if (canMovePromote(state, orig, dest) && (canMove(state, orig, dest) || canPremove(state, orig, dest))) {
const piece = state.pieces.get(orig);
if (piece && basePromotionDialog(state, piece, dest)) {
callUserFunction(state.promotion.events.initiated);
return true;
}
}
return false;
}
function promotePiece(s, piece) {
const promRole = s.promotion.promotesTo(piece.role);
return promRole !== void 0 ? { color: piece.color, role: promRole } : void 0;
}
function deletePiece(state, key) {
if (state.pieces.delete(key)) callUserFunction(state.events.change);
}
function selectSquare(state, key, prom, force) {
callUserFunction(state.events.select, key);
if (!state.draggable.enabled && state.selected === key) {
callUserFunction(state.events.unselect, key);
unselect(state);
return;
}
if (state.selectable.enabled || force || state.selectable.forceSpares && state.selectedPiece && state.droppable.spare) {
if (state.selectedPiece && userDrop(state, state.selectedPiece, key, prom)) return;
else if (state.selected && userMove(state, state.selected, key, prom)) return;
}
if ((state.selectable.enabled || state.draggable.enabled || force) && (isMovable(state, key) || isPremovable(state, key))) {
setSelected(state, key);
}
}
function selectPiece(state, piece, spare, force, api) {
callUserFunction(state.events.pieceSelect, piece);
if (state.selectable.addSparesToHand && state.droppable.spare && state.selectedPiece) {
addToHand(state, { role: state.selectedPiece.role, color: piece.color });
callUserFunction(state.events.change);
unselect(state);
} else if (!api && !state.draggable.enabled && state.selectedPiece && samePiece(state.selectedPiece, piece)) {
callUserFunction(state.events.pieceUnselect, piece);
unselect(state);
} else if ((state.selectable.enabled || state.draggable.enabled || force) && (isDroppable(state, piece, !!spare) || isPredroppable(state, piece))) {
setSelectedPiece(state, piece);
state.droppable.spare = !!spare;
} else {
unselect(state);
}
}
function setSelected(state, key) {
unselect(state);
state.selected = key;
setPreDests(state);
}
function setSelectedPiece(state, piece) {
unselect(state);
state.selectedPiece = piece;
setPreDests(state);
}
function setPreDests(state) {
state.premovable.dests = state.predroppable.dests = void 0;
if (state.selected && isPremovable(state, state.selected) && state.premovable.generate)
state.premovable.dests = state.premovable.generate(state.selected, state.pieces);
else if (state.selectedPiece && isPredroppable(state, state.selectedPiece) && state.predroppable.generate)
state.predroppable.dests = state.predroppable.generate(state.selectedPiece, state.pieces);
}
function unselect(state) {
state.selected = state.selectedPiece = state.premovable.dests = state.predroppable.dests = state.promotion.current = void 0;
state.droppable.spare = false;
}
function isMovable(state, orig) {
const piece = state.pieces.get(orig);
return !!piece && (state.activeColor === "both" || state.activeColor === piece.color && state.turnColor === piece.color);
}
function isDroppable(state, piece, spare) {
var _a;
return (spare || !!((_a = state.hands.handMap.get(piece.color)) == null ? void 0 : _a.get(piece.role))) && (state.activeColor === "both" || state.activeColor === piece.color && state.turnColor === piece.color);
}
function canMove(state, orig, dest) {
var _a, _b;
return orig !== dest && isMovable(state, orig) && (state.movable.free || !!((_b = (_a = state.movable.dests) == null ? void 0 : _a.get(orig)) == null ? void 0 : _b.includes(dest)));
}
function canDrop(state, piece, dest) {
var _a, _b;
return isDroppable(state, piece, state.droppable.spare) && (state.droppable.free || state.droppable.spare || !!((_b = (_a = state.droppable.dests) == null ? void 0 : _a.get(pieceNameOf(piece))) == null ? void 0 : _b.includes(dest)));
}
function canMovePromote(state, orig, dest) {
const piece = state.pieces.get(orig);
return !!piece && state.promotion.movePromotionDialog(orig, dest);
}
function canDropPromote(state, piece, key) {
return !state.droppable.spare && state.promotion.dropPromotionDialog(piece, key);
}
function isPremovable(state, orig) {
const piece = state.pieces.get(orig);
return !!piece && state.premovable.enabled && state.activeColor === piece.color && state.turnColor !== piece.color;
}
function isPredroppable(state, piece) {
var _a;
return !!((_a = state.hands.handMap.get(piece.color)) == null ? void 0 : _a.get(piece.role)) && state.predroppable.enabled && state.activeColor === piece.color && state.turnColor !== piece.color;
}
function canPremove(state, orig, dest) {
return orig !== dest && isPremovable(state, orig) && !!state.premovable.generate && state.premovable.generate(orig, state.pieces).includes(dest);
}
function canPredrop(state, piece, dest) {
const destPiece = state.pieces.get(dest);
return isPredroppable(state, piece) && (!destPiece || destPiece.color !== state.activeColor) && !!state.predroppable.generate && state.predroppable.generate(piece, state.pieces).includes(dest);
}
function isDraggable(state, piece) {
return state.draggable.enabled && (state.activeColor === "both" || state.activeColor === piece.color && (state.turnColor === piece.color || state.premovable.enabled));
}
function playPremove(state) {
const move3 = state.premovable.current;
if (!move3) return false;
const orig = move3.orig, dest = move3.dest, prom = move3.prom;
let success = false;
if (canMove(state, orig, dest)) {
const result = baseUserMove(state, orig, dest, prom);
if (result) {
const metadata = { premade: true };
if (result !== true) metadata.captured = result;
callUserFunction(state.movable.events.after, orig, dest, prom, metadata);
success = true;
}
}
unsetPremove(state);
return success;
}
function playPredrop(state) {
const drop = state.predroppable.current;
if (!drop) return false;
const piece = drop.piece, key = drop.key, prom = drop.prom;
let success = false;
if (canDrop(state, piece, key)) {
if (baseUserDrop(state, piece, key, prom)) {
callUserFunction(state.droppable.events.after, piece, key, prom, { premade: true });
success = true;
}
}
unsetPredrop(state);
return success;
}
function cancelMoveOrDrop(state) {
unsetPremove(state);
unsetPredrop(state);
unselect(state);
}
function cancelPromotion(state) {
if (!state.promotion.current) return;
unselect(state);
state.promotion.current = void 0;
state.hovered = void 0;
callUserFunction(state.promotion.events.cancel);
}
function stop(state) {
state.activeColor = state.movable.dests = state.droppable.dests = state.draggable.current = state.animation.current = state.promotion.current = state.hovered = void 0;
cancelMoveOrDrop(state);
}
// src/sfen.ts
function inferDimensions(boardSfen) {
const ranks2 = boardSfen.split("/"), firstFile = ranks2[0].split("");
let filesCnt = 0, cnt = 0;
for (const c of firstFile) {
const nb = c.charCodeAt(0);
if (nb < 58 && nb > 47) cnt = cnt * 10 + nb - 48;
else if (c !== "+") {
filesCnt += cnt + 1;
cnt = 0;
}
}
filesCnt += cnt;
return { files: filesCnt, ranks: ranks2.length };
}
function sfenToBoard(sfen, dims, fromForsyth) {
const sfenParser = fromForsyth || standardFromForsyth, pieces = /* @__PURE__ */ new Map();
let x = dims.files - 1, y = 0;
for (let i = 0; i < sfen.length; i++) {
switch (sfen[i]) {
case " ":
case "_":
return pieces;
case "/":
++y;
if (y > dims.ranks - 1) return pieces;
x = dims.files - 1;
break;
default: {
const nb1 = sfen[i].charCodeAt(0), nb2 = sfen[i + 1] && sfen[i + 1].charCodeAt(0);
if (nb1 < 58 && nb1 > 47) {
if (nb2 && nb2 < 58 && nb2 > 47) {
x -= (nb1 - 48) * 10 + (nb2 - 48);
i++;
} else x -= nb1 - 48;
} else {
const roleStr = sfen[i] === "+" && sfen.length > i + 1 ? "+" + sfen[++i] : sfen[i], role = sfenParser(roleStr);
if (x >= 0 && role) {
const color = roleStr === roleStr.toLowerCase() ? "gote" : "sente";
pieces.set(pos2key([x, y]), {
role,
color
});
}
--x;
}
}
}
}
return pieces;
}
function boardToSfen(pieces, dims, toForsyth) {
const sfenRenderer = toForsyth || standardToForsyth, reversedFiles = files.slice(0, dims.files).reverse();
return ranks.slice(0, dims.ranks).map(
(y) => reversedFiles.map((x) => {
const piece = pieces.get(x + y), forsyth = piece && sfenRenderer(piece.role);
if (forsyth) {
return piece.color === "sente" ? forsyth.toUpperCase() : forsyth.toLowerCase();
} else return "1";
}).join("")
).join("/").replace(/1{2,}/g, (s) => s.length.toString());
}
function sfenToHands(sfen, fromForsyth) {
const sfenParser = fromForsyth || standardFromForsyth, sente = /* @__PURE__ */ new Map(), gote = /* @__PURE__ */ new Map();
let tmpNum = 0, num = 1;
for (let i = 0; i < sfen.length; i++) {
const nb = sfen[i].charCodeAt(0);
if (nb < 58 && nb > 47) {
tmpNum = tmpNum * 10 + nb - 48;
num = tmpNum;
} else {
const roleStr = sfen[i] === "+" && sfen.length > i + 1 ? "+" + sfen[++i] : sfen[i], role = sfenParser(roleStr);
if (role) {
const color = roleStr === roleStr.toLowerCase() ? "gote" : "sente";
if (color === "sente") sente.set(role, (sente.get(role) || 0) + num);
else gote.set(role, (gote.get(role) || 0) + num);
}
tmpNum = 0;
num = 1;
}
}
return /* @__PURE__ */ new Map([
["sente", sente],
["gote", gote]
]);
}
function handsToSfen(hands, roles, toForsyth) {
var _a, _b;
const sfenRenderer = toForsyth || standardToForsyth;
let senteHandStr = "", goteHandStr = "";
for (const role of roles) {
const forsyth = sfenRenderer(role);
if (forsyth) {
const senteCnt = (_a = hands.get("sente")) == null ? void 0 : _a.get(role), goteCnt = (_b = hands.get("gote")) == null ? void 0 : _b.get(role);
if (senteCnt) senteHandStr += senteCnt > 1 ? senteCnt.toString() + forsyth : forsyth;
if (goteCnt) goteHandStr += goteCnt > 1 ? goteCnt.toString() + forsyth : forsyth;
}
}
if (senteHandStr || goteHandStr) return senteHandStr.toUpperCase() + goteHandStr.toLowerCase();
else return "-";
}
function standardFromForsyth(forsyth) {
switch (forsyth.toLowerCase()) {
case "p":
return "pawn";
case "l":
return "lance";
case "n":
return "knight";
case "s":
return "silver";
case "g":
return "gold";
case "b":
return "bishop";
case "r":
return "rook";
case "+p":
return "tokin";
case "+l":
return "promotedlance";
case "+n":
return "promotedknight";
case "+s":
return "promotedsilver";
case "+b":
return "horse";
case "+r":
return "dragon";
case "k":
return "king";
default:
return;
}
}
function standardToForsyth(role) {
switch (role) {
case "pawn":
return "p";
case "lance":
return "l";
case "knight":
return "n";
case "silver":
return "s";
case "gold":
return "g";
case "bishop":
return "b";
case "rook":
return "r";
case "tokin":
return "+p";
case "promotedlance":
return "+l";
case "promotedknight":
return "+n";
case "promotedsilver":
return "+s";
case "horse":
return "+b";
case "dragon":
return "+r";
case "king":
return "k";
default:
return;
}
}
// src/config.ts
function applyAnimation(state, config) {
if (config.animation) {
deepMerge(state.animation, config.animation);
if ((state.animation.duration || 0) < 70) state.animation.enabled = false;
}
}
function configure(state, config) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
if ((_a = config.movable) == null ? void 0 : _a.dests) state.movable.dests = void 0;
if ((_b = config.droppable) == null ? void 0 : _b.dests) state.droppable.dests = void 0;
if ((_c = config.drawable) == null ? void 0 : _c.shapes) state.drawable.shapes = [];
if ((_d = config.drawable) == null ? void 0 : _d.autoShapes) state.drawable.autoShapes = [];
if ((_e = config.drawable) == null ? void 0 : _e.squares) state.drawable.squares = [];
if ((_f = config.hands) == null ? void 0 : _f.roles) state.hands.roles = [];
deepMerge(state, config);
if ((_g = config.sfen) == null ? void 0 : _g.board) {
state.dimensions = inferDimensions(config.sfen.board);
state.pieces = sfenToBoard(config.sfen.board, state.dimensions, state.forsyth.fromForsyth);
state.drawable.shapes = ((_h = config.drawable) == null ? void 0 : _h.shapes) || [];
}
if ((_i = config.sfen) == null ? void 0 : _i.hands) {
state.hands.handMap = sfenToHands(config.sfen.hands, state.forsyth.fromForsyth);
}
if ("checks" in config) setChecks(state, config.checks || false);
if ("lastPiece" in config && !config.lastPiece) state.lastPiece = void 0;
if ("lastDests" in config && !config.lastDests) state.lastDests = void 0;
else if (config.lastDests) state.lastDests = config.lastDests;
setPreDests(state);
applyAnimation(state, config);
}
function deepMerge(base, extend) {
for (const key in extend) {
if (Object.prototype.hasOwnProperty.call(extend, key)) {
if (Object.prototype.hasOwnProperty.call(base, key) && isPlainObject(base[key]) && isPlainObject(extend[key]))
deepMerge(base[key], extend[key]);
else base[key] = extend[key];
}
}
}
function isPlainObject(o) {
if (typeof o !== "object" || o === null) return false;
const proto = Object.getPrototypeOf(o);
return proto === Object.prototype || proto === null;
}
// src/anim.ts
function anim(mutation, state) {
return state.animation.enabled ? animate(mutation, state) : render(mutation, state);
}
function render(mutation, state) {
const result = mutation(state);
state.dom.redraw();
return result;
}
function makePiece(key, piece) {
return {
key,
pos: key2pos(key),
piece
};
}
function closer(piece, pieces) {
return pieces.sort((p1, p2) => {
return distanceSq(piece.pos, p1.pos) - distanceSq(piece.pos, p2.pos);
})[0];
}
function computePlan(prevPieces, prevHands, current) {
const anims = /* @__PURE__ */ new Map(), animedOrigs = [], fadings = /* @__PURE__ */ new Map(), promotions = /* @__PURE__ */ new Map(), missings = [], news = [], prePieces = /* @__PURE__ */ new Map();
for (const [k, p] of prevPieces) {
prePieces.set(k, makePiece(k, p));
}
for (const key of allKeys) {
const curP = current.pieces.get(key), preP = prePieces.get(key);
if (curP) {
if (preP) {
if (!samePiece(curP, preP.piece)) {
missings.push(preP);
news.push(makePiece(key, curP));
}
} else news.push(makePiece(key, curP));
} else if (preP) missings.push(preP);
}
if (current.animation.hands) {
for (const color of colors) {
const curH = current.hands.handMap.get(color), preH = prevHands.get(color);
if (preH && curH) {
for (const [role, n] of preH) {
const piece = { role, color }, curN = curH.get(role) || 0;
if (curN < n) {
const handPieceOffset = current.dom.bounds.hands.pieceBounds().get(pieceNameOf(piece)), bounds = current.dom.bounds.board.bounds(), outPos = handPieceOffset && bounds ? posOfOutsideEl(
handPieceOffset.left,
handPieceOffset.top,
sentePov(current.orientation),
current.dimensions,
bounds
) : void 0;
if (outPos)
missings.push({
pos: outPos,
piece
});
}
}
}
}
}
for (const newP of news) {
const preP = closer(
newP,
missings.filter((p) => {
if (samePiece(newP.piece, p.piece)) return true;
const pRole = current.promotion.promotesTo(p.piece.role), pPiece = pRole && { color: p.piece.color, role: pRole };
const nRole = current.promotion.promotesTo(newP.piece.role), nPiece = nRole && { color: newP.piece.color, role: nRole };
return !!pPiece && samePiece(newP.piece, pPiece) || !!nPiece && samePiece(nPiece, p.piece);
})
);
if (preP) {
const vector = [preP.pos[0] - newP.pos[0], preP.pos[1] - newP.pos[1]];
anims.set(newP.key, vector.concat(vector));
if (preP.key) animedOrigs.push(preP.key);
if (!samePiece(newP.piece, preP.piece) && newP.key) promotions.set(newP.key, preP.piece);
}
}
for (const p of missings) {
if (p.key && !animedOrigs.includes(p.key)) fadings.set(p.key, p.piece);
}
return {
anims,
fadings,
promotions
};
}
function step(state, now) {
const cur = state.animation.current;
if (cur === void 0) {
if (!state.dom.destroyed) state.dom.redrawNow();
return;
}
const rest = 1 - (now - cur.start) * cur.frequency;
if (rest <= 0) {
state.animation.current = void 0;
state.dom.redrawNow();
} else {
const ease = easing(rest);
for (const cfg of cur.plan.anims.values()) {
cfg[2] = cfg[0] * ease;
cfg[3] = cfg[1] * ease;
}
state.dom.redrawNow(true);
requestAnimationFrame((now2 = performance.now()) => step(state, now2));
}
}
function animate(mutation, state) {
var _a;
const prevPieces = new Map(state.pieces), prevHands = /* @__PURE__ */ new Map([
["sente", new Map(state.hands.handMap.get("sente"))],
["gote", new Map(state.hands.handMap.get("gote"))]
]);
const result = mutation(state), plan = computePlan(prevPieces, prevHands, state);
if (plan.anims.size || plan.fadings.size) {
const alreadyRunning = ((_a = state.animation.current) == null ? void 0 : _a.start) !== void 0;
state.animation.current = {
start: performance.now(),
frequency: 1 / Math.max(state.animation.duration, 1),
plan
};
if (!alreadyRunning) step(state, performance.now());
} else {
state.dom.redraw();
}
return result;
}
function easing(t) {
return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
}
// src/shapes.ts
function createSVGElement(tagName) {
return document.createElementNS("http://www.w3.org/2000/svg", tagName);
}
var outsideArrowHash = "outsideArrow";
function renderShapes(state, svg, customSvg, freePieces) {
const d = state.drawable, curD = d.current, cur = (curD == null ? void 0 : curD.dest) ? curD : void 0, outsideArrow = !!curD && !cur, arrowDests = /* @__PURE__ */ new Map(), pieceMap = /* @__PURE__ */ new Map();
const hashBounds = () => {
const bounds = state.dom.bounds.board.bounds();
return bounds && bounds.width.toString() + bounds.height || "";
};
for (const s of d.shapes.concat(d.autoShapes).concat(cur ? [cur] : [])) {
const destName = isPiece(s.dest) ? pieceNameOf(s.dest) : s.dest;
if (!samePieceOrKey(s.dest, s.orig))
arrowDests.set(destName, (arrowDests.get(destName) || 0) + 1);
}
for (const s of d.shapes.concat(cur ? [cur] : []).concat(d.autoShapes)) {
if (s.piece && !isPiece(s.orig)) pieceMap.set(s.orig, s);
}
const pieceShapes = [...pieceMap.values()].map((s) => {
return {
shape: s,
hash: shapeHash(s, arrowDests, false, hashBounds)
};
});
const shapes = d.shapes.concat(d.autoShapes).map((s) => {
return {
shape: s,
hash: shapeHash(s, arrowDests, false, hashBounds)
};
});
if (cur)
shapes.push({
shape: cur,
hash: shapeHash(cur, arrowDests, true, hashBounds),
current: true
});
const fullHash = shapes.map((sc) => sc.hash).join(";") + (outsideArrow ? outsideArrowHash : "");
if (fullHash === state.drawable.prevSvgHash) return;
state.drawable.prevSvgHash = fullHash;
const defsEl = svg.querySelector("defs"), shapesEl = svg.querySelector("g"), customSvgsEl = customSvg.querySelector("g");
syncDefs(shapes, outsideArrow ? curD : void 0, defsEl);
syncShapes(
shapes.filter((s) => !s.shape.customSvg && (!s.shape.piece || s.current)),
shapesEl,
(shape) => renderSVGShape(state, shape, arrowDests),
outsideArrow
);
syncShapes(
shapes.filter((s) => s.shape.customSvg),
customSvgsEl,
(shape) => renderSVGShape(state, shape, arrowDests)
);
syncShapes(pieceShapes, freePieces, (shape) => renderPiece(state, shape));
if (!outsideArrow && curD) curD.arrow = void 0;
if (outsideArrow && !curD.arrow) {
const orig = pieceOrKeyToPos(curD.orig, state);
if (orig) {
const g = setAttributes(createSVGElement("g"), {
class: shapeClass(curD.brush, true, true),
sgHash: outsideArrowHash
}), el = renderArrow(curD.brush, orig, orig, state.squareRatio, true, false);
g.appendChild(el);
curD.arrow = el;
shapesEl.appendChild(g);
}
}
}
function syncDefs(shapes, outsideShape, defsEl) {
const brushes2 = /* @__PURE__ */ new Set();
for (const s of shapes) {
if (!samePieceOrKey(s.shape.dest, s.shape.orig)) brushes2.add(s.shape.brush);
}
if (outsideShape) brushes2.add(outsideShape.brush);
const keysInDom = /* @__PURE__ */ new Set();
let el = defsEl.firstElementChild;
while (el) {
keysInDom.add(el.getAttribute("sgKey"));
el = el.nextElementSibling;
}
for (const key of brushes2) {
const brush = key || "primary";
if (!keysInDom.has(brush)) defsEl.appendChild(renderMarker(brush));
}
}
function syncShapes(shapes, root, renderShape, outsideArrow) {
const hashesInDom = /* @__PURE__ */ new Map(), toRemove = [];
for (const sc of shapes) hashesInDom.set(sc.hash, false);
if (outsideArrow) hashesInDom.set(outsideArrowHash, true);
let el = root.firstElementChild, elHash;
while (el) {
elHash = el.getAttribute("sgHash");
if (hashesInDom.has(elHash)) hashesInDom.set(elHash, true);
else toRemove.push(el);
el = el.nextElementSibling;
}
for (const el2 of toRemove) root.removeChild(el2);
for (const sc of shapes) {
if (!hashesInDom.get(sc.hash)) {
const shapeEl = renderShape(sc);
if (shapeEl) root.appendChild(shapeEl);
}
}
}
function shapeHash({ orig, dest, brush, piece, customSvg, description }, arrowDests, current, boundHash) {
return [
current,
(isPiece(orig) || isPiece(dest)) && boundHash(),
isPiece(orig) ? pieceHash(orig) : orig,
isPiece(dest) ? pieceHash(dest) : dest,
brush,
(arrowDests.get(isPiece(dest) ? pieceNameOf(dest) : dest) || 0) > 1,
piece && pieceHash(piece),
customSvg && customSvgHash(customSvg),
description
].filter((x) => x).join(",");
}
function pieceHash(piece) {
return [piece.color, piece.role, piece.scale].filter((x) => x).join(",");
}
function customSvgHash(s) {
let h = 0;
for (let i = 0; i < s.length; i++) {
h = (h << 5) - h + s.charCodeAt(i) >>> 0;
}
return "custom-" + h.toString();
}
function renderSVGShape(state, { shape, current, hash }, arrowDests) {
const orig = pieceOrKeyToPos(shape.orig, state);
if (!orig) return;
if (shape.customSvg) {
return renderCustomSvg(shape.brush, shape.customSvg, orig, state.squareRatio);
} else {
let el;
const dest = !samePieceOrKey(shape.orig, shape.dest) && pieceOrKeyToPos(shape.dest, state);
if (dest) {
el = renderArrow(
shape.brush,
orig,
dest,
state.squareRatio,
!!current,
(arrowDests.get(isPiece(shape.dest) ? pieceNameOf(shape.dest) : shape.dest) || 0) > 1
);
} else if (samePieceOrKey(shape.dest, shape.orig)) {
let ratio = state.squareRatio;
if (isPiece(shape.orig)) {
const pieceBounds = state.dom.bounds.hands.pieceBounds().get(pieceNameOf(shape.orig)), bounds = state.dom.bounds.board.bounds();
if (pieceBounds && bounds) {
const heightBase = pieceBounds.height / (bounds.height / state.dimensions.ranks);
ratio = [heightBase * state.squareRatio[0], heightBase * state.squareRatio[1]];
}
}
el = renderEllipse(orig, ratio, !!current);
}
if (el) {
const g = setAttributes(createSVGElement("g"), {
class: shapeClass(shape.brush, !!current, false),
sgHash: hash
});
g.appendChild(el);
const descEl = shape.description && renderDescription(state, shape, arrowDests);
if (descEl) g.appendChild(descEl);
return g;
} else return;
}
}
function renderCustomSvg(brush, customSvg, pos, ratio) {
const [x, y] = pos;
const g = setAttributes(createSVGElement("g"), { transform: `translate(${x},${y})` });
const svg = setAttributes(createSVGElement("svg"), {
class: brush,
width: ratio[0],
height: ratio[1],
viewBox: `0 0 ${ratio[0] * 10} ${ratio[1] * 10}`
});
g.appendChild(svg);
svg.innerHTML = customSvg;
return g;
}
function renderEllipse(pos, ratio, current) {
const o = pos, widths = ellipseWidth(ratio);
return setAttributes(createSVGElement("ellipse"), {
"stroke-width": widths[current ? 0 : 1],
fill: "none",
cx: o[0],
cy: o[1],
rx: ratio[0] / 2 - widths[1] / 2,
ry: ratio[1] / 2 - widths[1] / 2
});
}
function renderArrow(brush, orig, dest, ratio, current, shorten) {
const m = arrowMargin(shorten && !current, ratio), a = orig, b = dest, dx = b[0] - a[0], dy = b[1] - a[1], angle = Math.atan2(dy, dx), xo = Math.cos(angle) * m, yo = Math.sin(angle) * m;
return setAttributes(createSVGElement("line"), {
"stroke-width": lineWidth(current, ratio),
"stroke-linecap": "round",
"marker-end": "url(#arrowhead-" + (brush || "primary") + ")",
x1: a[0],
y1: a[1],
x2: b[0] - xo,
y2: b[1] - yo
});
}
function renderPiece(state, { shape }) {
if (!shape.piece || isPiece(shape.orig)) return;
const orig = shape.orig, scale = (shape.piece.scale || 1) * (state.scaleDownPieces ? 0.5 : 1);
const pieceEl = createEl("piece", pieceNameOf(shape.piece));
pieceEl.sgKey = orig;
pieceEl.sgScale = scale;
translateRel(
pieceEl,
posToTranslateRel(state.dimensions)(key2pos(orig), sentePov(state.orientation)),
state.scaleDownPieces ? 0.5 : 1,
scale
);
return pieceEl;
}
function renderDescription(state, shape, arrowDests) {
const orig = pieceOrKeyToPos(shape.orig, state);
if (!orig || !shape.description) return;
const dest = !samePieceOrKey(shape.orig, shape.dest) && pieceOrKeyToPos(shape.dest, state), diff = dest ? [dest[0] - orig[0], dest[1] - orig[1]] : [0, 0], offset = (arrowDests.get(isPiece(shape.dest) ? pieceNameOf(shape.dest) : shape.dest) || 0) > 1 ? 0.3 : 0.15, close = (diff[0] === 0 || Math.abs(diff[0]) === state.squareRatio[0]) && (diff[1] === 0 || Math.abs(diff[1]) === state.squareRatio[1]), ratio = dest ? 0.55 - (close ? offset : 0) : 0, mid = [orig[0] + diff[0] * ratio, orig[1] + diff[1] * ratio], textLength = shape.description.length;
const g = setAttributes(createSVGElement("g"), { class: "description" }), circle = setAttributes(createSVGElement("ellipse"), {
cx: mid[0],
cy: mid[1],
rx: textLength + 1.5,
ry: 2.5
}), text = setAttributes(createSVGElement("text"), {
x: mid[0],
y: mid[1],
"text-anchor": "middle",
"dominant-baseline": "central"
});
g.appendChild(circle);
text.appendChild(document.createTextNode(shape.description));
g.appendChild(text);
return g;
}
function renderMarker(brush) {
const marker = setAttributes(createSVGElement("marker"), {
id: "arrowhead-" + brush,
orient: "auto",
markerWidth: 4,
markerHeight: 8,
refX: 2.05,
refY: 2.01
});
marker.appendChild(
setAttributes(createSVGElement("path"), {
d: "M0,0 V4 L3,2 Z"
})
);
marker.setAttribute("sgKey", brush);
return marker;
}
function setAttributes(el, attrs) {
for (const key in attrs) {
if (Object.prototype.hasOwnProperty.call(attrs, key)) el.setAttribute(key, attrs[key]);
}
return el;
}
function pos2user(pos, color, dims, ratio) {
return color === "sente" ? [(dims.files - 1 - pos[0]) * ratio[0], pos[1] * ratio[1]] : [pos[0] * ratio[0], (dims.ranks - 1 - pos[1]) * ratio[1]];
}
function isPiece(x) {
return typeof x === "object";
}
function samePieceOrKey(kp1, kp2) {
return isPiece(kp1) && isPiece(kp2) && samePiece(kp1, kp2) || kp1 === kp2;
}
function usesBounds(shapes) {
return shapes.some((s) => isPiece(s.orig) || isPiece(s.dest));
}
function shapeClass(brush, current, outside) {
return brush + (current ? " current" : "") + (outside ? " outside" : "");
}
function ratioAverage(ratio) {
return (ratio[0] + ratio[1]) / 2;
}
function ellipseWidth(ratio) {
return [3 / 64 * ratioAverage(ratio), 4 / 64 * ratioAverage(ratio)];
}
function lineWidth(current, ratio) {
return (current ? 8.5 : 10) / 64 * ratioAverage(ratio);
}
function arrowMargin(shorten, ratio) {
return (shorten ? 20 : 10) / 64 * ratioAverage(ratio);
}
function pieceOrKeyToPos(kp, state) {
if (isPiece(kp)) {
const pieceBounds = state.dom.bounds.hands.pieceBounds().get(pieceNameOf(kp)), bounds = state.dom.bounds.board.bounds(), offset = sentePov(state.orientation) ? [0.5, -0.5] : [-0.5, 0.5], pos = pieceBounds && bounds && posOfOutsideEl(
pieceBounds.left + pieceBounds.width / 2,
pieceBounds.top + pieceBounds.height / 2,
sentePov(state.orientation),
state.dimensions,
bounds
);
return pos && pos2user(
[pos[0] + offset[0], pos[1] + offset[1]],
state.orientation,
state.dimensions,
state.squareRatio
);
} else return pos2user(key2pos(kp), state.orientation, state.dimensions, state.squareRatio);
}
// src/draw.ts
var brushes = ["primary", "alternative0", "alternative1", "alternative2"];
function start(state, e) {
if (e.touches && e.touches.length > 1) return;
e.stopPropagation();
e.preventDefault();
if (e.ctrlKey) unselect(state);
else cancelMoveOrDrop(state);
const pos = eventPosition(e), bounds = state.dom.bounds.board.bounds(), orig = pos && bounds && getKeyAtDomPos(pos, sentePov(state.orientation), state.dimensions, bounds), piece = state.drawable.piece;
if (!orig) return;
state.drawable.current = {
orig,
dest: void 0,
pos,
piece,
brush: eventBrush(e, isRightButton(e) || state.drawable.forced)
};
processDraw(state);
}
function startFromHand(state, piece, e) {
if (e.touches && e.touches.length > 1) return;
e.stopPropagation();
e.preventDefault();
if (e.ctrlKey) unselect(state);
else cancelMoveOrDrop(state);
const pos = eventPosition(e);
if (!pos) return;
state.drawable.current = {
orig: piece,
dest: void 0,
pos,
brush: eventBrush(e, isRightButton(e) || state.drawable.forced)
};
processDraw(state);
}
function processDraw(state) {
requestAnimationFrame(() => {
const cur = state.drawable.current, bounds = state.dom.bounds.board.bounds();
if (cur && bounds) {
const dest = getKeyAtDomPos(cur.pos, sentePov(state.orientation), state.dimensions, bounds) || getHandPieceAtDomPos(cur.pos, state.hands.roles, state.dom.bounds.hands.pieceBounds());
if (cur.dest !== dest && !(cur.dest && dest && samePieceOrKey(dest, cur.dest))) {
cur.dest = dest;
state.dom.redrawNow();
}
const outPos = posOfOutsideEl(
cur.pos[0],
cur.pos[1],
sentePov(state.orientation),
state.dimensions,
bounds
);
if (!cur.dest && cur.arrow && outPos) {
const dest2 = pos2user(outPos, state.orientation, state.dimensions, state.squareRatio);
setAttributes(cur.arrow, {
x2: dest2[0] - state.squareRatio[0] / 2,
y2: dest2[1] - state.squareRatio[1] / 2
});
}
processDraw(state);
}
});
}
function move(state, e) {
if (state.drawable.current) state.drawable.current.pos = eventPosition(e);
}
function end(state, _) {
const cur = state.drawable.current;
if (cur) {
addShape(state.drawable, cur);
cancel(state);
}
}
function cancel(state) {
if (state.drawable.current) {
state.drawable.current = void 0;
state.dom.redraw();
}
}
function clear(state) {
const drawableLength = state.drawable.shapes.length;
if (drawableLength || state.drawable.piece) {
state.drawable.shapes = [];
state.drawable.piece = void 0;
state.dom.redraw();
if (drawableLength) onChange(state.drawable);
}
}
function setDrawPiece(state, piece) {
if (state.drawable.piece && samePiece(state.drawable.piece, piece))
state.drawable.piece = void 0;
else state.drawable.piece = piece;
state.dom.redraw();
}
function eventBrush(e, allowFirstModifier) {
var _a;
const modA = allowFirstModifier && (e.shiftKey || e.ctrlKey), modB = e.altKey || e.metaKey || ((_a = e.getModifierState) == null ? void 0 : _a.call(e, "AltGraph"));
return brushes[(modA ? 1 : 0) + (modB ? 2 : 0)];
}
function addShape(drawable, cur) {
if (!cur.dest) return;
const similarShape = (s) => cur.dest && samePieceOrKey(cur.orig, s.orig) && samePieceOrKey(cur.dest, s.dest);
const piece = cur.piece;
cur.piece = void 0;
const similar = drawable.shapes.find(similarShape), removePiece = drawable.shapes.find(
(s) => similarShape(s) && piece && s.piece && samePiece(piece, s.piece)
), diffPiece = drawable.shapes.find(
(s) => similarShape(s) && piece && s.piece && !samePiece(piece, s.piece)
);
if (similar) drawable.shapes = drawable.shapes.filter((s) => !similarShape(s));
if (!isPiece(cur.orig) && piece && !removePiece) {
drawable.shapes.push({ orig: cur.orig, dest: cur.orig, piece, brush: cur.brush });
if (!samePieceOrKey(cur.orig, cur.dest))
drawable.shapes.push({ orig: cur.orig, dest: cur.orig, brush: cur.brush });
}
if (!similar || diffPiece || similar.brush !== cur.brush) drawable.shapes.push(cur);
onChange(drawable);
}
function onChange(drawable) {
if (drawable.onChange) drawable.onChange(drawable.shapes);
}
// src/drag.ts
function start2(s, e) {
var _a;
const bounds = s.dom.bounds.board.bounds(), position = eventPosition(e), orig = bounds && position && getKeyAtDomPos(position, sentePov(s.orientation), s.dimensions, bounds);
if (!orig) return;
const piece = s.pieces.get(orig),