@hackersheet/next-document-content-kifu 0.1.0-alpha.1 → 0.1.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/kifu/kifu.d.ts +1 -1
- package/dist/cjs/components/kifu/kifu.js +5 -21
- package/dist/cjs/components/kifu-to/kifu-to.js +1 -1
- package/dist/cjs/components/shogi-player/button.d.ts +8 -0
- package/dist/cjs/components/shogi-player/button.js +44 -0
- package/dist/cjs/components/shogi-player/index.d.ts +2 -0
- package/dist/cjs/components/shogi-player/index.js +38 -0
- package/dist/cjs/components/shogi-player/moves-area.d.ts +11 -0
- package/dist/cjs/components/shogi-player/moves-area.js +78 -0
- package/dist/cjs/components/shogi-player/shogi-board-canvas.d.ts +17 -0
- package/dist/cjs/components/shogi-player/shogi-board-canvas.js +180 -0
- package/dist/cjs/components/shogi-player/shogi-hands-canvas.d.ts +16 -0
- package/dist/cjs/components/shogi-player/shogi-hands-canvas.js +140 -0
- package/dist/cjs/components/shogi-player/shogi-player.d.ts +10 -0
- package/dist/cjs/components/shogi-player/shogi-player.js +112 -0
- package/dist/cjs/index.js +3 -3
- package/dist/esm/components/kifu/kifu.d.mts +1 -1
- package/dist/esm/components/kifu/kifu.mjs +5 -21
- package/dist/esm/components/kifu-to/kifu-to.mjs +1 -1
- package/dist/esm/components/shogi-player/button.d.mts +8 -0
- package/dist/esm/components/shogi-player/button.mjs +14 -0
- package/dist/esm/components/shogi-player/index.d.mts +2 -0
- package/dist/esm/components/shogi-player/index.mjs +4 -0
- package/dist/esm/components/shogi-player/moves-area.d.mts +11 -0
- package/dist/esm/components/shogi-player/moves-area.mjs +44 -0
- package/dist/esm/components/shogi-player/shogi-board-canvas.d.mts +17 -0
- package/dist/esm/components/shogi-player/shogi-board-canvas.mjs +150 -0
- package/dist/esm/components/shogi-player/shogi-hands-canvas.d.mts +16 -0
- package/dist/esm/components/shogi-player/shogi-hands-canvas.mjs +110 -0
- package/dist/esm/components/shogi-player/shogi-player.d.mts +10 -0
- package/dist/esm/components/shogi-player/shogi-player.mjs +82 -0
- package/package.json +13 -13
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var shogi_player_exports = {};
|
|
31
|
+
__export(shogi_player_exports, {
|
|
32
|
+
default: () => ShogiPlayer
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(shogi_player_exports);
|
|
35
|
+
var import_json_kifu_format = require("json-kifu-format");
|
|
36
|
+
var import_react = __toESM(require("react"));
|
|
37
|
+
var import_button = __toESM(require("./button"));
|
|
38
|
+
var import_moves_area = require("./moves-area");
|
|
39
|
+
var import_shogi_board_canvas = __toESM(require("./shogi-board-canvas"));
|
|
40
|
+
var import_shogi_hands_canvas = __toESM(require("./shogi-hands-canvas"));
|
|
41
|
+
function ShogiPlayer(props) {
|
|
42
|
+
const [player] = (0, import_react.useState)(import_json_kifu_format.JKFPlayer.parse(props.kifuText.trim()));
|
|
43
|
+
const [pieces, setPieces] = (0, import_react.useState)(player.shogi.board);
|
|
44
|
+
const [hands, setHands] = (0, import_react.useState)(player.shogi.hands);
|
|
45
|
+
const [moves, setMoves] = (0, import_react.useState)(player.kifu.moves);
|
|
46
|
+
const [tesuu, setTesuu] = (0, import_react.useState)(player.tesuu);
|
|
47
|
+
const [currentMove, setCurrentMove] = (0, import_react.useState)(player.getMove());
|
|
48
|
+
const [isSente, setIsSente] = (0, import_react.useState)(true);
|
|
49
|
+
const [maxTesuu, setMaxTesuu] = (0, import_react.useState)(player.getMaxTesuu());
|
|
50
|
+
const [comments, setComments] = (0, import_react.useState)(player.getComments());
|
|
51
|
+
const size = props.size ? props.size : 360;
|
|
52
|
+
const updateState = () => {
|
|
53
|
+
setPieces([...player.shogi.board]);
|
|
54
|
+
setHands([...player.shogi.hands]);
|
|
55
|
+
setMoves([...player.kifu.moves]);
|
|
56
|
+
setCurrentMove(player.getMove());
|
|
57
|
+
setMaxTesuu(player.getMaxTesuu());
|
|
58
|
+
setComments(player.getComments());
|
|
59
|
+
setTesuu(player.tesuu);
|
|
60
|
+
};
|
|
61
|
+
const handleForward = () => {
|
|
62
|
+
player.forward();
|
|
63
|
+
updateState();
|
|
64
|
+
};
|
|
65
|
+
const handleBackward = () => {
|
|
66
|
+
player.backward();
|
|
67
|
+
updateState();
|
|
68
|
+
};
|
|
69
|
+
const handleGoto = (tesuu2) => {
|
|
70
|
+
player.goto(tesuu2);
|
|
71
|
+
updateState();
|
|
72
|
+
};
|
|
73
|
+
const handeleToggle = () => {
|
|
74
|
+
setIsSente(!isSente);
|
|
75
|
+
updateState();
|
|
76
|
+
};
|
|
77
|
+
const handleKeydown = (0, import_react.useCallback)(
|
|
78
|
+
(event) => {
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
event.stopPropagation();
|
|
81
|
+
switch (event.key) {
|
|
82
|
+
case "Up":
|
|
83
|
+
case "ArrowUp":
|
|
84
|
+
handleGoto(0);
|
|
85
|
+
break;
|
|
86
|
+
case "Down":
|
|
87
|
+
case "ArrowDown":
|
|
88
|
+
handleGoto(maxTesuu);
|
|
89
|
+
break;
|
|
90
|
+
case "Left":
|
|
91
|
+
case "ArrowLeft":
|
|
92
|
+
handleBackward();
|
|
93
|
+
break;
|
|
94
|
+
case " ":
|
|
95
|
+
case "Right":
|
|
96
|
+
case "ArrowRight":
|
|
97
|
+
handleForward();
|
|
98
|
+
break;
|
|
99
|
+
case "r":
|
|
100
|
+
handeleToggle();
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
[maxTesuu, isSente]
|
|
105
|
+
);
|
|
106
|
+
(0, import_react.useEffect)(() => {
|
|
107
|
+
if (props.tesuu !== void 0) {
|
|
108
|
+
handleGoto(props.tesuu);
|
|
109
|
+
}
|
|
110
|
+
}, [props.tesuu]);
|
|
111
|
+
return /* @__PURE__ */ import_react.default.createElement("div", { className: "flex w-fit", tabIndex: 1, onKeyDown: handleKeydown }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-[#f9d27a] text-black text-xs text-right p-1" }, isSente ? "\u2616 " + player.kifu.header["\u5F8C\u624B"] : "\u2617 " + player.kifu.header["\u5148\u624B"]), /* @__PURE__ */ import_react.default.createElement(import_shogi_hands_canvas.default, { size, hands, isSente, isTop: true }), /* @__PURE__ */ import_react.default.createElement(import_shogi_board_canvas.default, { size, pieces, isSente, currentMove }), /* @__PURE__ */ import_react.default.createElement(import_shogi_hands_canvas.default, { size, hands, isSente, isTop: false }), /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-[#f9d27a] text-black text-xs p-1" }, isSente ? "\u2617 " + player.kifu.header["\u5148\u624B"] : "\u2616 " + player.kifu.header["\u5F8C\u624B"])), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col w-fit bg-[#f9d27a] p-4 gap-4" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex-1 relative w-full" }, /* @__PURE__ */ import_react.default.createElement(import_moves_area.MovesArea, { moves, tesuu, onTesuuChange: handleGoto })), /* @__PURE__ */ import_react.default.createElement("div", { className: "text-black border-black border-2 p-1 text-xs max-h-40 w-0 min-w-full overflow-auto" }, comments.map((comment, index) => /* @__PURE__ */ import_react.default.createElement("div", { key: index }, comment)), comments.length === 0 && tesuu !== 0 && /* @__PURE__ */ import_react.default.createElement("div", null, "\xA0"), tesuu === 0 && Object.entries(player.kifu.header).map(([key, value], i) => /* @__PURE__ */ import_react.default.createElement("div", { key: i, className: "whitespace-nowrap" }, key, ": ", value))), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: () => handleGoto(0) }, "\u6700\u521D"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handleBackward }, "\u524D"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handleForward }, "\u6B21"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: () => handleGoto(maxTesuu) }, "\u6700\u5F8C"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handeleToggle }, "\u53CD\u8EE2"))));
|
|
112
|
+
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -13,9 +13,9 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
13
13
|
};
|
|
14
14
|
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
15
15
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
16
|
-
var
|
|
17
|
-
module.exports = __toCommonJS(
|
|
18
|
-
__reExport(
|
|
16
|
+
var index_exports = {};
|
|
17
|
+
module.exports = __toCommonJS(index_exports);
|
|
18
|
+
__reExport(index_exports, require("./components"), module.exports);
|
|
19
19
|
// Annotate the CommonJS export names for ESM import in node:
|
|
20
20
|
0 && (module.exports = {
|
|
21
21
|
...require("./components")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KifuComponentProps } from '@hackersheet/react-document-content';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
|
-
declare function Kifu({ code, language }: KifuComponentProps): React.JSX.Element
|
|
4
|
+
declare function Kifu({ code, language }: KifuComponentProps): React.JSX.Element;
|
|
5
5
|
|
|
6
6
|
export { Kifu as default };
|
|
@@ -1,36 +1,20 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { KifuLite, KifuStore } from "kifu-for-js";
|
|
3
2
|
import { useSearchParams } from "next/navigation";
|
|
4
3
|
import React, { useEffect, useState } from "react";
|
|
4
|
+
import { ShogiPlayer } from "../shogi-player";
|
|
5
5
|
function Kifu({ code, language }) {
|
|
6
|
+
const [ply, setPly] = useState(0);
|
|
6
7
|
const [, filename] = language.split(":");
|
|
7
8
|
const searchParams = useSearchParams();
|
|
8
9
|
const id = filename ? `user-content-${filename}` : void 0;
|
|
9
|
-
const [kifuStore] = useState(() => new KifuStore({ kifu: code }));
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
initKifuUserSettings();
|
|
12
|
-
}, []);
|
|
13
10
|
useEffect(() => {
|
|
14
11
|
const newPly = Number(searchParams.get("ply") ?? 0);
|
|
15
12
|
const hash = typeof window !== "undefined" ? window.location.hash.replace(/^#!?/, "") : "";
|
|
16
13
|
if (hash === id) {
|
|
17
|
-
|
|
14
|
+
setPly(newPly);
|
|
18
15
|
}
|
|
19
|
-
}, [searchParams,
|
|
20
|
-
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
return /* @__PURE__ */ React.createElement("div", { className: "my-4 flex justify-center", id }, /* @__PURE__ */ React.createElement(KifuLite, { style: { width: "100%" }, kifuStore }));
|
|
24
|
-
}
|
|
25
|
-
function initKifuUserSettings() {
|
|
26
|
-
const LOCALSTORAGE_KEY = "kifuforjs";
|
|
27
|
-
const defaultSettings = {
|
|
28
|
-
hapticFeedback: false
|
|
29
|
-
};
|
|
30
|
-
const settings = localStorage.getItem(LOCALSTORAGE_KEY);
|
|
31
|
-
if (!settings) {
|
|
32
|
-
localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(defaultSettings));
|
|
33
|
-
}
|
|
16
|
+
}, [searchParams, id]);
|
|
17
|
+
return /* @__PURE__ */ React.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ React.createElement(ShogiPlayer, { kifuText: code, tesuu: ply, size: 320 }));
|
|
34
18
|
}
|
|
35
19
|
export {
|
|
36
20
|
Kifu as default
|
|
@@ -4,7 +4,7 @@ function KifuTo({ id, ply, label: defaultLabel }) {
|
|
|
4
4
|
const fullId = `user-content-${id}`;
|
|
5
5
|
const href = `?ply=${ply}#${fullId}`;
|
|
6
6
|
const label = defaultLabel ?? `${ply}\u624B\u76EE`;
|
|
7
|
-
return /* @__PURE__ */ React.createElement(Link, { href }, label);
|
|
7
|
+
return /* @__PURE__ */ React.createElement(Link, { href, prefetch: false }, label);
|
|
8
8
|
}
|
|
9
9
|
export {
|
|
10
10
|
KifuTo as default
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React, { MouseEventHandler, PropsWithChildren } from 'react';
|
|
2
|
+
|
|
3
|
+
type ButtonProps = {
|
|
4
|
+
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
5
|
+
} & PropsWithChildren;
|
|
6
|
+
declare function Button({ children, onClick }: ButtonProps): React.JSX.Element;
|
|
7
|
+
|
|
8
|
+
export { type ButtonProps, Button as default };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
function Button({ children, onClick }) {
|
|
3
|
+
return /* @__PURE__ */ React.createElement(
|
|
4
|
+
"button",
|
|
5
|
+
{
|
|
6
|
+
className: "border-2 text-xs text-black p-2 border-black rounded-lg hover:bg-amber-100 cursor-pointer",
|
|
7
|
+
onClick
|
|
8
|
+
},
|
|
9
|
+
children
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
Button as default
|
|
14
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IMoveFormat } from 'json-kifu-format/dist/src/Formats';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
type MovesAreaProps = {
|
|
5
|
+
moves: IMoveFormat[];
|
|
6
|
+
tesuu: number;
|
|
7
|
+
onTesuuChange?: (tesuu: number) => void;
|
|
8
|
+
};
|
|
9
|
+
declare function MovesArea(props: MovesAreaProps): React.JSX.Element;
|
|
10
|
+
|
|
11
|
+
export { MovesArea, type MovesAreaProps };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { JKFPlayer } from "json-kifu-format";
|
|
3
|
+
import React, { Fragment, useEffect, useRef } from "react";
|
|
4
|
+
function MovesArea(props) {
|
|
5
|
+
const moves = props.moves;
|
|
6
|
+
const scrollRef = useRef(null);
|
|
7
|
+
const containerRef = useRef(null);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (scrollRef.current && containerRef.current) {
|
|
10
|
+
const containerRect = containerRef.current.getBoundingClientRect();
|
|
11
|
+
const scrollRect = scrollRef.current.getBoundingClientRect();
|
|
12
|
+
const offset = scrollRect.top - containerRect.top + containerRef.current.scrollTop - 72;
|
|
13
|
+
containerRef.current.scrollTo({
|
|
14
|
+
top: offset,
|
|
15
|
+
behavior: "auto"
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}, [props.tesuu]);
|
|
19
|
+
const current = 0 === props.tesuu ? " bg-amber-600" : "";
|
|
20
|
+
return /* @__PURE__ */ React.createElement("div", { className: "absolute overflow-y-auto h-full w-full border-2 border-black text-black", ref: containerRef }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-0 text-xs" }, /* @__PURE__ */ React.createElement(
|
|
21
|
+
"div",
|
|
22
|
+
{
|
|
23
|
+
onClick: () => props.onTesuuChange && props.onTesuuChange(0),
|
|
24
|
+
className: "col-span-500 grid grid-cols-subgrid gap-2 py-1 px-2 cursor-pointer hover:bg-amber-100" + current
|
|
25
|
+
},
|
|
26
|
+
/* @__PURE__ */ React.createElement("div", null, 0 === props.tesuu && /* @__PURE__ */ React.createElement("div", { ref: scrollRef })),
|
|
27
|
+
/* @__PURE__ */ React.createElement("div", null, "\u958B\u59CB\u5C40\u9762")
|
|
28
|
+
), moves.map((move, index) => {
|
|
29
|
+
if (index === 0) return;
|
|
30
|
+
const current2 = index === props.tesuu ? " bg-amber-600" : "";
|
|
31
|
+
return /* @__PURE__ */ React.createElement(Fragment, { key: index }, /* @__PURE__ */ React.createElement(
|
|
32
|
+
"div",
|
|
33
|
+
{
|
|
34
|
+
className: "col-span-500 grid grid-cols-subgrid border-black gap-2 border-t py-1 px-2 cursor-pointer hover:bg-amber-100" + current2,
|
|
35
|
+
onClick: () => props.onTesuuChange && props.onTesuuChange(index)
|
|
36
|
+
},
|
|
37
|
+
/* @__PURE__ */ React.createElement("div", { className: "flex" }, index === props.tesuu && /* @__PURE__ */ React.createElement("div", { ref: scrollRef }), /* @__PURE__ */ React.createElement("div", { className: "tabular-nums text-right flex-auto" }, index)),
|
|
38
|
+
/* @__PURE__ */ React.createElement("div", null, JKFPlayer.moveToReadableKifu(move))
|
|
39
|
+
));
|
|
40
|
+
})));
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
MovesArea
|
|
44
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IMoveMoveFormat } from 'json-kifu-format/dist/src/Formats';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Piece } from 'shogi.js';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
size?: number;
|
|
7
|
+
boardColor?: string;
|
|
8
|
+
lineColor?: string;
|
|
9
|
+
fontFamily?: string;
|
|
10
|
+
fontSizeRatio?: number;
|
|
11
|
+
pieces: Piece[][];
|
|
12
|
+
isSente?: boolean;
|
|
13
|
+
currentMove?: IMoveMoveFormat;
|
|
14
|
+
};
|
|
15
|
+
declare const ShogiBoardCanvas: React.FC<Props>;
|
|
16
|
+
|
|
17
|
+
export { ShogiBoardCanvas as default };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { JKFPlayer } from "json-kifu-format";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Color } from "shogi.js";
|
|
5
|
+
const getCanvasDimensions = (size) => {
|
|
6
|
+
const dpr = window.devicePixelRatio || 1;
|
|
7
|
+
return { width: size * dpr, height: size * dpr, dpr };
|
|
8
|
+
};
|
|
9
|
+
const getBoardLayout = (size) => {
|
|
10
|
+
const margin = size * 0.06;
|
|
11
|
+
const boardSize = size - margin * 2;
|
|
12
|
+
const cell = boardSize / 9;
|
|
13
|
+
return { margin, boardSize, cell };
|
|
14
|
+
};
|
|
15
|
+
const drawBackground = (ctx, size, boardColor) => {
|
|
16
|
+
ctx.fillStyle = boardColor;
|
|
17
|
+
ctx.fillRect(0, 0, size, size);
|
|
18
|
+
};
|
|
19
|
+
const drawBoard = (ctx, margin, boardSize, lineColor) => {
|
|
20
|
+
ctx.strokeStyle = lineColor;
|
|
21
|
+
ctx.lineWidth = 2;
|
|
22
|
+
ctx.strokeRect(margin, margin, boardSize, boardSize);
|
|
23
|
+
ctx.lineWidth = 1;
|
|
24
|
+
Array.from({ length: 8 }).forEach((_, i) => {
|
|
25
|
+
const offset = (i + 1) * (boardSize / 9);
|
|
26
|
+
ctx.beginPath();
|
|
27
|
+
ctx.moveTo(margin + offset, margin);
|
|
28
|
+
ctx.lineTo(margin + offset, margin + boardSize);
|
|
29
|
+
ctx.stroke();
|
|
30
|
+
ctx.beginPath();
|
|
31
|
+
ctx.moveTo(margin, margin + offset);
|
|
32
|
+
ctx.lineTo(margin + boardSize, margin + offset);
|
|
33
|
+
ctx.stroke();
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente) => {
|
|
37
|
+
ctx.textAlign = "center";
|
|
38
|
+
ctx.textBaseline = "middle";
|
|
39
|
+
pieces.forEach((row, rowIndex) => {
|
|
40
|
+
row.forEach((piece, colIndex) => {
|
|
41
|
+
if (!piece) return;
|
|
42
|
+
const x = isSente ? 8 - rowIndex : rowIndex;
|
|
43
|
+
const y = isSente ? colIndex : 8 - colIndex;
|
|
44
|
+
const px = margin + x * cell + cell / 2;
|
|
45
|
+
const py = margin + y * cell + cell / 2;
|
|
46
|
+
ctx.save();
|
|
47
|
+
ctx.translate(px, py);
|
|
48
|
+
if (isSente && piece.color === Color.White) {
|
|
49
|
+
ctx.rotate(Math.PI);
|
|
50
|
+
} else if (!isSente && piece.color === Color.Black) {
|
|
51
|
+
ctx.rotate(Math.PI);
|
|
52
|
+
}
|
|
53
|
+
const kan = JKFPlayer.kindToKan(piece.kind);
|
|
54
|
+
ctx.fillStyle = "#000";
|
|
55
|
+
if (kan.length === 2) {
|
|
56
|
+
const baseFontSize = cell * fontSizeRatio * 0.5;
|
|
57
|
+
ctx.font = `${baseFontSize}px ${fontFamily}`;
|
|
58
|
+
const scaleX = 2;
|
|
59
|
+
const scaleY = 1;
|
|
60
|
+
const offsetY = cell * 0.18;
|
|
61
|
+
ctx.save();
|
|
62
|
+
ctx.scale(scaleX, scaleY);
|
|
63
|
+
ctx.fillText(kan[0], 0 / scaleX, -offsetY / scaleY);
|
|
64
|
+
ctx.restore();
|
|
65
|
+
ctx.save();
|
|
66
|
+
ctx.scale(scaleX, scaleY);
|
|
67
|
+
ctx.fillText(kan[1], 0 / scaleX, offsetY / scaleY);
|
|
68
|
+
ctx.restore();
|
|
69
|
+
} else {
|
|
70
|
+
const fontSize = cell * fontSizeRatio;
|
|
71
|
+
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
72
|
+
ctx.fillText(kan, 0, 0);
|
|
73
|
+
}
|
|
74
|
+
ctx.restore();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const drawCoordinates = (ctx, margin, boardSize, cell, fontFamily, isSente) => {
|
|
79
|
+
ctx.fillStyle = "#000";
|
|
80
|
+
ctx.font = `${cell * 0.35}px ${fontFamily}`;
|
|
81
|
+
ctx.textAlign = "center";
|
|
82
|
+
ctx.textBaseline = "middle";
|
|
83
|
+
Array.from({ length: 9 }).forEach((_, i) => {
|
|
84
|
+
const x = margin + i * cell + cell / 2;
|
|
85
|
+
const y = margin / 2;
|
|
86
|
+
const label = isSente ? JKFPlayer.numToZen(9 - i) : JKFPlayer.numToZen(i + 1);
|
|
87
|
+
ctx.fillText(label, x, y);
|
|
88
|
+
});
|
|
89
|
+
Array.from({ length: 9 }).forEach((_, i) => {
|
|
90
|
+
const x = margin + boardSize + margin / 2;
|
|
91
|
+
const y = margin + i * cell + cell / 2;
|
|
92
|
+
const label = isSente ? JKFPlayer.numToKan(i + 1) : JKFPlayer.numToKan(9 - i);
|
|
93
|
+
ctx.fillText(label, x, y);
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
const drawHighlightedCell = (ctx, margin, cell, isSente, currentMove) => {
|
|
97
|
+
if (!currentMove) return;
|
|
98
|
+
if (currentMove.to) {
|
|
99
|
+
const toRow = currentMove.to.x - 1;
|
|
100
|
+
const toCol = currentMove.to.y - 1;
|
|
101
|
+
const toX = isSente ? 8 - toRow : toRow;
|
|
102
|
+
const toY = isSente ? toCol : 8 - toCol;
|
|
103
|
+
ctx.fillStyle = "rgba(255,0,0,0.1)";
|
|
104
|
+
ctx.fillRect(margin + toX * cell, margin + toY * cell, cell, cell);
|
|
105
|
+
}
|
|
106
|
+
if (currentMove.from) {
|
|
107
|
+
const fromRow = currentMove.from.x - 1;
|
|
108
|
+
const fromCol = currentMove.from.y - 1;
|
|
109
|
+
const fromX = isSente ? 8 - fromRow : fromRow;
|
|
110
|
+
const fromY = isSente ? fromCol : 8 - fromCol;
|
|
111
|
+
ctx.fillStyle = "rgba(255,0,0,0.1)";
|
|
112
|
+
ctx.fillRect(margin + fromX * cell, margin + fromY * cell, cell, cell);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const ShogiBoardCanvas = ({
|
|
116
|
+
size = 360,
|
|
117
|
+
boardColor = "#f9d27a",
|
|
118
|
+
lineColor = "#000",
|
|
119
|
+
fontFamily = "serif",
|
|
120
|
+
fontSizeRatio = 0.7,
|
|
121
|
+
pieces,
|
|
122
|
+
isSente = true,
|
|
123
|
+
currentMove
|
|
124
|
+
}) => {
|
|
125
|
+
const canvasRef = React.useRef(null);
|
|
126
|
+
React.useEffect(() => {
|
|
127
|
+
const canvas = canvasRef.current;
|
|
128
|
+
if (!canvas) return;
|
|
129
|
+
const ctx = canvas.getContext("2d");
|
|
130
|
+
if (!ctx) return;
|
|
131
|
+
const { width, height, dpr } = getCanvasDimensions(size);
|
|
132
|
+
canvas.width = width;
|
|
133
|
+
canvas.height = height;
|
|
134
|
+
canvas.style.width = `${size}px`;
|
|
135
|
+
canvas.style.height = `${size}px`;
|
|
136
|
+
ctx.scale(dpr, dpr);
|
|
137
|
+
ctx.clearRect(0, 0, size, size);
|
|
138
|
+
const { margin, boardSize, cell } = getBoardLayout(size);
|
|
139
|
+
drawBackground(ctx, size, boardColor);
|
|
140
|
+
drawBoard(ctx, margin, boardSize, lineColor);
|
|
141
|
+
drawHighlightedCell(ctx, margin, cell, isSente, currentMove);
|
|
142
|
+
drawPieces(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente);
|
|
143
|
+
drawCoordinates(ctx, margin, boardSize, cell, fontFamily, isSente);
|
|
144
|
+
}, [size, boardColor, lineColor, fontFamily, fontSizeRatio, pieces, isSente, currentMove]);
|
|
145
|
+
return /* @__PURE__ */ React.createElement("canvas", { ref: canvasRef, width: size, height: size });
|
|
146
|
+
};
|
|
147
|
+
var shogi_board_canvas_default = ShogiBoardCanvas;
|
|
148
|
+
export {
|
|
149
|
+
shogi_board_canvas_default as default
|
|
150
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Piece } from 'shogi.js';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
size?: number;
|
|
6
|
+
boardColor?: string;
|
|
7
|
+
lineColor?: string;
|
|
8
|
+
fontFamily?: string;
|
|
9
|
+
fontSizeRatio?: number;
|
|
10
|
+
hands: Piece[][];
|
|
11
|
+
isSente?: boolean;
|
|
12
|
+
isTop?: boolean;
|
|
13
|
+
};
|
|
14
|
+
declare const ShogiHandsCanvas: React.FC<Props>;
|
|
15
|
+
|
|
16
|
+
export { ShogiHandsCanvas as default };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { JKFPlayer } from "json-kifu-format";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Color } from "shogi.js";
|
|
5
|
+
const getCanvasDimensions = (size) => {
|
|
6
|
+
const dpr = window.devicePixelRatio || 1;
|
|
7
|
+
return { width: size * dpr, height: size * dpr, dpr };
|
|
8
|
+
};
|
|
9
|
+
const getHandsLayout = (size) => {
|
|
10
|
+
const margin = size * 0.06;
|
|
11
|
+
const boardSize = size - margin * 2;
|
|
12
|
+
const cellSize = boardSize / 9;
|
|
13
|
+
const handsHeight = cellSize + margin + 2;
|
|
14
|
+
return { margin, handsHeight, boardSize, cellSize };
|
|
15
|
+
};
|
|
16
|
+
const drawBackground = (ctx, width, height, color) => {
|
|
17
|
+
ctx.fillStyle = color;
|
|
18
|
+
ctx.fillRect(0, 0, width, height);
|
|
19
|
+
};
|
|
20
|
+
const drawHandsFrame = (ctx, x, y, width, height, lineColor, isTop) => {
|
|
21
|
+
ctx.strokeStyle = lineColor;
|
|
22
|
+
ctx.lineWidth = 2;
|
|
23
|
+
if (isTop) {
|
|
24
|
+
ctx.strokeRect(x, y, width, height);
|
|
25
|
+
} else {
|
|
26
|
+
ctx.strokeRect(x, 2, width, height);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const drawCoordinates = (ctx, fontSize, fontFamily) => {
|
|
30
|
+
ctx.fillStyle = "#000";
|
|
31
|
+
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
32
|
+
ctx.textAlign = "center";
|
|
33
|
+
ctx.textBaseline = "middle";
|
|
34
|
+
};
|
|
35
|
+
const drawPieces = (ctx, hands, margin, boardSize, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
|
|
36
|
+
ctx.textAlign = "center";
|
|
37
|
+
ctx.textBaseline = "middle";
|
|
38
|
+
const pieces = isSente && isTop || !isSente && !isTop ? hands[Color.White] : hands[Color.Black];
|
|
39
|
+
if (!pieces || pieces.length === 0) return;
|
|
40
|
+
const grouped = pieces.reduce(
|
|
41
|
+
(acc, piece) => {
|
|
42
|
+
if (!piece) return acc;
|
|
43
|
+
const key = piece.kind;
|
|
44
|
+
if (!acc[key]) acc[key] = { count: 0, color: piece.color };
|
|
45
|
+
acc[key].count += 1;
|
|
46
|
+
return acc;
|
|
47
|
+
},
|
|
48
|
+
{}
|
|
49
|
+
);
|
|
50
|
+
const order = ["OU", "HI", "KA", "KI", "GI", "KE", "KY", "FU"];
|
|
51
|
+
const kinds = order.filter((kind) => grouped[kind]);
|
|
52
|
+
kinds.forEach((kind, index) => {
|
|
53
|
+
const { count, color } = grouped[kind];
|
|
54
|
+
const px = isTop ? margin + boardSize - (index * cell + cell / 2) : margin + index * cell + cell / 2;
|
|
55
|
+
const py = isTop ? margin + cell / 2 : cell / 2 + 2;
|
|
56
|
+
ctx.save();
|
|
57
|
+
ctx.translate(px, py);
|
|
58
|
+
if (isSente && color === Color.White) {
|
|
59
|
+
ctx.rotate(Math.PI);
|
|
60
|
+
} else if (!isSente && color === Color.Black) {
|
|
61
|
+
ctx.rotate(Math.PI);
|
|
62
|
+
}
|
|
63
|
+
const fontSize = cell * fontSizeRatio;
|
|
64
|
+
const kan = JKFPlayer.kindToKan(kind);
|
|
65
|
+
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
66
|
+
ctx.fillText(kan, 0, 0);
|
|
67
|
+
if (count > 1) {
|
|
68
|
+
ctx.font = `${fontSize * 0.5}px ${fontFamily}`;
|
|
69
|
+
const countOffsetX = cell * 0;
|
|
70
|
+
const countOffsetY = cell * 0.8;
|
|
71
|
+
ctx.fillText(String(count), countOffsetX, countOffsetY);
|
|
72
|
+
}
|
|
73
|
+
ctx.restore();
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
const ShogiHandsCanvas = ({
|
|
77
|
+
size = 360,
|
|
78
|
+
boardColor = "#f9d27a",
|
|
79
|
+
lineColor = "#000",
|
|
80
|
+
fontFamily = "serif",
|
|
81
|
+
fontSizeRatio = 0.7,
|
|
82
|
+
hands,
|
|
83
|
+
isSente = true,
|
|
84
|
+
isTop = false
|
|
85
|
+
}) => {
|
|
86
|
+
const canvasRef = React.useRef(null);
|
|
87
|
+
React.useEffect(() => {
|
|
88
|
+
const canvas = canvasRef.current;
|
|
89
|
+
if (!canvas) return;
|
|
90
|
+
const ctx = canvas.getContext("2d");
|
|
91
|
+
if (!ctx) return;
|
|
92
|
+
const { margin, handsHeight, boardSize, cellSize } = getHandsLayout(size);
|
|
93
|
+
const { width: canvasWidth, dpr } = getCanvasDimensions(size);
|
|
94
|
+
canvas.width = canvasWidth;
|
|
95
|
+
canvas.height = handsHeight * dpr;
|
|
96
|
+
canvas.style.width = `${size}px`;
|
|
97
|
+
canvas.style.height = `${handsHeight}px`;
|
|
98
|
+
ctx.scale(dpr, dpr);
|
|
99
|
+
ctx.clearRect(0, 0, size, handsHeight);
|
|
100
|
+
drawBackground(ctx, size, handsHeight, boardColor);
|
|
101
|
+
drawHandsFrame(ctx, margin, margin, boardSize, cellSize, lineColor, isTop);
|
|
102
|
+
drawCoordinates(ctx, cellSize * 0.35, fontFamily);
|
|
103
|
+
drawPieces(ctx, hands, margin, boardSize, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
|
|
104
|
+
}, [size, boardColor, lineColor, fontFamily, fontSizeRatio, isSente, hands, isTop]);
|
|
105
|
+
return /* @__PURE__ */ React.createElement("canvas", { ref: canvasRef });
|
|
106
|
+
};
|
|
107
|
+
var shogi_hands_canvas_default = ShogiHandsCanvas;
|
|
108
|
+
export {
|
|
109
|
+
shogi_hands_canvas_default as default
|
|
110
|
+
};
|