@hackersheet/next-document-content-kifu 0.1.0-alpha.15 → 0.1.0-alpha.17
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 +2 -2
- package/dist/cjs/components/shogi-player/adapters/kifu-adapter.d.ts +11 -0
- package/dist/cjs/components/shogi-player/adapters/kifu-adapter.js +104 -0
- package/dist/cjs/components/shogi-player/board-renderer.d.ts +60 -0
- package/dist/cjs/components/shogi-player/board-renderer.js +137 -0
- package/dist/cjs/components/shogi-player/button.d.ts +22 -1
- package/dist/cjs/components/shogi-player/button.js +2 -1
- package/dist/cjs/components/shogi-player/canvas-utils.d.ts +29 -0
- package/dist/cjs/components/shogi-player/{index.js → canvas-utils.js} +26 -16
- package/dist/cjs/components/shogi-player/hands-renderer.d.ts +42 -0
- package/dist/cjs/components/shogi-player/hands-renderer.js +86 -0
- package/dist/cjs/components/shogi-player/moves-area.d.ts +24 -3
- package/dist/cjs/components/shogi-player/moves-area.js +15 -20
- package/dist/cjs/components/shogi-player/shogi-board-canvas.d.ts +20 -13
- package/dist/cjs/components/shogi-player/shogi-board-canvas.js +9 -119
- package/dist/cjs/components/shogi-player/shogi-hands-canvas.d.ts +20 -12
- package/dist/cjs/components/shogi-player/shogi-hands-canvas.js +7 -79
- package/dist/cjs/components/shogi-player/shogi-player.d.ts +22 -0
- package/dist/cjs/components/shogi-player/shogi-player.js +51 -41
- package/dist/cjs/components/shogi-player/types.d.ts +169 -0
- package/dist/cjs/components/shogi-player/types.js +16 -0
- package/dist/esm/components/kifu/kifu.mjs +1 -1
- package/dist/esm/components/shogi-player/adapters/kifu-adapter.d.mts +11 -0
- package/dist/esm/components/shogi-player/adapters/kifu-adapter.mjs +80 -0
- package/dist/esm/components/shogi-player/board-renderer.d.mts +60 -0
- package/dist/esm/components/shogi-player/board-renderer.mjs +109 -0
- package/dist/esm/components/shogi-player/button.d.mts +22 -1
- package/dist/esm/components/shogi-player/button.mjs +2 -1
- package/dist/esm/components/shogi-player/canvas-utils.d.mts +29 -0
- package/dist/esm/components/shogi-player/canvas-utils.mjs +22 -0
- package/dist/esm/components/shogi-player/hands-renderer.d.mts +42 -0
- package/dist/esm/components/shogi-player/hands-renderer.mjs +60 -0
- package/dist/esm/components/shogi-player/moves-area.d.mts +24 -3
- package/dist/esm/components/shogi-player/moves-area.mjs +16 -17
- package/dist/esm/components/shogi-player/shogi-board-canvas.d.mts +20 -13
- package/dist/esm/components/shogi-player/shogi-board-canvas.mjs +12 -116
- package/dist/esm/components/shogi-player/shogi-hands-canvas.d.mts +20 -12
- package/dist/esm/components/shogi-player/shogi-hands-canvas.mjs +4 -76
- package/dist/esm/components/shogi-player/shogi-player.d.mts +22 -0
- package/dist/esm/components/shogi-player/shogi-player.mjs +52 -42
- package/dist/esm/components/shogi-player/types.d.mts +169 -0
- package/dist/esm/components/shogi-player/types.mjs +0 -0
- package/package.json +3 -3
- package/dist/cjs/components/shogi-player/index.d.ts +0 -2
- package/dist/esm/components/shogi-player/index.d.mts +0 -2
- package/dist/esm/components/shogi-player/index.mjs +0 -4
|
@@ -29,13 +29,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
30
|
var moves_area_exports = {};
|
|
31
31
|
__export(moves_area_exports, {
|
|
32
|
-
|
|
32
|
+
default: () => MovesArea
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(moves_area_exports);
|
|
35
|
-
var import_json_kifu_format = require("json-kifu-format");
|
|
36
35
|
var import_react = __toESM(require("react"));
|
|
37
36
|
function MovesArea(props) {
|
|
38
|
-
const moves = props.moves;
|
|
39
37
|
const scrollRef = (0, import_react.useRef)(null);
|
|
40
38
|
const containerRef = (0, import_react.useRef)(null);
|
|
41
39
|
(0, import_react.useEffect)(() => {
|
|
@@ -49,30 +47,27 @@ function MovesArea(props) {
|
|
|
49
47
|
});
|
|
50
48
|
}
|
|
51
49
|
}, [props.tesuu]);
|
|
52
|
-
const
|
|
50
|
+
const initialCurrent = 0 === props.tesuu ? " bg-amber-600" : "";
|
|
53
51
|
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(
|
|
54
52
|
"div",
|
|
55
53
|
{
|
|
56
54
|
onClick: () => props.onTesuuChange && props.onTesuuChange(0),
|
|
57
|
-
className: "col-span-500 grid grid-cols-subgrid gap-2 py-1 px-2 cursor-pointer hover:bg-amber-100" +
|
|
55
|
+
className: "col-span-500 grid grid-cols-subgrid gap-2 py-1 px-2 cursor-pointer hover:bg-amber-100" + initialCurrent
|
|
58
56
|
},
|
|
59
|
-
/* @__PURE__ */ import_react.default.createElement("div", null, 0 === props.tesuu && /* @__PURE__ */ import_react.default.createElement("
|
|
60
|
-
/* @__PURE__ */ import_react.default.createElement("div", null,
|
|
61
|
-
),
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
return /* @__PURE__ */ import_react.default.createElement(
|
|
57
|
+
/* @__PURE__ */ import_react.default.createElement("div", null, 0 === props.tesuu && /* @__PURE__ */ import_react.default.createElement("span", { ref: scrollRef, className: "sr-only", "aria-hidden": "true" })),
|
|
58
|
+
/* @__PURE__ */ import_react.default.createElement("div", null, props.readableMoves[0])
|
|
59
|
+
), props.readableMoves.slice(1).map((moveText, index) => {
|
|
60
|
+
const moveIndex = index + 1;
|
|
61
|
+
const moveCurrent = moveIndex === props.tesuu ? " bg-amber-600" : "";
|
|
62
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
65
63
|
"div",
|
|
66
64
|
{
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
key: moveIndex,
|
|
66
|
+
className: "col-span-500 grid grid-cols-subgrid border-black gap-2 border-t py-1 px-2 cursor-pointer hover:bg-amber-100" + moveCurrent,
|
|
67
|
+
onClick: () => props.onTesuuChange && props.onTesuuChange(moveIndex)
|
|
69
68
|
},
|
|
70
|
-
/* @__PURE__ */ import_react.default.createElement("div", { className: "flex" },
|
|
71
|
-
/* @__PURE__ */ import_react.default.createElement("div", null,
|
|
72
|
-
)
|
|
69
|
+
/* @__PURE__ */ import_react.default.createElement("div", { className: "flex" }, moveIndex === props.tesuu && /* @__PURE__ */ import_react.default.createElement("span", { ref: scrollRef, className: "sr-only", "aria-hidden": "true" }), /* @__PURE__ */ import_react.default.createElement("div", { className: "tabular-nums text-right flex-auto" }, moveIndex)),
|
|
70
|
+
/* @__PURE__ */ import_react.default.createElement("div", null, moveText)
|
|
71
|
+
);
|
|
73
72
|
})));
|
|
74
73
|
}
|
|
75
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
76
|
-
0 && (module.exports = {
|
|
77
|
-
MovesArea
|
|
78
|
-
});
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import { IMoveMoveFormat } from 'json-kifu-format/dist/src/Formats';
|
|
2
1
|
import React from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import { ShogiBoardCanvasProps } from './types.js';
|
|
3
|
+
import 'shogi.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Canvas component for rendering the shogi board with pieces
|
|
7
|
+
*
|
|
8
|
+
* @component
|
|
9
|
+
* @param props - Component props
|
|
10
|
+
* @returns Canvas element displaying the shogi board
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <ShogiBoardCanvas
|
|
15
|
+
* size={360}
|
|
16
|
+
* pieces={boardState}
|
|
17
|
+
* isSente={true}
|
|
18
|
+
* currentMove={lastMove}
|
|
19
|
+
* />
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare const ShogiBoardCanvas: React.FC<ShogiBoardCanvasProps>;
|
|
16
23
|
|
|
17
24
|
export { ShogiBoardCanvas as default };
|
|
@@ -32,119 +32,9 @@ __export(shogi_board_canvas_exports, {
|
|
|
32
32
|
default: () => shogi_board_canvas_default
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(shogi_board_canvas_exports);
|
|
35
|
-
var import_json_kifu_format = require("json-kifu-format");
|
|
36
35
|
var import_react = __toESM(require("react"));
|
|
37
|
-
var
|
|
38
|
-
|
|
39
|
-
const dpr = window.devicePixelRatio || 1;
|
|
40
|
-
return { width: size * dpr, height: size * dpr, dpr };
|
|
41
|
-
};
|
|
42
|
-
const getBoardLayout = (size) => {
|
|
43
|
-
const margin = size * 0.06;
|
|
44
|
-
const boardSize = size - margin * 2;
|
|
45
|
-
const cell = boardSize / 9;
|
|
46
|
-
return { margin, boardSize, cell };
|
|
47
|
-
};
|
|
48
|
-
const drawBackground = (ctx, size, boardColor) => {
|
|
49
|
-
ctx.fillStyle = boardColor;
|
|
50
|
-
ctx.fillRect(0, 0, size, size);
|
|
51
|
-
};
|
|
52
|
-
const drawBoard = (ctx, margin, boardSize, lineColor) => {
|
|
53
|
-
ctx.strokeStyle = lineColor;
|
|
54
|
-
ctx.lineWidth = 2;
|
|
55
|
-
ctx.strokeRect(margin, margin, boardSize, boardSize);
|
|
56
|
-
ctx.lineWidth = 1;
|
|
57
|
-
Array.from({ length: 8 }).forEach((_, i) => {
|
|
58
|
-
const offset = (i + 1) * (boardSize / 9);
|
|
59
|
-
ctx.beginPath();
|
|
60
|
-
ctx.moveTo(margin + offset, margin);
|
|
61
|
-
ctx.lineTo(margin + offset, margin + boardSize);
|
|
62
|
-
ctx.stroke();
|
|
63
|
-
ctx.beginPath();
|
|
64
|
-
ctx.moveTo(margin, margin + offset);
|
|
65
|
-
ctx.lineTo(margin + boardSize, margin + offset);
|
|
66
|
-
ctx.stroke();
|
|
67
|
-
});
|
|
68
|
-
};
|
|
69
|
-
const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente) => {
|
|
70
|
-
ctx.textAlign = "center";
|
|
71
|
-
ctx.textBaseline = "middle";
|
|
72
|
-
pieces.forEach((row, rowIndex) => {
|
|
73
|
-
row.forEach((piece, colIndex) => {
|
|
74
|
-
if (!piece) return;
|
|
75
|
-
const x = isSente ? 8 - rowIndex : rowIndex;
|
|
76
|
-
const y = isSente ? colIndex : 8 - colIndex;
|
|
77
|
-
const px = margin + x * cell + cell / 2;
|
|
78
|
-
const py = margin + y * cell + cell / 2;
|
|
79
|
-
ctx.save();
|
|
80
|
-
ctx.translate(px, py);
|
|
81
|
-
if (isSente && piece.color === import_shogi.Color.White) {
|
|
82
|
-
ctx.rotate(Math.PI);
|
|
83
|
-
} else if (!isSente && piece.color === import_shogi.Color.Black) {
|
|
84
|
-
ctx.rotate(Math.PI);
|
|
85
|
-
}
|
|
86
|
-
const kan = import_json_kifu_format.JKFPlayer.kindToKan(piece.kind);
|
|
87
|
-
ctx.fillStyle = "#000";
|
|
88
|
-
if (kan.length === 2) {
|
|
89
|
-
const baseFontSize = cell * fontSizeRatio * 0.5;
|
|
90
|
-
ctx.font = `${baseFontSize}px ${fontFamily}`;
|
|
91
|
-
const scaleX = 2;
|
|
92
|
-
const scaleY = 1;
|
|
93
|
-
const offsetY = cell * 0.18;
|
|
94
|
-
ctx.save();
|
|
95
|
-
ctx.scale(scaleX, scaleY);
|
|
96
|
-
ctx.fillText(kan[0], 0 / scaleX, -offsetY / scaleY);
|
|
97
|
-
ctx.restore();
|
|
98
|
-
ctx.save();
|
|
99
|
-
ctx.scale(scaleX, scaleY);
|
|
100
|
-
ctx.fillText(kan[1], 0 / scaleX, offsetY / scaleY);
|
|
101
|
-
ctx.restore();
|
|
102
|
-
} else {
|
|
103
|
-
const fontSize = cell * fontSizeRatio;
|
|
104
|
-
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
105
|
-
ctx.fillText(kan, 0, 0);
|
|
106
|
-
}
|
|
107
|
-
ctx.restore();
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
};
|
|
111
|
-
const drawCoordinates = (ctx, margin, boardSize, cell, fontFamily, isSente) => {
|
|
112
|
-
ctx.fillStyle = "#000";
|
|
113
|
-
ctx.font = `${cell * 0.35}px ${fontFamily}`;
|
|
114
|
-
ctx.textAlign = "center";
|
|
115
|
-
ctx.textBaseline = "middle";
|
|
116
|
-
Array.from({ length: 9 }).forEach((_, i) => {
|
|
117
|
-
const x = margin + i * cell + cell / 2;
|
|
118
|
-
const y = margin / 2;
|
|
119
|
-
const label = isSente ? import_json_kifu_format.JKFPlayer.numToZen(9 - i) : import_json_kifu_format.JKFPlayer.numToZen(i + 1);
|
|
120
|
-
ctx.fillText(label, x, y);
|
|
121
|
-
});
|
|
122
|
-
Array.from({ length: 9 }).forEach((_, i) => {
|
|
123
|
-
const x = margin + boardSize + margin / 2;
|
|
124
|
-
const y = margin + i * cell + cell / 2;
|
|
125
|
-
const label = isSente ? import_json_kifu_format.JKFPlayer.numToKan(i + 1) : import_json_kifu_format.JKFPlayer.numToKan(9 - i);
|
|
126
|
-
ctx.fillText(label, x, y);
|
|
127
|
-
});
|
|
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
|
-
};
|
|
36
|
+
var import_board_renderer = require("./board-renderer");
|
|
37
|
+
var import_canvas_utils = require("./canvas-utils");
|
|
148
38
|
const ShogiBoardCanvas = ({
|
|
149
39
|
size = 360,
|
|
150
40
|
boardColor = "#f9d27a",
|
|
@@ -161,19 +51,19 @@ const ShogiBoardCanvas = ({
|
|
|
161
51
|
if (!canvas) return;
|
|
162
52
|
const ctx = canvas.getContext("2d");
|
|
163
53
|
if (!ctx) return;
|
|
164
|
-
const { width, height, dpr } = getCanvasDimensions(size);
|
|
54
|
+
const { width, height, dpr } = (0, import_canvas_utils.getCanvasDimensions)(size);
|
|
165
55
|
canvas.width = width;
|
|
166
56
|
canvas.height = height;
|
|
167
57
|
canvas.style.width = `${size}px`;
|
|
168
58
|
canvas.style.height = `${size}px`;
|
|
169
59
|
ctx.scale(dpr, dpr);
|
|
170
60
|
ctx.clearRect(0, 0, size, size);
|
|
171
|
-
const { margin, boardSize, cell } = getBoardLayout(size);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
drawHighlightedCell(ctx, margin, cell, isSente, currentMove);
|
|
175
|
-
|
|
176
|
-
|
|
61
|
+
const { margin, boardSize, cell } = (0, import_canvas_utils.getBoardLayout)(size);
|
|
62
|
+
(0, import_board_renderer.drawBoardBackground)(ctx, size, boardColor);
|
|
63
|
+
(0, import_board_renderer.drawBoardGrid)(ctx, margin, boardSize, lineColor);
|
|
64
|
+
(0, import_board_renderer.drawHighlightedCell)(ctx, margin, cell, isSente, currentMove);
|
|
65
|
+
(0, import_board_renderer.drawBoardPieces)(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente);
|
|
66
|
+
(0, import_board_renderer.drawBoardCoordinates)(ctx, margin, boardSize, cell, fontFamily, isSente);
|
|
177
67
|
}, [size, boardColor, lineColor, fontFamily, fontSizeRatio, pieces, isSente, currentMove]);
|
|
178
68
|
return /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef, width: size, height: size });
|
|
179
69
|
};
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ShogiHandsCanvasProps } from './types.js';
|
|
3
|
+
import 'shogi.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Canvas component for rendering captured pieces (hands)
|
|
7
|
+
*
|
|
8
|
+
* @component
|
|
9
|
+
* @param props - Component props
|
|
10
|
+
* @returns Canvas element displaying the hands (captured pieces area)
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <ShogiHandsCanvas
|
|
15
|
+
* size={360}
|
|
16
|
+
* hands={capturedPieces}
|
|
17
|
+
* isSente={true}
|
|
18
|
+
* isTop={true}
|
|
19
|
+
* />
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare const ShogiHandsCanvas: React.FC<ShogiHandsCanvasProps>;
|
|
15
23
|
|
|
16
24
|
export { ShogiHandsCanvas as default };
|
|
@@ -32,80 +32,9 @@ __export(shogi_hands_canvas_exports, {
|
|
|
32
32
|
default: () => shogi_hands_canvas_default
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(shogi_hands_canvas_exports);
|
|
35
|
-
var import_json_kifu_format = require("json-kifu-format");
|
|
36
35
|
var import_react = __toESM(require("react"));
|
|
37
|
-
var
|
|
38
|
-
|
|
39
|
-
const dpr = window.devicePixelRatio || 1;
|
|
40
|
-
return { width: size * dpr, height: size * dpr, dpr };
|
|
41
|
-
};
|
|
42
|
-
const getHandsLayout = (size) => {
|
|
43
|
-
const margin = size * 0.06;
|
|
44
|
-
const boardSize = size - margin * 2;
|
|
45
|
-
const cellSize = boardSize / 9;
|
|
46
|
-
const handsHeight = cellSize + margin + 2;
|
|
47
|
-
return { margin, handsHeight, boardSize, cellSize };
|
|
48
|
-
};
|
|
49
|
-
const drawBackground = (ctx, width, height, color) => {
|
|
50
|
-
ctx.fillStyle = color;
|
|
51
|
-
ctx.fillRect(0, 0, width, height);
|
|
52
|
-
};
|
|
53
|
-
const drawHandsFrame = (ctx, x, y, width, height, lineColor, isTop) => {
|
|
54
|
-
ctx.strokeStyle = lineColor;
|
|
55
|
-
ctx.lineWidth = 2;
|
|
56
|
-
if (isTop) {
|
|
57
|
-
ctx.strokeRect(x, y, width, height);
|
|
58
|
-
} else {
|
|
59
|
-
ctx.strokeRect(x, 2, width, height);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
const drawCoordinates = (ctx, fontSize, fontFamily) => {
|
|
63
|
-
ctx.fillStyle = "#000";
|
|
64
|
-
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
65
|
-
ctx.textAlign = "center";
|
|
66
|
-
ctx.textBaseline = "middle";
|
|
67
|
-
};
|
|
68
|
-
const drawPieces = (ctx, hands, margin, boardSize, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
|
|
69
|
-
ctx.textAlign = "center";
|
|
70
|
-
ctx.textBaseline = "middle";
|
|
71
|
-
const pieces = isSente && isTop || !isSente && !isTop ? hands[import_shogi.Color.White] : hands[import_shogi.Color.Black];
|
|
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;
|
|
88
|
-
const py = isTop ? margin + cell / 2 : cell / 2 + 2;
|
|
89
|
-
ctx.save();
|
|
90
|
-
ctx.translate(px, py);
|
|
91
|
-
if (isSente && color === import_shogi.Color.White) {
|
|
92
|
-
ctx.rotate(Math.PI);
|
|
93
|
-
} else if (!isSente && color === import_shogi.Color.Black) {
|
|
94
|
-
ctx.rotate(Math.PI);
|
|
95
|
-
}
|
|
96
|
-
const fontSize = cell * fontSizeRatio;
|
|
97
|
-
const kan = import_json_kifu_format.JKFPlayer.kindToKan(kind);
|
|
98
|
-
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
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
|
-
}
|
|
106
|
-
ctx.restore();
|
|
107
|
-
});
|
|
108
|
-
};
|
|
36
|
+
var import_canvas_utils = require("./canvas-utils");
|
|
37
|
+
var import_hands_renderer = require("./hands-renderer");
|
|
109
38
|
const ShogiHandsCanvas = ({
|
|
110
39
|
size = 360,
|
|
111
40
|
boardColor = "#f9d27a",
|
|
@@ -122,18 +51,17 @@ const ShogiHandsCanvas = ({
|
|
|
122
51
|
if (!canvas) return;
|
|
123
52
|
const ctx = canvas.getContext("2d");
|
|
124
53
|
if (!ctx) return;
|
|
125
|
-
const { margin, handsHeight, boardSize, cellSize } = getHandsLayout(size);
|
|
126
|
-
const { width: canvasWidth, dpr } = getCanvasDimensions(size);
|
|
54
|
+
const { margin, handsHeight, boardSize, cellSize } = (0, import_canvas_utils.getHandsLayout)(size);
|
|
55
|
+
const { width: canvasWidth, dpr } = (0, import_canvas_utils.getCanvasDimensions)(size);
|
|
127
56
|
canvas.width = canvasWidth;
|
|
128
57
|
canvas.height = handsHeight * dpr;
|
|
129
58
|
canvas.style.width = `${size}px`;
|
|
130
59
|
canvas.style.height = `${handsHeight}px`;
|
|
131
60
|
ctx.scale(dpr, dpr);
|
|
132
61
|
ctx.clearRect(0, 0, size, handsHeight);
|
|
133
|
-
|
|
134
|
-
drawHandsFrame(ctx, margin, margin, boardSize, cellSize, lineColor, isTop);
|
|
135
|
-
|
|
136
|
-
drawPieces(ctx, hands, margin, boardSize, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
|
|
62
|
+
(0, import_hands_renderer.drawHandsBackground)(ctx, size, handsHeight, boardColor);
|
|
63
|
+
(0, import_hands_renderer.drawHandsFrame)(ctx, margin, margin, boardSize, cellSize, lineColor, isTop);
|
|
64
|
+
(0, import_hands_renderer.drawHandsPieces)(ctx, hands, margin, boardSize, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
|
|
137
65
|
}, [size, boardColor, lineColor, fontFamily, fontSizeRatio, isSente, hands, isTop]);
|
|
138
66
|
return /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef });
|
|
139
67
|
};
|
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { KifuAdapterFactory } from './types.js';
|
|
3
|
+
import 'shogi.js';
|
|
2
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Props for the ShogiPlayer component
|
|
7
|
+
* @property kifuText - KIF format kifu (game record) text
|
|
8
|
+
* @property size - Canvas size in CSS pixels (default: 360)
|
|
9
|
+
* @property tesuu - Initial move number to display
|
|
10
|
+
* @property adapterFactory - Factory function for creating KifuAdapter (for DI/testing)
|
|
11
|
+
*/
|
|
3
12
|
type ShogiPlayerProps = {
|
|
4
13
|
kifuText: string;
|
|
5
14
|
size?: number;
|
|
6
15
|
tesuu?: number;
|
|
16
|
+
adapterFactory?: KifuAdapterFactory;
|
|
7
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Shogi game viewer component with board, hands, and move navigation
|
|
20
|
+
*
|
|
21
|
+
* @component
|
|
22
|
+
* @param props - Component props
|
|
23
|
+
* @returns Interactive shogi player with keyboard and button navigation
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* <ShogiPlayer kifuText={kifString} tesuu={10} />
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
8
30
|
declare function ShogiPlayer(props: ShogiPlayerProps): React.JSX.Element;
|
|
9
31
|
|
|
10
32
|
export { type ShogiPlayerProps, ShogiPlayer as default };
|
|
@@ -32,48 +32,33 @@ __export(shogi_player_exports, {
|
|
|
32
32
|
default: () => ShogiPlayer
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(shogi_player_exports);
|
|
35
|
-
var import_json_kifu_format = require("json-kifu-format");
|
|
36
35
|
var import_react = __toESM(require("react"));
|
|
36
|
+
var import_kifu_adapter = require("./adapters/kifu-adapter");
|
|
37
37
|
var import_button = __toESM(require("./button"));
|
|
38
|
-
var import_moves_area = require("./moves-area");
|
|
38
|
+
var import_moves_area = __toESM(require("./moves-area"));
|
|
39
39
|
var import_shogi_board_canvas = __toESM(require("./shogi-board-canvas"));
|
|
40
40
|
var import_shogi_hands_canvas = __toESM(require("./shogi-hands-canvas"));
|
|
41
41
|
function ShogiPlayer(props) {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const [
|
|
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());
|
|
42
|
+
const factory = props.adapterFactory ?? import_kifu_adapter.createKifuAdapter;
|
|
43
|
+
const adapterRef = (0, import_react.useRef)(factory(props.kifuText));
|
|
44
|
+
const [gameState, setGameState] = (0, import_react.useState)(() => adapterRef.current.getState());
|
|
48
45
|
const [isSente, setIsSente] = (0, import_react.useState)(true);
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
};
|
|
46
|
+
const size = props.size ?? 360;
|
|
47
|
+
const handleForward = (0, import_react.useCallback)(() => {
|
|
48
|
+
const newState = adapterRef.current.forward();
|
|
49
|
+
setGameState(newState);
|
|
50
|
+
}, []);
|
|
51
|
+
const handleBackward = (0, import_react.useCallback)(() => {
|
|
52
|
+
const newState = adapterRef.current.backward();
|
|
53
|
+
setGameState(newState);
|
|
54
|
+
}, []);
|
|
55
|
+
const handleGoto = (0, import_react.useCallback)((tesuu) => {
|
|
56
|
+
const newState = adapterRef.current.goto(tesuu);
|
|
57
|
+
setGameState(newState);
|
|
58
|
+
}, []);
|
|
59
|
+
const handleToggle = (0, import_react.useCallback)(() => {
|
|
60
|
+
setIsSente((prev) => !prev);
|
|
61
|
+
}, []);
|
|
77
62
|
const handleKeydown = (0, import_react.useCallback)(
|
|
78
63
|
(event) => {
|
|
79
64
|
event.preventDefault();
|
|
@@ -85,7 +70,7 @@ function ShogiPlayer(props) {
|
|
|
85
70
|
break;
|
|
86
71
|
case "Down":
|
|
87
72
|
case "ArrowDown":
|
|
88
|
-
handleGoto(
|
|
73
|
+
handleGoto(gameState.maxMoveIndex);
|
|
89
74
|
break;
|
|
90
75
|
case "Left":
|
|
91
76
|
case "ArrowLeft":
|
|
@@ -97,16 +82,41 @@ function ShogiPlayer(props) {
|
|
|
97
82
|
handleForward();
|
|
98
83
|
break;
|
|
99
84
|
case "r":
|
|
100
|
-
|
|
85
|
+
handleToggle();
|
|
101
86
|
break;
|
|
102
87
|
}
|
|
103
88
|
},
|
|
104
|
-
[
|
|
89
|
+
[gameState.maxMoveIndex, handleGoto, handleBackward, handleForward, handleToggle]
|
|
105
90
|
);
|
|
106
91
|
(0, import_react.useEffect)(() => {
|
|
107
92
|
if (props.tesuu !== void 0) {
|
|
108
93
|
handleGoto(props.tesuu);
|
|
109
94
|
}
|
|
110
|
-
}, [props.tesuu]);
|
|
111
|
-
|
|
95
|
+
}, [props.tesuu, handleGoto]);
|
|
96
|
+
(0, import_react.useEffect)(() => {
|
|
97
|
+
return () => {
|
|
98
|
+
adapterRef.current.dispose();
|
|
99
|
+
};
|
|
100
|
+
}, []);
|
|
101
|
+
const topPlayerName = isSente ? `\u2616 ${gameState.header.goteName}` : `\u2617 ${gameState.header.senteName}`;
|
|
102
|
+
const bottomPlayerName = isSente ? `\u2617 ${gameState.header.senteName}` : `\u2616 ${gameState.header.goteName}`;
|
|
103
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
104
|
+
"div",
|
|
105
|
+
{
|
|
106
|
+
className: "flex flex-col sm:flex-row w-fit",
|
|
107
|
+
tabIndex: 0,
|
|
108
|
+
role: "application",
|
|
109
|
+
"aria-label": "Shogi player - use arrow keys to navigate, space or right arrow to move forward, left arrow to move backward, 'r' to flip board",
|
|
110
|
+
onKeyDown: handleKeydown
|
|
111
|
+
},
|
|
112
|
+
/* @__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" }, topPlayerName), /* @__PURE__ */ import_react.default.createElement(import_shogi_hands_canvas.default, { size, hands: gameState.hands, isSente, isTop: true }), /* @__PURE__ */ import_react.default.createElement(import_shogi_board_canvas.default, { size, pieces: gameState.board, isSente, currentMove: gameState.currentMove }), /* @__PURE__ */ import_react.default.createElement(import_shogi_hands_canvas.default, { size, hands: gameState.hands, isSente, isTop: false }), /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-[#f9d27a] text-black text-xs p-1" }, bottomPlayerName)),
|
|
113
|
+
/* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col sm:w-fit bg-[#f9d27a] p-4 gap-4" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hidden sm:block flex-1 relative w-full" }, /* @__PURE__ */ import_react.default.createElement(
|
|
114
|
+
import_moves_area.default,
|
|
115
|
+
{
|
|
116
|
+
readableMoves: gameState.readableMoves,
|
|
117
|
+
tesuu: gameState.currentMoveIndex,
|
|
118
|
+
onTesuuChange: handleGoto
|
|
119
|
+
}
|
|
120
|
+
)), /* @__PURE__ */ import_react.default.createElement("div", { className: "hidden sm:block text-black border-black border-2 p-1 text-xs max-h-40 w-0 min-w-full overflow-auto" }, gameState.comments.map((comment, index) => /* @__PURE__ */ import_react.default.createElement("div", { key: index }, comment)), gameState.comments.length === 0 && gameState.currentMoveIndex !== 0 && /* @__PURE__ */ import_react.default.createElement("div", null, "\xA0"), gameState.currentMoveIndex === 0 && Object.entries(gameState.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(gameState.maxMoveIndex) }, "\u6700\u5F8C"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handleToggle }, "\u53CD\u8EE2")))
|
|
121
|
+
);
|
|
112
122
|
}
|