@hackersheet/next-document-content-kifu 0.1.0-alpha.5 → 0.1.0-alpha.7
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.js +3 -2
- package/dist/cjs/components/shogi-player/button.js +1 -1
- package/dist/cjs/components/shogi-player/moves-area.js +12 -8
- package/dist/cjs/components/shogi-player/shogi-board-canvas.js +25 -12
- package/dist/cjs/components/shogi-player/shogi-hands-canvas.js +27 -8
- package/dist/cjs/components/shogi-player/shogi-player.d.ts +2 -0
- package/dist/cjs/components/shogi-player/shogi-player.js +36 -2
- package/dist/esm/components/kifu/kifu.mjs +4 -3
- package/dist/esm/components/shogi-player/button.mjs +1 -1
- package/dist/esm/components/shogi-player/moves-area.mjs +12 -8
- package/dist/esm/components/shogi-player/shogi-board-canvas.mjs +25 -12
- package/dist/esm/components/shogi-player/shogi-hands-canvas.mjs +27 -8
- package/dist/esm/components/shogi-player/shogi-player.d.mts +2 -0
- package/dist/esm/components/shogi-player/shogi-player.mjs +37 -3
- package/package.json +2 -2
|
@@ -36,6 +36,7 @@ var import_navigation = require("next/navigation");
|
|
|
36
36
|
var import_react = __toESM(require("react"));
|
|
37
37
|
var import_shogi_player = require("../shogi-player");
|
|
38
38
|
function Kifu({ code, language }) {
|
|
39
|
+
const [ply, setPly] = (0, import_react.useState)(0);
|
|
39
40
|
const [, filename] = language.split(":");
|
|
40
41
|
const searchParams = (0, import_navigation.useSearchParams)();
|
|
41
42
|
const id = filename ? `user-content-${filename}` : void 0;
|
|
@@ -43,8 +44,8 @@ function Kifu({ code, language }) {
|
|
|
43
44
|
const newPly = Number(searchParams.get("ply") ?? 0);
|
|
44
45
|
const hash = typeof window !== "undefined" ? window.location.hash.replace(/^#!?/, "") : "";
|
|
45
46
|
if (hash === id) {
|
|
46
|
-
|
|
47
|
+
setPly(newPly);
|
|
47
48
|
}
|
|
48
49
|
}, [searchParams, id]);
|
|
49
|
-
return /* @__PURE__ */ import_react.default.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ import_react.default.createElement(import_shogi_player.ShogiPlayer, { kifuText: code }));
|
|
50
|
+
return /* @__PURE__ */ import_react.default.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ import_react.default.createElement(import_shogi_player.ShogiPlayer, { kifuText: code, tesuu: ply, size: 320 }));
|
|
50
51
|
}
|
|
@@ -36,7 +36,7 @@ function Button({ children, onClick }) {
|
|
|
36
36
|
return /* @__PURE__ */ import_react.default.createElement(
|
|
37
37
|
"button",
|
|
38
38
|
{
|
|
39
|
-
className: "border-2 text-black p-2 border-black rounded-lg hover:bg-amber-100 cursor-pointer",
|
|
39
|
+
className: "border-2 text-xs text-black p-2 border-black rounded-lg hover:bg-amber-100 cursor-pointer",
|
|
40
40
|
onClick
|
|
41
41
|
},
|
|
42
42
|
children
|
|
@@ -49,24 +49,28 @@ function MovesArea(props) {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
}, [props.tesuu]);
|
|
52
|
-
|
|
52
|
+
const current = 0 === props.tesuu ? " bg-amber-600" : "";
|
|
53
|
+
return /* @__PURE__ */ import_react.default.createElement("div", { className: "absolute overflow-y-auto h-full w-full border-2 border-black text-black", ref: containerRef }, /* @__PURE__ */ import_react.default.createElement("div", { className: "grid gap-0 text-xs" }, /* @__PURE__ */ import_react.default.createElement(
|
|
53
54
|
"div",
|
|
54
55
|
{
|
|
55
56
|
onClick: () => props.onTesuuChange && props.onTesuuChange(0),
|
|
56
|
-
className:
|
|
57
|
+
className: "col-span-500 grid grid-cols-subgrid gap-2 py-1 px-2 cursor-pointer hover:bg-amber-100" + current
|
|
57
58
|
},
|
|
59
|
+
/* @__PURE__ */ import_react.default.createElement("div", null, 0 === props.tesuu && /* @__PURE__ */ import_react.default.createElement("div", { ref: scrollRef })),
|
|
58
60
|
/* @__PURE__ */ import_react.default.createElement("div", null, "\u958B\u59CB\u5C40\u9762")
|
|
59
|
-
), moves.map(
|
|
60
|
-
|
|
61
|
+
), moves.map((move, index) => {
|
|
62
|
+
if (index === 0) return;
|
|
63
|
+
const current2 = index === props.tesuu ? " bg-amber-600" : "";
|
|
64
|
+
return /* @__PURE__ */ import_react.default.createElement(import_react.Fragment, { key: index }, /* @__PURE__ */ import_react.default.createElement(
|
|
61
65
|
"div",
|
|
62
66
|
{
|
|
63
|
-
className:
|
|
67
|
+
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,
|
|
64
68
|
onClick: () => props.onTesuuChange && props.onTesuuChange(index)
|
|
65
69
|
},
|
|
66
|
-
/* @__PURE__ */ import_react.default.createElement("div", { className: "
|
|
70
|
+
/* @__PURE__ */ import_react.default.createElement("div", { className: "flex" }, index === props.tesuu && /* @__PURE__ */ import_react.default.createElement("div", { ref: scrollRef }), /* @__PURE__ */ import_react.default.createElement("div", { className: "tabular-nums text-right flex-auto" }, index)),
|
|
67
71
|
/* @__PURE__ */ import_react.default.createElement("div", null, import_json_kifu_format.JKFPlayer.moveToReadableKifu(move))
|
|
68
|
-
))
|
|
69
|
-
)));
|
|
72
|
+
));
|
|
73
|
+
})));
|
|
70
74
|
}
|
|
71
75
|
// Annotate the CommonJS export names for ESM import in node:
|
|
72
76
|
0 && (module.exports = {
|
|
@@ -66,7 +66,7 @@ const drawBoard = (ctx, margin, boardSize, lineColor) => {
|
|
|
66
66
|
ctx.stroke();
|
|
67
67
|
});
|
|
68
68
|
};
|
|
69
|
-
const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente
|
|
69
|
+
const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente) => {
|
|
70
70
|
ctx.textAlign = "center";
|
|
71
71
|
ctx.textBaseline = "middle";
|
|
72
72
|
pieces.forEach((row, rowIndex) => {
|
|
@@ -85,12 +85,6 @@ const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSent
|
|
|
85
85
|
}
|
|
86
86
|
const kan = import_json_kifu_format.JKFPlayer.kindToKan(piece.kind);
|
|
87
87
|
ctx.fillStyle = "#000";
|
|
88
|
-
if (currentMove) {
|
|
89
|
-
const to = currentMove.to;
|
|
90
|
-
if (to && to.x === rowIndex + 1 && to.y === colIndex + 1) {
|
|
91
|
-
ctx.fillStyle = "red";
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
88
|
if (kan.length === 2) {
|
|
95
89
|
const baseFontSize = cell * fontSizeRatio * 0.5;
|
|
96
90
|
ctx.font = `${baseFontSize}px ${fontFamily}`;
|
|
@@ -122,17 +116,35 @@ const drawCoordinates = (ctx, margin, boardSize, cell, fontFamily, isSente) => {
|
|
|
122
116
|
Array.from({ length: 9 }).forEach((_, i) => {
|
|
123
117
|
const x = margin + i * cell + cell / 2;
|
|
124
118
|
const y = margin / 2;
|
|
125
|
-
const label = isSente ? (9 - i)
|
|
119
|
+
const label = isSente ? import_json_kifu_format.JKFPlayer.numToZen(9 - i) : import_json_kifu_format.JKFPlayer.numToZen(i + 1);
|
|
126
120
|
ctx.fillText(label, x, y);
|
|
127
121
|
});
|
|
128
|
-
|
|
129
|
-
kanji.forEach((k, i) => {
|
|
122
|
+
Array.from({ length: 9 }).forEach((_, i) => {
|
|
130
123
|
const x = margin + boardSize + margin / 2;
|
|
131
124
|
const y = margin + i * cell + cell / 2;
|
|
132
|
-
const label = isSente ?
|
|
125
|
+
const label = isSente ? import_json_kifu_format.JKFPlayer.numToKan(i + 1) : import_json_kifu_format.JKFPlayer.numToKan(9 - i);
|
|
133
126
|
ctx.fillText(label, x, y);
|
|
134
127
|
});
|
|
135
128
|
};
|
|
129
|
+
const drawHighlightedCell = (ctx, margin, cell, isSente, currentMove) => {
|
|
130
|
+
if (!currentMove) return;
|
|
131
|
+
if (currentMove.to) {
|
|
132
|
+
const toRow = currentMove.to.x - 1;
|
|
133
|
+
const toCol = currentMove.to.y - 1;
|
|
134
|
+
const toX = isSente ? 8 - toRow : toRow;
|
|
135
|
+
const toY = isSente ? toCol : 8 - toCol;
|
|
136
|
+
ctx.fillStyle = "rgba(255,0,0,0.1)";
|
|
137
|
+
ctx.fillRect(margin + toX * cell, margin + toY * cell, cell, cell);
|
|
138
|
+
}
|
|
139
|
+
if (currentMove.from) {
|
|
140
|
+
const fromRow = currentMove.from.x - 1;
|
|
141
|
+
const fromCol = currentMove.from.y - 1;
|
|
142
|
+
const fromX = isSente ? 8 - fromRow : fromRow;
|
|
143
|
+
const fromY = isSente ? fromCol : 8 - fromCol;
|
|
144
|
+
ctx.fillStyle = "rgba(255,0,0,0.1)";
|
|
145
|
+
ctx.fillRect(margin + fromX * cell, margin + fromY * cell, cell, cell);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
136
148
|
const ShogiBoardCanvas = ({
|
|
137
149
|
size = 360,
|
|
138
150
|
boardColor = "#f9d27a",
|
|
@@ -159,7 +171,8 @@ const ShogiBoardCanvas = ({
|
|
|
159
171
|
const { margin, boardSize, cell } = getBoardLayout(size);
|
|
160
172
|
drawBackground(ctx, size, boardColor);
|
|
161
173
|
drawBoard(ctx, margin, boardSize, lineColor);
|
|
162
|
-
|
|
174
|
+
drawHighlightedCell(ctx, margin, cell, isSente, currentMove);
|
|
175
|
+
drawPieces(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente);
|
|
163
176
|
drawCoordinates(ctx, margin, boardSize, cell, fontFamily, isSente);
|
|
164
177
|
}, [size, boardColor, lineColor, fontFamily, fontSizeRatio, pieces, isSente, currentMove]);
|
|
165
178
|
return /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef, width: size, height: size });
|
|
@@ -65,25 +65,44 @@ const drawCoordinates = (ctx, fontSize, fontFamily) => {
|
|
|
65
65
|
ctx.textAlign = "center";
|
|
66
66
|
ctx.textBaseline = "middle";
|
|
67
67
|
};
|
|
68
|
-
const drawPieces = (ctx, hands, margin, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
|
|
68
|
+
const drawPieces = (ctx, hands, margin, boardSize, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
|
|
69
69
|
ctx.textAlign = "center";
|
|
70
70
|
ctx.textBaseline = "middle";
|
|
71
71
|
const pieces = isSente && isTop || !isSente && !isTop ? hands[import_shogi.Color.White] : hands[import_shogi.Color.Black];
|
|
72
|
-
pieces.
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
if (!pieces || pieces.length === 0) return;
|
|
73
|
+
const grouped = pieces.reduce(
|
|
74
|
+
(acc, piece) => {
|
|
75
|
+
if (!piece) return acc;
|
|
76
|
+
const key = piece.kind;
|
|
77
|
+
if (!acc[key]) acc[key] = { count: 0, color: piece.color };
|
|
78
|
+
acc[key].count += 1;
|
|
79
|
+
return acc;
|
|
80
|
+
},
|
|
81
|
+
{}
|
|
82
|
+
);
|
|
83
|
+
const order = ["OU", "HI", "KA", "KI", "GI", "KE", "KY", "FU"];
|
|
84
|
+
const kinds = order.filter((kind) => grouped[kind]);
|
|
85
|
+
kinds.forEach((kind, index) => {
|
|
86
|
+
const { count, color } = grouped[kind];
|
|
87
|
+
const px = isTop ? margin + boardSize - (index * cell + cell / 2) : margin + index * cell + cell / 2;
|
|
75
88
|
const py = isTop ? margin + cell / 2 : cell / 2 + 2;
|
|
76
89
|
ctx.save();
|
|
77
90
|
ctx.translate(px, py);
|
|
78
|
-
if (isSente &&
|
|
91
|
+
if (isSente && color === import_shogi.Color.White) {
|
|
79
92
|
ctx.rotate(Math.PI);
|
|
80
|
-
} else if (!isSente &&
|
|
93
|
+
} else if (!isSente && color === import_shogi.Color.Black) {
|
|
81
94
|
ctx.rotate(Math.PI);
|
|
82
95
|
}
|
|
83
96
|
const fontSize = cell * fontSizeRatio;
|
|
84
|
-
const kan = import_json_kifu_format.JKFPlayer.kindToKan(
|
|
97
|
+
const kan = import_json_kifu_format.JKFPlayer.kindToKan(kind);
|
|
85
98
|
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
86
99
|
ctx.fillText(kan, 0, 0);
|
|
100
|
+
if (count > 1) {
|
|
101
|
+
ctx.font = `${fontSize * 0.5}px ${fontFamily}`;
|
|
102
|
+
const countOffsetX = cell * 0;
|
|
103
|
+
const countOffsetY = cell * 0.8;
|
|
104
|
+
ctx.fillText(String(count), countOffsetX, countOffsetY);
|
|
105
|
+
}
|
|
87
106
|
ctx.restore();
|
|
88
107
|
});
|
|
89
108
|
};
|
|
@@ -114,7 +133,7 @@ const ShogiHandsCanvas = ({
|
|
|
114
133
|
drawBackground(ctx, size, handsHeight, boardColor);
|
|
115
134
|
drawHandsFrame(ctx, margin, margin, boardSize, cellSize, lineColor, isTop);
|
|
116
135
|
drawCoordinates(ctx, cellSize * 0.35, fontFamily);
|
|
117
|
-
drawPieces(ctx, hands, margin, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
|
|
136
|
+
drawPieces(ctx, hands, margin, boardSize, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
|
|
118
137
|
}, [size, boardColor, lineColor, fontFamily, fontSizeRatio, isSente, hands, isTop]);
|
|
119
138
|
return /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef });
|
|
120
139
|
};
|
|
@@ -48,7 +48,7 @@ function ShogiPlayer(props) {
|
|
|
48
48
|
const [isSente, setIsSente] = (0, import_react.useState)(true);
|
|
49
49
|
const [maxTesuu, setMaxTesuu] = (0, import_react.useState)(player.getMaxTesuu());
|
|
50
50
|
const [comments, setComments] = (0, import_react.useState)(player.getComments());
|
|
51
|
-
const size = 360;
|
|
51
|
+
const size = props.size ? props.size : 360;
|
|
52
52
|
const updateState = () => {
|
|
53
53
|
setPieces([...player.shogi.board]);
|
|
54
54
|
setHands([...player.shogi.hands]);
|
|
@@ -74,5 +74,39 @@ function ShogiPlayer(props) {
|
|
|
74
74
|
setIsSente(!isSente);
|
|
75
75
|
updateState();
|
|
76
76
|
};
|
|
77
|
-
|
|
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" }, 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 }, 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"))));
|
|
78
112
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useSearchParams } from "next/navigation";
|
|
3
|
-
import React, { useEffect } from "react";
|
|
3
|
+
import React, { useEffect, useState } from "react";
|
|
4
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;
|
|
@@ -10,10 +11,10 @@ function Kifu({ code, language }) {
|
|
|
10
11
|
const newPly = Number(searchParams.get("ply") ?? 0);
|
|
11
12
|
const hash = typeof window !== "undefined" ? window.location.hash.replace(/^#!?/, "") : "";
|
|
12
13
|
if (hash === id) {
|
|
13
|
-
|
|
14
|
+
setPly(newPly);
|
|
14
15
|
}
|
|
15
16
|
}, [searchParams, id]);
|
|
16
|
-
return /* @__PURE__ */ React.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ React.createElement(ShogiPlayer, { kifuText: code }));
|
|
17
|
+
return /* @__PURE__ */ React.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ React.createElement(ShogiPlayer, { kifuText: code, tesuu: ply, size: 320 }));
|
|
17
18
|
}
|
|
18
19
|
export {
|
|
19
20
|
Kifu as default
|
|
@@ -3,7 +3,7 @@ function Button({ children, onClick }) {
|
|
|
3
3
|
return /* @__PURE__ */ React.createElement(
|
|
4
4
|
"button",
|
|
5
5
|
{
|
|
6
|
-
className: "border-2 text-black p-2 border-black rounded-lg hover:bg-amber-100 cursor-pointer",
|
|
6
|
+
className: "border-2 text-xs text-black p-2 border-black rounded-lg hover:bg-amber-100 cursor-pointer",
|
|
7
7
|
onClick
|
|
8
8
|
},
|
|
9
9
|
children
|
|
@@ -16,24 +16,28 @@ function MovesArea(props) {
|
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
}, [props.tesuu]);
|
|
19
|
-
|
|
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(
|
|
20
21
|
"div",
|
|
21
22
|
{
|
|
22
23
|
onClick: () => props.onTesuuChange && props.onTesuuChange(0),
|
|
23
|
-
className:
|
|
24
|
+
className: "col-span-500 grid grid-cols-subgrid gap-2 py-1 px-2 cursor-pointer hover:bg-amber-100" + current
|
|
24
25
|
},
|
|
26
|
+
/* @__PURE__ */ React.createElement("div", null, 0 === props.tesuu && /* @__PURE__ */ React.createElement("div", { ref: scrollRef })),
|
|
25
27
|
/* @__PURE__ */ React.createElement("div", null, "\u958B\u59CB\u5C40\u9762")
|
|
26
|
-
), moves.map(
|
|
27
|
-
|
|
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(
|
|
28
32
|
"div",
|
|
29
33
|
{
|
|
30
|
-
className:
|
|
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,
|
|
31
35
|
onClick: () => props.onTesuuChange && props.onTesuuChange(index)
|
|
32
36
|
},
|
|
33
|
-
/* @__PURE__ */ React.createElement("div", { className: "
|
|
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)),
|
|
34
38
|
/* @__PURE__ */ React.createElement("div", null, JKFPlayer.moveToReadableKifu(move))
|
|
35
|
-
))
|
|
36
|
-
)));
|
|
39
|
+
));
|
|
40
|
+
})));
|
|
37
41
|
}
|
|
38
42
|
export {
|
|
39
43
|
MovesArea
|
|
@@ -33,7 +33,7 @@ const drawBoard = (ctx, margin, boardSize, lineColor) => {
|
|
|
33
33
|
ctx.stroke();
|
|
34
34
|
});
|
|
35
35
|
};
|
|
36
|
-
const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente
|
|
36
|
+
const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente) => {
|
|
37
37
|
ctx.textAlign = "center";
|
|
38
38
|
ctx.textBaseline = "middle";
|
|
39
39
|
pieces.forEach((row, rowIndex) => {
|
|
@@ -52,12 +52,6 @@ const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSent
|
|
|
52
52
|
}
|
|
53
53
|
const kan = JKFPlayer.kindToKan(piece.kind);
|
|
54
54
|
ctx.fillStyle = "#000";
|
|
55
|
-
if (currentMove) {
|
|
56
|
-
const to = currentMove.to;
|
|
57
|
-
if (to && to.x === rowIndex + 1 && to.y === colIndex + 1) {
|
|
58
|
-
ctx.fillStyle = "red";
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
55
|
if (kan.length === 2) {
|
|
62
56
|
const baseFontSize = cell * fontSizeRatio * 0.5;
|
|
63
57
|
ctx.font = `${baseFontSize}px ${fontFamily}`;
|
|
@@ -89,17 +83,35 @@ const drawCoordinates = (ctx, margin, boardSize, cell, fontFamily, isSente) => {
|
|
|
89
83
|
Array.from({ length: 9 }).forEach((_, i) => {
|
|
90
84
|
const x = margin + i * cell + cell / 2;
|
|
91
85
|
const y = margin / 2;
|
|
92
|
-
const label = isSente ? (9 - i)
|
|
86
|
+
const label = isSente ? JKFPlayer.numToZen(9 - i) : JKFPlayer.numToZen(i + 1);
|
|
93
87
|
ctx.fillText(label, x, y);
|
|
94
88
|
});
|
|
95
|
-
|
|
96
|
-
kanji.forEach((k, i) => {
|
|
89
|
+
Array.from({ length: 9 }).forEach((_, i) => {
|
|
97
90
|
const x = margin + boardSize + margin / 2;
|
|
98
91
|
const y = margin + i * cell + cell / 2;
|
|
99
|
-
const label = isSente ?
|
|
92
|
+
const label = isSente ? JKFPlayer.numToKan(i + 1) : JKFPlayer.numToKan(9 - i);
|
|
100
93
|
ctx.fillText(label, x, y);
|
|
101
94
|
});
|
|
102
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
|
+
};
|
|
103
115
|
const ShogiBoardCanvas = ({
|
|
104
116
|
size = 360,
|
|
105
117
|
boardColor = "#f9d27a",
|
|
@@ -126,7 +138,8 @@ const ShogiBoardCanvas = ({
|
|
|
126
138
|
const { margin, boardSize, cell } = getBoardLayout(size);
|
|
127
139
|
drawBackground(ctx, size, boardColor);
|
|
128
140
|
drawBoard(ctx, margin, boardSize, lineColor);
|
|
129
|
-
|
|
141
|
+
drawHighlightedCell(ctx, margin, cell, isSente, currentMove);
|
|
142
|
+
drawPieces(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente);
|
|
130
143
|
drawCoordinates(ctx, margin, boardSize, cell, fontFamily, isSente);
|
|
131
144
|
}, [size, boardColor, lineColor, fontFamily, fontSizeRatio, pieces, isSente, currentMove]);
|
|
132
145
|
return /* @__PURE__ */ React.createElement("canvas", { ref: canvasRef, width: size, height: size });
|
|
@@ -32,25 +32,44 @@ const drawCoordinates = (ctx, fontSize, fontFamily) => {
|
|
|
32
32
|
ctx.textAlign = "center";
|
|
33
33
|
ctx.textBaseline = "middle";
|
|
34
34
|
};
|
|
35
|
-
const drawPieces = (ctx, hands, margin, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
|
|
35
|
+
const drawPieces = (ctx, hands, margin, boardSize, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
|
|
36
36
|
ctx.textAlign = "center";
|
|
37
37
|
ctx.textBaseline = "middle";
|
|
38
38
|
const pieces = isSente && isTop || !isSente && !isTop ? hands[Color.White] : hands[Color.Black];
|
|
39
|
-
pieces.
|
|
40
|
-
|
|
41
|
-
|
|
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;
|
|
42
55
|
const py = isTop ? margin + cell / 2 : cell / 2 + 2;
|
|
43
56
|
ctx.save();
|
|
44
57
|
ctx.translate(px, py);
|
|
45
|
-
if (isSente &&
|
|
58
|
+
if (isSente && color === Color.White) {
|
|
46
59
|
ctx.rotate(Math.PI);
|
|
47
|
-
} else if (!isSente &&
|
|
60
|
+
} else if (!isSente && color === Color.Black) {
|
|
48
61
|
ctx.rotate(Math.PI);
|
|
49
62
|
}
|
|
50
63
|
const fontSize = cell * fontSizeRatio;
|
|
51
|
-
const kan = JKFPlayer.kindToKan(
|
|
64
|
+
const kan = JKFPlayer.kindToKan(kind);
|
|
52
65
|
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
53
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
|
+
}
|
|
54
73
|
ctx.restore();
|
|
55
74
|
});
|
|
56
75
|
};
|
|
@@ -81,7 +100,7 @@ const ShogiHandsCanvas = ({
|
|
|
81
100
|
drawBackground(ctx, size, handsHeight, boardColor);
|
|
82
101
|
drawHandsFrame(ctx, margin, margin, boardSize, cellSize, lineColor, isTop);
|
|
83
102
|
drawCoordinates(ctx, cellSize * 0.35, fontFamily);
|
|
84
|
-
drawPieces(ctx, hands, margin, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
|
|
103
|
+
drawPieces(ctx, hands, margin, boardSize, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
|
|
85
104
|
}, [size, boardColor, lineColor, fontFamily, fontSizeRatio, isSente, hands, isTop]);
|
|
86
105
|
return /* @__PURE__ */ React.createElement("canvas", { ref: canvasRef });
|
|
87
106
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { JKFPlayer } from "json-kifu-format";
|
|
3
|
-
import React, { useState } from "react";
|
|
3
|
+
import React, { useCallback, useEffect, useState } from "react";
|
|
4
4
|
import Button from "./button";
|
|
5
5
|
import { MovesArea } from "./moves-area";
|
|
6
6
|
import ShogiBoardCanvas from "./shogi-board-canvas";
|
|
@@ -15,7 +15,7 @@ function ShogiPlayer(props) {
|
|
|
15
15
|
const [isSente, setIsSente] = useState(true);
|
|
16
16
|
const [maxTesuu, setMaxTesuu] = useState(player.getMaxTesuu());
|
|
17
17
|
const [comments, setComments] = useState(player.getComments());
|
|
18
|
-
const size = 360;
|
|
18
|
+
const size = props.size ? props.size : 360;
|
|
19
19
|
const updateState = () => {
|
|
20
20
|
setPieces([...player.shogi.board]);
|
|
21
21
|
setHands([...player.shogi.hands]);
|
|
@@ -41,7 +41,41 @@ function ShogiPlayer(props) {
|
|
|
41
41
|
setIsSente(!isSente);
|
|
42
42
|
updateState();
|
|
43
43
|
};
|
|
44
|
-
|
|
44
|
+
const handleKeydown = useCallback(
|
|
45
|
+
(event) => {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
event.stopPropagation();
|
|
48
|
+
switch (event.key) {
|
|
49
|
+
case "Up":
|
|
50
|
+
case "ArrowUp":
|
|
51
|
+
handleGoto(0);
|
|
52
|
+
break;
|
|
53
|
+
case "Down":
|
|
54
|
+
case "ArrowDown":
|
|
55
|
+
handleGoto(maxTesuu);
|
|
56
|
+
break;
|
|
57
|
+
case "Left":
|
|
58
|
+
case "ArrowLeft":
|
|
59
|
+
handleBackward();
|
|
60
|
+
break;
|
|
61
|
+
case " ":
|
|
62
|
+
case "Right":
|
|
63
|
+
case "ArrowRight":
|
|
64
|
+
handleForward();
|
|
65
|
+
break;
|
|
66
|
+
case "r":
|
|
67
|
+
handeleToggle();
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
[maxTesuu, isSente]
|
|
72
|
+
);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (props.tesuu !== void 0) {
|
|
75
|
+
handleGoto(props.tesuu);
|
|
76
|
+
}
|
|
77
|
+
}, [props.tesuu]);
|
|
78
|
+
return /* @__PURE__ */ React.createElement("div", { className: "flex w-fit", tabIndex: 1, onKeyDown: handleKeydown }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React.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__ */ React.createElement(ShogiHandsCanvas, { size, hands, isSente, isTop: true }), /* @__PURE__ */ React.createElement(ShogiBoardCanvas, { size, pieces, isSente, currentMove }), /* @__PURE__ */ React.createElement(ShogiHandsCanvas, { size, hands, isSente, isTop: false }), /* @__PURE__ */ React.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__ */ React.createElement("div", { className: "flex flex-col w-fit bg-[#f9d27a] p-4 gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex-1 relative w-full" }, /* @__PURE__ */ React.createElement(MovesArea, { moves, tesuu, onTesuuChange: handleGoto })), /* @__PURE__ */ React.createElement("div", { className: "text-black border-black border-2 p-1 text-xs" }, comments.map((comment, index) => /* @__PURE__ */ React.createElement("div", { key: index }, comment)), comments.length === 0 && tesuu !== 0 && /* @__PURE__ */ React.createElement("div", null, "\xA0"), tesuu === 0 && Object.entries(player.kifu.header).map(([key, value], i) => /* @__PURE__ */ React.createElement("div", { key: i }, key, ": ", value))), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React.createElement(Button, { onClick: () => handleGoto(0) }, "\u6700\u521D"), /* @__PURE__ */ React.createElement(Button, { onClick: handleBackward }, "\u524D"), /* @__PURE__ */ React.createElement(Button, { onClick: handleForward }, "\u6B21"), /* @__PURE__ */ React.createElement(Button, { onClick: () => handleGoto(maxTesuu) }, "\u6700\u5F8C"), /* @__PURE__ */ React.createElement(Button, { onClick: handeleToggle }, "\u53CD\u8EE2"))));
|
|
45
79
|
}
|
|
46
80
|
export {
|
|
47
81
|
ShogiPlayer as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hackersheet/next-document-content-kifu",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.7",
|
|
4
4
|
"description": "Hacker Sheet document content kifu components for Next.js",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"repository": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"shogi.js": "^5.4.1",
|
|
28
28
|
"json-kifu-format": "^5.4.1",
|
|
29
29
|
"@hackersheet/core": "0.1.0-alpha.11",
|
|
30
|
-
"@hackersheet/react-document-content": "0.1.0-alpha.
|
|
30
|
+
"@hackersheet/react-document-content": "0.1.0-alpha.13"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/react": "^19.2.0",
|