@hackersheet/next-document-content-kifu 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +1 -1
- package/dist/cjs/components/shogi-player/button.js +1 -1
- 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 +1 -0
- package/dist/cjs/components/shogi-player/shogi-player.js +31 -2
- package/dist/esm/components/kifu/kifu.mjs +1 -1
- package/dist/esm/components/shogi-player/button.mjs +1 -1
- 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 +1 -0
- package/dist/esm/components/shogi-player/shogi-player.mjs +32 -3
- package/package.json +1 -1
|
@@ -47,5 +47,5 @@ function Kifu({ code, language }) {
|
|
|
47
47
|
setPly(newPly);
|
|
48
48
|
}
|
|
49
49
|
}, [searchParams, id]);
|
|
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 }));
|
|
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 }));
|
|
51
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
|
|
@@ -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,10 +74,39 @@ function ShogiPlayer(props) {
|
|
|
74
74
|
setIsSente(!isSente);
|
|
75
75
|
updateState();
|
|
76
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
|
+
);
|
|
77
106
|
(0, import_react.useEffect)(() => {
|
|
78
107
|
if (props.tesuu !== void 0) {
|
|
79
108
|
handleGoto(props.tesuu);
|
|
80
109
|
}
|
|
81
110
|
}, [props.tesuu]);
|
|
82
|
-
return /* @__PURE__ */ import_react.default.createElement("div", { className: "flex w-fit", tabIndex: 1 }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col" }, /* @__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: "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 && /* @__PURE__ */ import_react.default.createElement("div", null, "\xA0")), /* @__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"))));
|
|
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"))));
|
|
83
112
|
}
|
|
@@ -14,7 +14,7 @@ function Kifu({ code, language }) {
|
|
|
14
14
|
setPly(newPly);
|
|
15
15
|
}
|
|
16
16
|
}, [searchParams, id]);
|
|
17
|
-
return /* @__PURE__ */ React.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ React.createElement(ShogiPlayer, { kifuText: code, tesuu: ply }));
|
|
17
|
+
return /* @__PURE__ */ React.createElement("div", { className: "kifu-block", id }, /* @__PURE__ */ React.createElement(ShogiPlayer, { kifuText: code, tesuu: ply, size: 320 }));
|
|
18
18
|
}
|
|
19
19
|
export {
|
|
20
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
|
|
@@ -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, { useEffect, 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,12 +41,41 @@ function ShogiPlayer(props) {
|
|
|
41
41
|
setIsSente(!isSente);
|
|
42
42
|
updateState();
|
|
43
43
|
};
|
|
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
|
+
);
|
|
44
73
|
useEffect(() => {
|
|
45
74
|
if (props.tesuu !== void 0) {
|
|
46
75
|
handleGoto(props.tesuu);
|
|
47
76
|
}
|
|
48
77
|
}, [props.tesuu]);
|
|
49
|
-
return /* @__PURE__ */ React.createElement("div", { className: "flex w-fit", tabIndex: 1 }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-col" }, /* @__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: "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 && /* @__PURE__ */ React.createElement("div", null, "\xA0")), /* @__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"))));
|
|
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 max-h-40 w-0 min-w-full overflow-auto" }, 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, className: "whitespace-nowrap" }, 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"))));
|
|
50
79
|
}
|
|
51
80
|
export {
|
|
52
81
|
ShogiPlayer as default
|