react-chess-explorer 0.0.1
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/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/features/explorer/ExplorerPlaceholder.d.ts +10 -0
- package/dist/features/explorer/GameReplayTrainer.d.ts +21 -0
- package/dist/features/explorer/PositionReferenceExplorer.d.ts +4 -0
- package/dist/features/explorer/components/DefaultBoardNav.d.ts +8 -0
- package/dist/features/explorer/components/EloRangeFilter.d.ts +9 -0
- package/dist/features/explorer/components/ExplorerStatusBanner.d.ts +5 -0
- package/dist/features/explorer/components/GamesTable.d.ts +6 -0
- package/dist/features/explorer/components/LineHeader.d.ts +4 -0
- package/dist/features/explorer/components/MoveStatsTable.d.ts +7 -0
- package/dist/features/explorer/components/PositionGamesPanel.d.ts +15 -0
- package/dist/features/explorer/components/explorerStyles.d.ts +26 -0
- package/dist/features/explorer/constants.d.ts +4 -0
- package/dist/features/explorer/core/PositionReferenceExplorerCore.d.ts +2 -0
- package/dist/features/explorer/core/renderProps.d.ts +77 -0
- package/dist/features/explorer/defaults/DefaultReferenceLayout.d.ts +3 -0
- package/dist/features/explorer/defaults/DefaultReferencePanel.d.ts +10 -0
- package/dist/features/explorer/defaults/DefaultVariationsStrip.d.ts +3 -0
- package/dist/features/explorer/defaults/defaultRenderers.d.ts +8 -0
- package/dist/features/explorer/defaults/index.d.ts +7 -0
- package/dist/features/explorer/gameReplayUtils.d.ts +6 -0
- package/dist/features/explorer/hooks/useGameReplayTraining.d.ts +22 -0
- package/dist/features/explorer/hooks/usePositionHistory.d.ts +19 -0
- package/dist/features/explorer/hooks/usePositionReferenceData.d.ts +41 -0
- package/dist/features/explorer/hooks/useVariationLines.d.ts +16 -0
- package/dist/features/explorer/index.d.ts +15 -0
- package/dist/features/explorer/mocks.d.ts +4 -0
- package/dist/features/explorer/positionUtils.d.ts +21 -0
- package/dist/features/explorer/referenceLayout.d.ts +13 -0
- package/dist/features/explorer/types.d.ts +96 -0
- package/dist/features/explorer/variationLines.d.ts +5 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +1105 -0
- package/dist/index.js +1133 -0
- package/package.json +45 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var reactChessCore = require('react-chess-core');
|
|
5
|
+
var reactChessboard = require('react-chessboard');
|
|
6
|
+
var chess_js = require('chess.js');
|
|
7
|
+
var react = require('react');
|
|
8
|
+
|
|
9
|
+
/** Standard start position — placeholder until game replay is implemented. */
|
|
10
|
+
const EXPLORER_START_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
|
11
|
+
/** Delay between plies when animating a clicked variation line. */
|
|
12
|
+
const VARIATION_LINE_STEP_MS = 500;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Scaffold UI — proves core wiring and Rollup build.
|
|
16
|
+
* Replace with game replay once requirements are defined.
|
|
17
|
+
*/
|
|
18
|
+
const ExplorerPlaceholder = ({ theme = "dark", boardWidth = 400, }) => (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, children: jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, position: EXPLORER_START_FEN, checkSquare: "", hintSquare: null, incorrectMoveSquare: null }), jsxRuntime.jsx("p", { style: {
|
|
19
|
+
margin: 0,
|
|
20
|
+
fontFamily: "system-ui, sans-serif",
|
|
21
|
+
fontSize: 14,
|
|
22
|
+
opacity: 0.8,
|
|
23
|
+
}, children: "react-chess-explorer (scaffold) \u2014 game replay UI not implemented yet." })] }) }));
|
|
24
|
+
|
|
25
|
+
const DEFAULT_REFERENCE_LAYOUT = {
|
|
26
|
+
boardWidth: 560,
|
|
27
|
+
boardColumn: "minmax(420px, 1.45fr)",
|
|
28
|
+
referenceColumn: "minmax(380px, 1fr)",
|
|
29
|
+
columnGap: 0,
|
|
30
|
+
minHeight: "calc(100vh - 120px)",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const panelStyle = {
|
|
34
|
+
fontFamily: "system-ui, sans-serif",
|
|
35
|
+
fontSize: 13,
|
|
36
|
+
};
|
|
37
|
+
const tableStyle = {
|
|
38
|
+
width: "100%",
|
|
39
|
+
borderCollapse: "collapse",
|
|
40
|
+
fontSize: 12,
|
|
41
|
+
};
|
|
42
|
+
const thStyle = {
|
|
43
|
+
textAlign: "left",
|
|
44
|
+
padding: "4px 8px",
|
|
45
|
+
borderBottom: "1px solid rgba(128,128,128,0.35)",
|
|
46
|
+
fontWeight: 600,
|
|
47
|
+
whiteSpace: "nowrap",
|
|
48
|
+
};
|
|
49
|
+
const tdStyle = {
|
|
50
|
+
padding: "3px 8px",
|
|
51
|
+
borderBottom: "1px solid rgba(128,128,128,0.12)",
|
|
52
|
+
whiteSpace: "nowrap",
|
|
53
|
+
};
|
|
54
|
+
const sectionTitleStyle = {
|
|
55
|
+
margin: 0,
|
|
56
|
+
fontSize: 12,
|
|
57
|
+
fontWeight: 600,
|
|
58
|
+
padding: "6px 8px",
|
|
59
|
+
borderBottom: "1px solid rgba(128,128,128,0.2)",
|
|
60
|
+
background: "rgba(128,128,128,0.06)",
|
|
61
|
+
};
|
|
62
|
+
/** Full-width shell: board | reference — no wrap, no footer below. */
|
|
63
|
+
const referenceShellStyle = {
|
|
64
|
+
display: "grid",
|
|
65
|
+
gridTemplateColumns: `${DEFAULT_REFERENCE_LAYOUT.boardColumn} ${DEFAULT_REFERENCE_LAYOUT.referenceColumn}`,
|
|
66
|
+
width: "100%",
|
|
67
|
+
height: "100%",
|
|
68
|
+
minHeight: DEFAULT_REFERENCE_LAYOUT.minHeight,
|
|
69
|
+
overflow: "hidden",
|
|
70
|
+
boxSizing: "border-box",
|
|
71
|
+
};
|
|
72
|
+
const boardColumnStyle = {
|
|
73
|
+
display: "flex",
|
|
74
|
+
flexDirection: "column",
|
|
75
|
+
alignItems: "center",
|
|
76
|
+
justifyContent: "flex-start",
|
|
77
|
+
padding: "8px 12px",
|
|
78
|
+
minHeight: 0,
|
|
79
|
+
overflow: "auto",
|
|
80
|
+
boxSizing: "border-box",
|
|
81
|
+
gap: 8,
|
|
82
|
+
};
|
|
83
|
+
const boardNavStyle = {
|
|
84
|
+
display: "flex",
|
|
85
|
+
alignItems: "center",
|
|
86
|
+
justifyContent: "center",
|
|
87
|
+
gap: 8,
|
|
88
|
+
width: "100%",
|
|
89
|
+
};
|
|
90
|
+
const boardNavButtonStyle = {
|
|
91
|
+
minWidth: 44,
|
|
92
|
+
padding: "6px 14px",
|
|
93
|
+
fontSize: 16,
|
|
94
|
+
lineHeight: 1,
|
|
95
|
+
cursor: "pointer",
|
|
96
|
+
border: "1px solid rgba(128,128,128,0.4)",
|
|
97
|
+
borderRadius: 4,
|
|
98
|
+
background: "rgba(128,128,128,0.1)",
|
|
99
|
+
};
|
|
100
|
+
const referencePanelStyle = {
|
|
101
|
+
display: "flex",
|
|
102
|
+
flexDirection: "column",
|
|
103
|
+
minHeight: 0,
|
|
104
|
+
height: "100%",
|
|
105
|
+
overflow: "hidden",
|
|
106
|
+
borderLeft: "1px solid rgba(128,128,128,0.3)",
|
|
107
|
+
boxSizing: "border-box",
|
|
108
|
+
};
|
|
109
|
+
const referenceTabBarStyle = {
|
|
110
|
+
flex: "0 0 auto",
|
|
111
|
+
display: "flex",
|
|
112
|
+
alignItems: "center",
|
|
113
|
+
gap: 4,
|
|
114
|
+
padding: "6px 10px",
|
|
115
|
+
borderBottom: "1px solid rgba(128,128,128,0.35)",
|
|
116
|
+
background: "rgba(128,128,128,0.08)",
|
|
117
|
+
fontSize: 12,
|
|
118
|
+
};
|
|
119
|
+
const referenceTabLabelStyle = {
|
|
120
|
+
padding: "2px 8px",
|
|
121
|
+
};
|
|
122
|
+
const moveStatsSectionStyle = {
|
|
123
|
+
flex: "0 1 auto",
|
|
124
|
+
maxHeight: "38%",
|
|
125
|
+
minHeight: 120,
|
|
126
|
+
overflow: "auto",
|
|
127
|
+
borderBottom: "1px solid rgba(128,128,128,0.2)",
|
|
128
|
+
};
|
|
129
|
+
const variationsStripStyle = {
|
|
130
|
+
flex: "0 0 auto",
|
|
131
|
+
display: "flex",
|
|
132
|
+
alignItems: "center",
|
|
133
|
+
gap: 12,
|
|
134
|
+
padding: "6px 10px",
|
|
135
|
+
borderBottom: "1px solid rgba(128,128,128,0.2)",
|
|
136
|
+
fontSize: 12,
|
|
137
|
+
background: "rgba(128,128,128,0.04)",
|
|
138
|
+
};
|
|
139
|
+
const variationsTabStyle = {
|
|
140
|
+
cursor: "default",
|
|
141
|
+
};
|
|
142
|
+
const gamesSectionStyle = {
|
|
143
|
+
flex: "1 1 auto",
|
|
144
|
+
display: "flex",
|
|
145
|
+
flexDirection: "column",
|
|
146
|
+
minHeight: 0,
|
|
147
|
+
overflow: "hidden",
|
|
148
|
+
};
|
|
149
|
+
const gamesHeaderStyle = {
|
|
150
|
+
flex: "0 0 auto",
|
|
151
|
+
padding: "6px 10px",
|
|
152
|
+
fontSize: 12,
|
|
153
|
+
borderBottom: "1px solid rgba(128,128,128,0.15)",
|
|
154
|
+
background: "rgba(128,128,128,0.05)",
|
|
155
|
+
};
|
|
156
|
+
const gamesScrollStyle = {
|
|
157
|
+
flex: "1 1 auto",
|
|
158
|
+
overflow: "auto",
|
|
159
|
+
minHeight: 0,
|
|
160
|
+
};
|
|
161
|
+
const gamesToolbarStyle = {
|
|
162
|
+
flex: "0 0 auto",
|
|
163
|
+
display: "flex",
|
|
164
|
+
flexWrap: "wrap",
|
|
165
|
+
alignItems: "center",
|
|
166
|
+
gap: 10,
|
|
167
|
+
padding: "6px 10px",
|
|
168
|
+
borderTop: "1px solid rgba(128,128,128,0.3)",
|
|
169
|
+
background: "rgba(128,128,128,0.08)",
|
|
170
|
+
fontSize: 12,
|
|
171
|
+
};
|
|
172
|
+
const statusInlineStyle = {
|
|
173
|
+
flex: "0 0 auto",
|
|
174
|
+
padding: "4px 10px",
|
|
175
|
+
margin: 0,
|
|
176
|
+
fontSize: 12,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/** Right column shell: tab bar + stacked sections (no page footer below). */
|
|
180
|
+
const DefaultReferencePanel = ({ theme, status, moveStats, variationsStrip, gamesPanel, }) => (jsxRuntime.jsxs("div", { style: Object.assign(Object.assign({}, referencePanelStyle), panelStyle), "data-theme": theme, children: [jsxRuntime.jsx("div", { style: referenceTabBarStyle, children: jsxRuntime.jsx("span", { style: Object.assign(Object.assign({}, referenceTabLabelStyle), { fontWeight: 600 }), children: "Reference" }) }), status, jsxRuntime.jsx("div", { style: { flex: "0 0 auto", minHeight: 0 }, children: moveStats }), variationsStrip, gamesPanel] }));
|
|
181
|
+
|
|
182
|
+
const DefaultBoardNav = ({ canGoBack, canGoForward, onBack, onForward, }) => (jsxRuntime.jsxs("div", { style: boardNavStyle, children: [jsxRuntime.jsx("button", { type: "button", style: boardNavButtonStyle, onClick: onBack, disabled: !canGoBack, "aria-label": "Previous position", title: "Back", children: "\u25C0" }), jsxRuntime.jsx("button", { type: "button", style: boardNavButtonStyle, onClick: onForward, disabled: !canGoForward, "aria-label": "Next position", title: "Forward", children: "\u25B6" })] }));
|
|
183
|
+
const defaultRenderBoardNav = (props) => (jsxRuntime.jsx(DefaultBoardNav, Object.assign({}, props)));
|
|
184
|
+
|
|
185
|
+
const ExplorerStatusBanner = ({ error, loading, }) => {
|
|
186
|
+
if (!error && !loading)
|
|
187
|
+
return null;
|
|
188
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [error && (jsxRuntime.jsx("p", { style: Object.assign(Object.assign({}, statusInlineStyle), { color: "#e57373" }), role: "alert", children: error })), loading && (jsxRuntime.jsx("p", { style: Object.assign(Object.assign({}, statusInlineStyle), { opacity: 0.7 }), children: "Loading\u2026" }))] }));
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/** Match endchess-backend / mass-games-import (first four FEN fields). */
|
|
192
|
+
function normalizeFen(fen) {
|
|
193
|
+
const parts = fen.trim().split(/\s+/);
|
|
194
|
+
if (parts.length < 4) {
|
|
195
|
+
throw new Error(`Invalid FEN: ${fen}`);
|
|
196
|
+
}
|
|
197
|
+
return `${parts[0]} ${parts[1]} ${parts[2]} ${parts[3]}`;
|
|
198
|
+
}
|
|
199
|
+
/** Legal move from a board drag/drop (react-chessboard piece string, e.g. wQ). */
|
|
200
|
+
function applyBoardMove(fen, sourceSquare, targetSquare, piece) {
|
|
201
|
+
var _a, _b;
|
|
202
|
+
const chess = new chess_js.Chess(fen);
|
|
203
|
+
const pieceType = (_a = piece[1]) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
204
|
+
const legal = chess
|
|
205
|
+
.moves({ square: sourceSquare, verbose: true })
|
|
206
|
+
.find((move) => move.to === targetSquare &&
|
|
207
|
+
(!move.promotion || move.promotion === pieceType));
|
|
208
|
+
if (!legal)
|
|
209
|
+
return null;
|
|
210
|
+
const played = chess.move({
|
|
211
|
+
from: legal.from,
|
|
212
|
+
to: legal.to,
|
|
213
|
+
promotion: legal.promotion,
|
|
214
|
+
});
|
|
215
|
+
if (!played)
|
|
216
|
+
return null;
|
|
217
|
+
const uci = `${played.from}${played.to}${(_b = played.promotion) !== null && _b !== void 0 ? _b : ""}`;
|
|
218
|
+
return { fen: chess.fen(), uci, san: played.san };
|
|
219
|
+
}
|
|
220
|
+
/** Play a SAN sequence from a start FEN; returns null if any move is illegal. */
|
|
221
|
+
function applyLineSans(startFen, sans) {
|
|
222
|
+
const chess = new chess_js.Chess(startFen);
|
|
223
|
+
const entries = [];
|
|
224
|
+
for (const san of sans) {
|
|
225
|
+
try {
|
|
226
|
+
const move = chess.move(san);
|
|
227
|
+
if (!move)
|
|
228
|
+
return null;
|
|
229
|
+
entries.push({ fen: chess.fen(), lastSan: move.san });
|
|
230
|
+
}
|
|
231
|
+
catch (_a) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return { fen: chess.fen(), entries };
|
|
236
|
+
}
|
|
237
|
+
/** Apply a UCI move to a FEN; returns null if illegal. */
|
|
238
|
+
function fenAfterUci(fen, uci) {
|
|
239
|
+
const chess = new chess_js.Chess(fen);
|
|
240
|
+
const from = uci.slice(0, 2);
|
|
241
|
+
const to = uci.slice(2, 4);
|
|
242
|
+
const promotion = uci.length > 4 ? uci[4] : undefined;
|
|
243
|
+
try {
|
|
244
|
+
const move = chess.move({ from, to, promotion });
|
|
245
|
+
if (!move)
|
|
246
|
+
return null;
|
|
247
|
+
return chess.fen();
|
|
248
|
+
}
|
|
249
|
+
catch (_a) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function whiteScorePercent(whiteWins, draws, blackWins) {
|
|
254
|
+
const total = whiteWins + draws + blackWins;
|
|
255
|
+
if (total === 0)
|
|
256
|
+
return null;
|
|
257
|
+
return Math.round((100 * (whiteWins + 0.5 * draws)) / total);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const MoveStatsTable = ({ moves, selectedUci, onMoveSelect, }) => (jsxRuntime.jsxs("div", { style: moveStatsSectionStyle, children: [jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Moves" }), jsxRuntime.jsxs("table", { style: tableStyle, children: [jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("th", { style: thStyle, children: "Move" }), jsxRuntime.jsx("th", { style: thStyle, children: "Games" }), jsxRuntime.jsx("th", { style: Object.assign(Object.assign({}, thStyle), { textAlign: "right" }), children: "Score %" }), jsxRuntime.jsx("th", { style: Object.assign(Object.assign({}, thStyle), { textAlign: "right" }), children: "Avg" })] }) }), jsxRuntime.jsx("tbody", { children: moves.map((move) => {
|
|
261
|
+
var _a;
|
|
262
|
+
const score = whiteScorePercent(move.whiteWins, move.draws, move.blackWins);
|
|
263
|
+
const selected = move.uci === selectedUci;
|
|
264
|
+
return (jsxRuntime.jsxs("tr", { onClick: () => onMoveSelect(move), style: {
|
|
265
|
+
cursor: "pointer",
|
|
266
|
+
background: selected ? "rgba(100,149,237,0.25)" : undefined,
|
|
267
|
+
}, children: [jsxRuntime.jsx("td", { style: tdStyle, children: move.san }), jsxRuntime.jsx("td", { style: tdStyle, children: move.games.toLocaleString() }), jsxRuntime.jsx("td", { style: Object.assign(Object.assign({}, tdStyle), { textAlign: "right" }), children: score !== null ? `${score}` : "—" }), jsxRuntime.jsx("td", { style: Object.assign(Object.assign({}, tdStyle), { textAlign: "right" }), children: (_a = move.avgElo) !== null && _a !== void 0 ? _a : "—" })] }, move.uci));
|
|
268
|
+
}) })] })] }));
|
|
269
|
+
|
|
270
|
+
const inputStyle = {
|
|
271
|
+
width: 64,
|
|
272
|
+
padding: "2px 6px",
|
|
273
|
+
fontSize: 12,
|
|
274
|
+
};
|
|
275
|
+
const rowStyle = {
|
|
276
|
+
display: "flex",
|
|
277
|
+
flexWrap: "wrap",
|
|
278
|
+
gap: 6,
|
|
279
|
+
alignItems: "center",
|
|
280
|
+
};
|
|
281
|
+
const EloRangeFilter = ({ minElo, maxElo, defaultMinElo, defaultMaxElo, onMinEloChange, onMaxEloChange, }) => (jsxRuntime.jsxs("div", { style: rowStyle, children: [jsxRuntime.jsx("span", { style: { fontWeight: 600 }, children: "Filter" }), jsxRuntime.jsx("input", { type: "number", value: minElo, min: 0, max: 3000, onChange: (e) => onMinEloChange(Number(e.target.value) || defaultMinElo), style: inputStyle, "aria-label": "Minimum Elo" }), jsxRuntime.jsx("span", { children: "\u2013" }), jsxRuntime.jsx("input", { type: "number", value: maxElo, min: 0, max: 3000, onChange: (e) => onMaxEloChange(Number(e.target.value) || defaultMaxElo), style: inputStyle, "aria-label": "Maximum Elo" })] }));
|
|
282
|
+
|
|
283
|
+
const linkStyle = {
|
|
284
|
+
color: "inherit",
|
|
285
|
+
textDecoration: "underline",
|
|
286
|
+
textUnderlineOffset: 2,
|
|
287
|
+
};
|
|
288
|
+
const LichessGameLink = ({ url, name }) => (jsxRuntime.jsx("a", { href: url, target: "_blank", rel: "noopener noreferrer", style: linkStyle, children: name }));
|
|
289
|
+
const GamesTable = ({ games, onGameSelect }) => (jsxRuntime.jsxs("table", { style: tableStyle, children: [jsxRuntime.jsx("thead", { children: jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("th", { style: thStyle, children: "White" }), jsxRuntime.jsx("th", { style: thStyle, children: "Elo" }), jsxRuntime.jsx("th", { style: thStyle, children: "Black" }), jsxRuntime.jsx("th", { style: thStyle, children: "Elo" }), jsxRuntime.jsx("th", { style: thStyle, children: "Result" }), onGameSelect && jsxRuntime.jsx("th", { style: thStyle })] }) }), jsxRuntime.jsx("tbody", { children: games.length === 0 ? (jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { colSpan: onGameSelect ? 6 : 5, style: Object.assign(Object.assign({}, tdStyle), { opacity: 0.7, fontStyle: "italic" }), children: "No games match this position and filter. Widen the Elo range or turn off Top games." }) })) : (games.map((g) => (jsxRuntime.jsxs("tr", { children: [jsxRuntime.jsx("td", { style: tdStyle, children: jsxRuntime.jsx(LichessGameLink, { url: g.url, name: g.white }) }), jsxRuntime.jsx("td", { style: tdStyle, children: g.whiteElo }), jsxRuntime.jsx("td", { style: tdStyle, children: jsxRuntime.jsx(LichessGameLink, { url: g.url, name: g.black }) }), jsxRuntime.jsx("td", { style: tdStyle, children: g.blackElo }), jsxRuntime.jsx("td", { style: tdStyle, children: g.result }), onGameSelect && (jsxRuntime.jsx("td", { style: tdStyle, children: jsxRuntime.jsx("button", { type: "button", onClick: () => onGameSelect(g), children: "Train" }) }))] }, g.gameId)))) })] }));
|
|
290
|
+
|
|
291
|
+
const mainLineTitleStyle = {
|
|
292
|
+
fontWeight: 600,
|
|
293
|
+
};
|
|
294
|
+
const PositionGamesPanel = ({ games, lineLabel, minElo, maxElo, defaultMinElo, defaultMaxElo, topOnly, onMinEloChange, onMaxEloChange, onTopOnlyChange, onGameSelect, }) => (jsxRuntime.jsxs("div", { style: gamesSectionStyle, children: [jsxRuntime.jsx("div", { style: gamesHeaderStyle, children: lineLabel ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: mainLineTitleStyle, children: "Main line: " }), lineLabel, jsxRuntime.jsxs("span", { style: { opacity: 0.75 }, children: [" (", games.length, " games)"] })] })) : (jsxRuntime.jsxs("span", { children: ["Games ", jsxRuntime.jsxs("span", { style: { opacity: 0.75 }, children: ["(", games.length, ")"] })] })) }), jsxRuntime.jsx("div", { style: gamesScrollStyle, children: jsxRuntime.jsx(GamesTable, { games: games, onGameSelect: onGameSelect }) }), jsxRuntime.jsxs("div", { style: gamesToolbarStyle, children: [jsxRuntime.jsx(EloRangeFilter, { minElo: minElo, maxElo: maxElo, defaultMinElo: defaultMinElo, defaultMaxElo: defaultMaxElo, onMinEloChange: onMinEloChange, onMaxEloChange: onMaxEloChange }), jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: topOnly, onChange: (e) => onTopOnlyChange(e.target.checked) }), "Top games"] })] })] }));
|
|
295
|
+
|
|
296
|
+
const DefaultReferenceLayout = ({ board, referencePanel, }) => (jsxRuntime.jsxs("div", { style: referenceShellStyle, children: [jsxRuntime.jsx("div", { style: boardColumnStyle, children: board }), referencePanel] }));
|
|
297
|
+
const defaultRenderLayout = (props) => (jsxRuntime.jsx(DefaultReferenceLayout, Object.assign({}, props)));
|
|
298
|
+
|
|
299
|
+
function isVariationLineActive(line, selectedLineKey, forwardSans = []) {
|
|
300
|
+
if (selectedLineKey && line.key === selectedLineKey) {
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
if (forwardSans.length === 0) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
return forwardSans.every((san, index) => { var _a; return ((_a = line.moves[index]) === null || _a === void 0 ? void 0 : _a.san) === san; });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const tabButtonStyle = (active) => (Object.assign(Object.assign({}, variationsTabStyle), { fontWeight: active ? 600 : 400, opacity: active ? 1 : 0.55, cursor: "pointer", border: "none", background: "transparent", padding: 0, color: "inherit", font: "inherit" }));
|
|
310
|
+
const DefaultVariationsStrip = ({ theme, tab, onTabChange, lines, loading, selectedLineKey, forwardSans, onLineSelect, }) => (jsxRuntime.jsxs("div", { style: Object.assign(Object.assign({}, variationsStripStyle), { flexDirection: "column", alignItems: "stretch", gap: 6 }), "data-theme": theme, children: [jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [jsxRuntime.jsx("button", { type: "button", style: tabButtonStyle(tab === "variations"), onClick: () => onTabChange("variations"), children: "Variations" }), jsxRuntime.jsx("button", { type: "button", style: tabButtonStyle(tab === "popularity"), onClick: () => onTabChange("popularity"), children: "Popularity" }), jsxRuntime.jsx("button", { type: "button", style: tabButtonStyle(tab === "endgames"), onClick: () => onTabChange("endgames"), children: "Endgames" })] }), jsxRuntime.jsx("div", { style: {
|
|
311
|
+
minWidth: 0,
|
|
312
|
+
maxHeight: 132,
|
|
313
|
+
overflow: "auto",
|
|
314
|
+
}, children: tab === "endgames" ? (jsxRuntime.jsx("span", { style: { fontSize: 11, opacity: 0.55 }, children: "Coming soon" })) : loading ? (jsxRuntime.jsx("span", { style: { fontSize: 11, opacity: 0.55 }, children: "Loading\u2026" })) : lines.length === 0 ? (jsxRuntime.jsx("span", { style: { fontSize: 11, opacity: 0.55 }, children: "No lines" })) : (lines.map((line) => {
|
|
315
|
+
var _a, _b;
|
|
316
|
+
const active = isVariationLineActive(line, selectedLineKey, forwardSans);
|
|
317
|
+
return (jsxRuntime.jsxs("button", { type: "button", onClick: () => onLineSelect(line), style: {
|
|
318
|
+
display: "flex",
|
|
319
|
+
width: "100%",
|
|
320
|
+
gap: 12,
|
|
321
|
+
alignItems: "baseline",
|
|
322
|
+
border: "none",
|
|
323
|
+
background: "transparent",
|
|
324
|
+
padding: "2px 0",
|
|
325
|
+
cursor: "pointer",
|
|
326
|
+
textAlign: "left",
|
|
327
|
+
color: active ? "#2e7d32" : "inherit",
|
|
328
|
+
font: "inherit",
|
|
329
|
+
fontSize: 12,
|
|
330
|
+
}, children: [jsxRuntime.jsx("span", { style: { flex: 1, minWidth: 0 }, children: line.label }), jsxRuntime.jsxs("span", { children: ["N = ", line.games.toLocaleString()] }), jsxRuntime.jsxs("span", { children: [(_a = line.scorePercent) !== null && _a !== void 0 ? _a : "—", "%"] }), jsxRuntime.jsx("span", { children: line.lastPlayedYear
|
|
331
|
+
? `Last played ${line.lastPlayedYear}`
|
|
332
|
+
: "Last played —" }), jsxRuntime.jsx("span", { children: (_b = line.avgElo) !== null && _b !== void 0 ? _b : "—" })] }, line.key));
|
|
333
|
+
})) })] }));
|
|
334
|
+
const defaultRenderVariationsStrip = (props) => jsxRuntime.jsx(DefaultVariationsStrip, Object.assign({}, props));
|
|
335
|
+
|
|
336
|
+
const defaultRenderStatus = (props) => (jsxRuntime.jsx(ExplorerStatusBanner, Object.assign({}, props)));
|
|
337
|
+
const defaultRenderMoveStats = (props) => (jsxRuntime.jsx(MoveStatsTable, Object.assign({}, props)));
|
|
338
|
+
const defaultRenderGamesPanel = (props) => (jsxRuntime.jsx(PositionGamesPanel, Object.assign({}, props)));
|
|
339
|
+
|
|
340
|
+
/******************************************************************************
|
|
341
|
+
Copyright (c) Microsoft Corporation.
|
|
342
|
+
|
|
343
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
344
|
+
purpose with or without fee is hereby granted.
|
|
345
|
+
|
|
346
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
347
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
348
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
349
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
350
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
351
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
352
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
353
|
+
***************************************************************************** */
|
|
354
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
358
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
359
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
360
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
361
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
362
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
363
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
368
|
+
var e = new Error(message);
|
|
369
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
function usePositionHistory(initialFen) {
|
|
373
|
+
const [history, setHistory] = react.useState([
|
|
374
|
+
{ fen: initialFen },
|
|
375
|
+
]);
|
|
376
|
+
const [historyIndex, setHistoryIndex] = react.useState(0);
|
|
377
|
+
const canGoBack = historyIndex > 0;
|
|
378
|
+
const canGoForward = historyIndex < history.length - 1;
|
|
379
|
+
const pushEntry = react.useCallback((fen, lastSan) => {
|
|
380
|
+
setHistory((prev) => {
|
|
381
|
+
const trimmed = prev.slice(0, historyIndex + 1);
|
|
382
|
+
const next = [...trimmed, { fen, lastSan }];
|
|
383
|
+
setHistoryIndex(next.length - 1);
|
|
384
|
+
return next;
|
|
385
|
+
});
|
|
386
|
+
return { fen, lastSan };
|
|
387
|
+
}, [historyIndex]);
|
|
388
|
+
const goBack = react.useCallback(() => {
|
|
389
|
+
if (historyIndex <= 0)
|
|
390
|
+
return null;
|
|
391
|
+
const nextIndex = historyIndex - 1;
|
|
392
|
+
const entry = history[nextIndex];
|
|
393
|
+
setHistoryIndex(nextIndex);
|
|
394
|
+
return entry;
|
|
395
|
+
}, [history, historyIndex]);
|
|
396
|
+
const goForward = react.useCallback(() => {
|
|
397
|
+
if (historyIndex >= history.length - 1)
|
|
398
|
+
return null;
|
|
399
|
+
const nextIndex = historyIndex + 1;
|
|
400
|
+
const entry = history[nextIndex];
|
|
401
|
+
setHistoryIndex(nextIndex);
|
|
402
|
+
return entry;
|
|
403
|
+
}, [history, historyIndex]);
|
|
404
|
+
const resetHistory = react.useCallback((fen) => {
|
|
405
|
+
const entry = { fen };
|
|
406
|
+
setHistory([entry]);
|
|
407
|
+
setHistoryIndex(0);
|
|
408
|
+
return entry;
|
|
409
|
+
}, []);
|
|
410
|
+
const pushEntries = react.useCallback((entries) => {
|
|
411
|
+
if (entries.length === 0)
|
|
412
|
+
return;
|
|
413
|
+
setHistory((prev) => {
|
|
414
|
+
const trimmed = prev.slice(0, historyIndex + 1);
|
|
415
|
+
const next = [...trimmed, ...entries];
|
|
416
|
+
setHistoryIndex(next.length - 1);
|
|
417
|
+
return next;
|
|
418
|
+
});
|
|
419
|
+
}, [historyIndex]);
|
|
420
|
+
const lineSans = history
|
|
421
|
+
.slice(1, historyIndex + 1)
|
|
422
|
+
.map((entry) => entry.lastSan)
|
|
423
|
+
.filter((san) => Boolean(san));
|
|
424
|
+
const forwardSans = history
|
|
425
|
+
.slice(historyIndex + 1)
|
|
426
|
+
.map((entry) => entry.lastSan)
|
|
427
|
+
.filter((san) => Boolean(san));
|
|
428
|
+
return {
|
|
429
|
+
canGoBack,
|
|
430
|
+
canGoForward,
|
|
431
|
+
lineSans,
|
|
432
|
+
forwardSans,
|
|
433
|
+
pushEntry,
|
|
434
|
+
pushEntries,
|
|
435
|
+
goBack,
|
|
436
|
+
goForward,
|
|
437
|
+
resetHistory,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLineSansChange, fetchPosition, fetchPositionGames, defaultMinElo, defaultMaxElo, }) {
|
|
442
|
+
const initialFen = fenProp !== null && fenProp !== void 0 ? fenProp : EXPLORER_START_FEN;
|
|
443
|
+
const [fen, setFen] = react.useState(initialFen);
|
|
444
|
+
/** Board display FEN; may lead {@link fen} while a variation line is animating. */
|
|
445
|
+
const [boardFen, setBoardFen] = react.useState(initialFen);
|
|
446
|
+
const [position, setPosition] = react.useState(null);
|
|
447
|
+
const [games, setGames] = react.useState(null);
|
|
448
|
+
/** Filter games to those that played this UCI from the current FEN (optional). */
|
|
449
|
+
const [gamesMoveFilterUci, setGamesMoveFilterUci] = react.useState();
|
|
450
|
+
const [minElo, setMinElo] = react.useState(defaultMinElo);
|
|
451
|
+
const [maxElo, setMaxElo] = react.useState(defaultMaxElo);
|
|
452
|
+
const [topOnly, setTopOnly] = react.useState(false);
|
|
453
|
+
const [loading, setLoading] = react.useState(false);
|
|
454
|
+
const [error, setError] = react.useState(null);
|
|
455
|
+
const [variationsTab, setVariationsTab] = react.useState("variations");
|
|
456
|
+
const [selectedVariationKey, setSelectedVariationKey] = react.useState();
|
|
457
|
+
const variationAnimationTimerRef = react.useRef(null);
|
|
458
|
+
const isAnimatingVariationRef = react.useRef(false);
|
|
459
|
+
const readyForLineSyncRef = react.useRef(false);
|
|
460
|
+
const cancelVariationAnimation = react.useCallback(() => {
|
|
461
|
+
if (variationAnimationTimerRef.current !== null) {
|
|
462
|
+
clearTimeout(variationAnimationTimerRef.current);
|
|
463
|
+
variationAnimationTimerRef.current = null;
|
|
464
|
+
}
|
|
465
|
+
isAnimatingVariationRef.current = false;
|
|
466
|
+
}, []);
|
|
467
|
+
const { canGoBack, canGoForward, lineSans, forwardSans, pushEntry, pushEntries, goBack, goForward, resetHistory, } = usePositionHistory(initialFen);
|
|
468
|
+
const applyNavigation = react.useCallback((nextFen, clearMoveFilter = true) => {
|
|
469
|
+
setFen(nextFen);
|
|
470
|
+
setBoardFen(nextFen);
|
|
471
|
+
if (clearMoveFilter) {
|
|
472
|
+
setGamesMoveFilterUci(undefined);
|
|
473
|
+
}
|
|
474
|
+
onFenChange === null || onFenChange === void 0 ? void 0 : onFenChange(nextFen);
|
|
475
|
+
}, [onFenChange]);
|
|
476
|
+
const initialLineKey = react.useMemo(() => { var _a; return (_a = initialLineSans === null || initialLineSans === void 0 ? void 0 : initialLineSans.join("|")) !== null && _a !== void 0 ? _a : ""; }, [initialLineSans]);
|
|
477
|
+
const lastAppliedLineKeyRef = react.useRef(undefined);
|
|
478
|
+
react.useEffect(() => () => cancelVariationAnimation(), [cancelVariationAnimation]);
|
|
479
|
+
react.useEffect(() => {
|
|
480
|
+
if (lastAppliedLineKeyRef.current === initialLineKey)
|
|
481
|
+
return;
|
|
482
|
+
const currentLineKey = lineSans.join("|");
|
|
483
|
+
if (currentLineKey === initialLineKey) {
|
|
484
|
+
lastAppliedLineKeyRef.current = initialLineKey;
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
lastAppliedLineKeyRef.current = initialLineKey;
|
|
488
|
+
cancelVariationAnimation();
|
|
489
|
+
resetHistory(EXPLORER_START_FEN);
|
|
490
|
+
setSelectedVariationKey(undefined);
|
|
491
|
+
if (initialLineSans === null || initialLineSans === void 0 ? void 0 : initialLineSans.length) {
|
|
492
|
+
const result = applyLineSans(EXPLORER_START_FEN, initialLineSans);
|
|
493
|
+
if (result) {
|
|
494
|
+
pushEntries(result.entries);
|
|
495
|
+
applyNavigation(result.fen, true);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
applyNavigation(EXPLORER_START_FEN, true);
|
|
500
|
+
}, [
|
|
501
|
+
initialLineKey,
|
|
502
|
+
initialLineSans,
|
|
503
|
+
lineSans,
|
|
504
|
+
cancelVariationAnimation,
|
|
505
|
+
resetHistory,
|
|
506
|
+
pushEntries,
|
|
507
|
+
applyNavigation,
|
|
508
|
+
]);
|
|
509
|
+
react.useEffect(() => {
|
|
510
|
+
if (readyForLineSyncRef.current)
|
|
511
|
+
return;
|
|
512
|
+
const expected = initialLineSans !== null && initialLineSans !== void 0 ? initialLineSans : [];
|
|
513
|
+
const matchesInitialLine = lineSans.length === expected.length &&
|
|
514
|
+
lineSans.every((san, index) => san === expected[index]);
|
|
515
|
+
if (matchesInitialLine) {
|
|
516
|
+
readyForLineSyncRef.current = true;
|
|
517
|
+
}
|
|
518
|
+
}, [lineSans, initialLineSans]);
|
|
519
|
+
react.useEffect(() => {
|
|
520
|
+
if (!readyForLineSyncRef.current)
|
|
521
|
+
return;
|
|
522
|
+
onLineSansChange === null || onLineSansChange === void 0 ? void 0 : onLineSansChange(lineSans);
|
|
523
|
+
}, [lineSans, onLineSansChange]);
|
|
524
|
+
react.useEffect(() => {
|
|
525
|
+
if (fenProp === undefined || fenProp === fen)
|
|
526
|
+
return;
|
|
527
|
+
cancelVariationAnimation();
|
|
528
|
+
resetHistory(fenProp);
|
|
529
|
+
applyNavigation(fenProp, true);
|
|
530
|
+
}, [fenProp, fen, resetHistory, applyNavigation, cancelVariationAnimation]);
|
|
531
|
+
react.useEffect(() => {
|
|
532
|
+
let cancelled = false;
|
|
533
|
+
setLoading(true);
|
|
534
|
+
setError(null);
|
|
535
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
536
|
+
try {
|
|
537
|
+
const [pos, gameList] = yield Promise.all([
|
|
538
|
+
fetchPosition(fen),
|
|
539
|
+
fetchPositionGames({
|
|
540
|
+
fen,
|
|
541
|
+
minElo,
|
|
542
|
+
maxElo,
|
|
543
|
+
uci: gamesMoveFilterUci,
|
|
544
|
+
topOnly,
|
|
545
|
+
}),
|
|
546
|
+
]);
|
|
547
|
+
if (cancelled)
|
|
548
|
+
return;
|
|
549
|
+
setPosition(pos);
|
|
550
|
+
setGames(gameList);
|
|
551
|
+
if (!pos) {
|
|
552
|
+
setError("No explorer data for this position yet");
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
catch (e) {
|
|
556
|
+
if (!cancelled) {
|
|
557
|
+
setError(e instanceof Error ? e.message : "Failed to load explorer data");
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
finally {
|
|
561
|
+
if (!cancelled)
|
|
562
|
+
setLoading(false);
|
|
563
|
+
}
|
|
564
|
+
}))();
|
|
565
|
+
return () => {
|
|
566
|
+
cancelled = true;
|
|
567
|
+
};
|
|
568
|
+
}, [
|
|
569
|
+
fen,
|
|
570
|
+
minElo,
|
|
571
|
+
maxElo,
|
|
572
|
+
gamesMoveFilterUci,
|
|
573
|
+
topOnly,
|
|
574
|
+
fetchPosition,
|
|
575
|
+
fetchPositionGames,
|
|
576
|
+
]);
|
|
577
|
+
const handleMoveSelect = react.useCallback((move) => {
|
|
578
|
+
if (isAnimatingVariationRef.current) {
|
|
579
|
+
cancelVariationAnimation();
|
|
580
|
+
setBoardFen(fen);
|
|
581
|
+
setSelectedVariationKey(undefined);
|
|
582
|
+
}
|
|
583
|
+
const nextFen = fenAfterUci(fen, move.uci);
|
|
584
|
+
if (!nextFen)
|
|
585
|
+
return;
|
|
586
|
+
pushEntry(nextFen, move.san);
|
|
587
|
+
setSelectedVariationKey(undefined);
|
|
588
|
+
applyNavigation(nextFen, true);
|
|
589
|
+
}, [fen, pushEntry, applyNavigation, cancelVariationAnimation]);
|
|
590
|
+
const handleLineSelect = react.useCallback((line) => {
|
|
591
|
+
if (isAnimatingVariationRef.current)
|
|
592
|
+
return;
|
|
593
|
+
cancelVariationAnimation();
|
|
594
|
+
let currentFen = fen;
|
|
595
|
+
const entries = [];
|
|
596
|
+
for (let i = 0; i < line.uciPath.length; i += 1) {
|
|
597
|
+
const nextFen = fenAfterUci(currentFen, line.uciPath[i]);
|
|
598
|
+
if (!nextFen)
|
|
599
|
+
return;
|
|
600
|
+
entries.push({ fen: nextFen, lastSan: line.moves[i].san });
|
|
601
|
+
currentFen = nextFen;
|
|
602
|
+
}
|
|
603
|
+
if (entries.length === 0)
|
|
604
|
+
return;
|
|
605
|
+
setSelectedVariationKey(line.key);
|
|
606
|
+
const commitLine = () => {
|
|
607
|
+
isAnimatingVariationRef.current = false;
|
|
608
|
+
variationAnimationTimerRef.current = null;
|
|
609
|
+
pushEntries(entries);
|
|
610
|
+
applyNavigation(entries[entries.length - 1].fen, true);
|
|
611
|
+
};
|
|
612
|
+
isAnimatingVariationRef.current = true;
|
|
613
|
+
let step = 0;
|
|
614
|
+
const tick = () => {
|
|
615
|
+
setBoardFen(entries[step].fen);
|
|
616
|
+
step += 1;
|
|
617
|
+
if (step >= entries.length) {
|
|
618
|
+
commitLine();
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
variationAnimationTimerRef.current = setTimeout(tick, VARIATION_LINE_STEP_MS);
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
tick();
|
|
625
|
+
}, [fen, pushEntries, applyNavigation, cancelVariationAnimation]);
|
|
626
|
+
const handlePieceDrop = react.useCallback((sourceSquare, targetSquare, piece) => {
|
|
627
|
+
if (isAnimatingVariationRef.current) {
|
|
628
|
+
cancelVariationAnimation();
|
|
629
|
+
setBoardFen(fen);
|
|
630
|
+
setSelectedVariationKey(undefined);
|
|
631
|
+
}
|
|
632
|
+
const result = applyBoardMove(fen, sourceSquare, targetSquare, piece);
|
|
633
|
+
if (!result)
|
|
634
|
+
return false;
|
|
635
|
+
pushEntry(result.fen, result.san);
|
|
636
|
+
setSelectedVariationKey(undefined);
|
|
637
|
+
applyNavigation(result.fen, true);
|
|
638
|
+
return true;
|
|
639
|
+
}, [fen, pushEntry, applyNavigation, cancelVariationAnimation]);
|
|
640
|
+
const handleBack = react.useCallback(() => {
|
|
641
|
+
if (isAnimatingVariationRef.current) {
|
|
642
|
+
cancelVariationAnimation();
|
|
643
|
+
setBoardFen(fen);
|
|
644
|
+
setSelectedVariationKey(undefined);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const entry = goBack();
|
|
648
|
+
if (entry)
|
|
649
|
+
applyNavigation(entry.fen, true);
|
|
650
|
+
}, [fen, goBack, applyNavigation, cancelVariationAnimation]);
|
|
651
|
+
const handleForward = react.useCallback(() => {
|
|
652
|
+
if (isAnimatingVariationRef.current) {
|
|
653
|
+
cancelVariationAnimation();
|
|
654
|
+
setBoardFen(fen);
|
|
655
|
+
setSelectedVariationKey(undefined);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const entry = goForward();
|
|
659
|
+
if (entry)
|
|
660
|
+
applyNavigation(entry.fen, true);
|
|
661
|
+
}, [fen, goForward, applyNavigation, cancelVariationAnimation]);
|
|
662
|
+
const lineLabel = react.useMemo(() => {
|
|
663
|
+
if (lineSans.length > 0) {
|
|
664
|
+
return lineSans.join(" ");
|
|
665
|
+
}
|
|
666
|
+
if (position) {
|
|
667
|
+
return `Starting position (${position.totalGames.toLocaleString()} games)`;
|
|
668
|
+
}
|
|
669
|
+
return "";
|
|
670
|
+
}, [lineSans, position]);
|
|
671
|
+
return {
|
|
672
|
+
fen,
|
|
673
|
+
boardFen,
|
|
674
|
+
position,
|
|
675
|
+
games,
|
|
676
|
+
gamesMoveFilterUci,
|
|
677
|
+
minElo,
|
|
678
|
+
maxElo,
|
|
679
|
+
topOnly,
|
|
680
|
+
loading,
|
|
681
|
+
error,
|
|
682
|
+
lineLabel,
|
|
683
|
+
canGoBack,
|
|
684
|
+
canGoForward,
|
|
685
|
+
variationsTab,
|
|
686
|
+
forwardSans,
|
|
687
|
+
selectedVariationKey,
|
|
688
|
+
setMinElo,
|
|
689
|
+
setMaxElo,
|
|
690
|
+
setTopOnly,
|
|
691
|
+
setVariationsTab,
|
|
692
|
+
setGamesMoveFilterUci,
|
|
693
|
+
handleMoveSelect,
|
|
694
|
+
handleLineSelect,
|
|
695
|
+
handlePieceDrop,
|
|
696
|
+
handleBack,
|
|
697
|
+
handleForward,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function useVariationLines({ fen, tab, minElo, maxElo, fetchPositionVariations, lineCount = 8, lineDepth = 4, enabled = true, }) {
|
|
702
|
+
const [lines, setLines] = react.useState([]);
|
|
703
|
+
const [loading, setLoading] = react.useState(false);
|
|
704
|
+
react.useEffect(() => {
|
|
705
|
+
if (!enabled || tab === "endgames") {
|
|
706
|
+
setLines([]);
|
|
707
|
+
setLoading(false);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
let cancelled = false;
|
|
711
|
+
setLoading(true);
|
|
712
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
713
|
+
var _a;
|
|
714
|
+
try {
|
|
715
|
+
const result = yield fetchPositionVariations({
|
|
716
|
+
fen,
|
|
717
|
+
mode: tab,
|
|
718
|
+
minElo,
|
|
719
|
+
maxElo,
|
|
720
|
+
lineCount,
|
|
721
|
+
depth: lineDepth,
|
|
722
|
+
});
|
|
723
|
+
if (cancelled)
|
|
724
|
+
return;
|
|
725
|
+
setLines((_a = result === null || result === void 0 ? void 0 : result.lines) !== null && _a !== void 0 ? _a : []);
|
|
726
|
+
}
|
|
727
|
+
catch (_b) {
|
|
728
|
+
if (!cancelled) {
|
|
729
|
+
setLines([]);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
finally {
|
|
733
|
+
if (!cancelled) {
|
|
734
|
+
setLoading(false);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}))();
|
|
738
|
+
return () => {
|
|
739
|
+
cancelled = true;
|
|
740
|
+
};
|
|
741
|
+
}, [
|
|
742
|
+
enabled,
|
|
743
|
+
fen,
|
|
744
|
+
tab,
|
|
745
|
+
minElo,
|
|
746
|
+
maxElo,
|
|
747
|
+
fetchPositionVariations,
|
|
748
|
+
lineCount,
|
|
749
|
+
lineDepth,
|
|
750
|
+
]);
|
|
751
|
+
return { lines, loading };
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineSans, onLineSansChange, fetchPosition, fetchPositionGames, fetchPositionVariations, theme = "dark", boardWidth = DEFAULT_REFERENCE_LAYOUT.boardWidth, defaultMinElo = 2200, defaultMaxElo = 2800, fillHeight = true, layoutMinHeight, renderLayout = defaultRenderLayout, renderStatus = defaultRenderStatus, renderMoveStats = defaultRenderMoveStats, renderVariationsStrip = defaultRenderVariationsStrip, renderGamesPanel = defaultRenderGamesPanel, renderBoardNav = defaultRenderBoardNav, onGameSelect, }) => {
|
|
755
|
+
var _a, _b;
|
|
756
|
+
const referenceData = usePositionReferenceData({
|
|
757
|
+
fenProp,
|
|
758
|
+
onFenChange,
|
|
759
|
+
initialLineSans,
|
|
760
|
+
onLineSansChange,
|
|
761
|
+
fetchPosition,
|
|
762
|
+
fetchPositionGames,
|
|
763
|
+
defaultMinElo,
|
|
764
|
+
defaultMaxElo,
|
|
765
|
+
});
|
|
766
|
+
const { fen, boardFen, position, games, minElo, maxElo, topOnly, loading, error, lineLabel, canGoBack, canGoForward, variationsTab, forwardSans, selectedVariationKey, setMinElo, setMaxElo, setTopOnly, setVariationsTab, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, } = referenceData;
|
|
767
|
+
const { lines: variationLines, loading: variationLinesLoading } = useVariationLines({
|
|
768
|
+
fen,
|
|
769
|
+
tab: variationsTab,
|
|
770
|
+
minElo,
|
|
771
|
+
maxElo,
|
|
772
|
+
fetchPositionVariations,
|
|
773
|
+
enabled: Boolean(position),
|
|
774
|
+
});
|
|
775
|
+
const outerStyle = {
|
|
776
|
+
width: "100%",
|
|
777
|
+
height: fillHeight ? "100%" : "auto",
|
|
778
|
+
minHeight: layoutMinHeight !== null && layoutMinHeight !== void 0 ? layoutMinHeight : DEFAULT_REFERENCE_LAYOUT.minHeight,
|
|
779
|
+
overflow: "hidden",
|
|
780
|
+
boxSizing: "border-box",
|
|
781
|
+
};
|
|
782
|
+
const board = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactChessboard.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, position: boardFen, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }) }), renderBoardNav({
|
|
783
|
+
canGoBack,
|
|
784
|
+
canGoForward,
|
|
785
|
+
onBack: handleBack,
|
|
786
|
+
onForward: handleForward,
|
|
787
|
+
})] }));
|
|
788
|
+
const referencePanel = (jsxRuntime.jsx(DefaultReferencePanel, { theme: theme, status: renderStatus({ error, loading }), moveStats: renderMoveStats({
|
|
789
|
+
moves: (_a = position === null || position === void 0 ? void 0 : position.moves) !== null && _a !== void 0 ? _a : [],
|
|
790
|
+
onMoveSelect: handleMoveSelect,
|
|
791
|
+
}), variationsStrip: renderVariationsStrip({
|
|
792
|
+
theme,
|
|
793
|
+
tab: variationsTab,
|
|
794
|
+
onTabChange: setVariationsTab,
|
|
795
|
+
lines: variationLines,
|
|
796
|
+
loading: variationLinesLoading,
|
|
797
|
+
selectedLineKey: selectedVariationKey,
|
|
798
|
+
forwardSans,
|
|
799
|
+
onLineSelect: handleLineSelect,
|
|
800
|
+
}), gamesPanel: renderGamesPanel({
|
|
801
|
+
games: (_b = games === null || games === void 0 ? void 0 : games.games) !== null && _b !== void 0 ? _b : [],
|
|
802
|
+
lineLabel,
|
|
803
|
+
minElo,
|
|
804
|
+
maxElo,
|
|
805
|
+
defaultMinElo,
|
|
806
|
+
defaultMaxElo,
|
|
807
|
+
topOnly,
|
|
808
|
+
onMinEloChange: setMinElo,
|
|
809
|
+
onMaxEloChange: setMaxElo,
|
|
810
|
+
onTopOnlyChange: setTopOnly,
|
|
811
|
+
onGameSelect,
|
|
812
|
+
}) }));
|
|
813
|
+
return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, children: jsxRuntime.jsx("div", { style: outerStyle, children: renderLayout({ theme, board, referencePanel }) }) }));
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
/** Reference explorer with library default layout and renderers (ChessBase-style grid). */
|
|
817
|
+
const PositionReferenceExplorer = (props) => jsxRuntime.jsx(PositionReferenceExplorerCore, Object.assign({}, props));
|
|
818
|
+
|
|
819
|
+
function fenAtPly(movesUci, ply) {
|
|
820
|
+
const chess = new chess_js.Chess(EXPLORER_START_FEN);
|
|
821
|
+
for (let i = 0; i < ply && i < movesUci.length; i++) {
|
|
822
|
+
applyUci(chess, movesUci[i]);
|
|
823
|
+
}
|
|
824
|
+
return chess.fen();
|
|
825
|
+
}
|
|
826
|
+
/** Index of the next move to play to reach `targetFen`, or 0 if not found. */
|
|
827
|
+
function findPlyIndexForFen(movesUci, targetFen) {
|
|
828
|
+
const target = normalizeFen(targetFen);
|
|
829
|
+
const chess = new chess_js.Chess(EXPLORER_START_FEN);
|
|
830
|
+
if (normalizeFen(chess.fen()) === target) {
|
|
831
|
+
return 0;
|
|
832
|
+
}
|
|
833
|
+
for (let i = 0; i < movesUci.length; i++) {
|
|
834
|
+
applyUci(chess, movesUci[i]);
|
|
835
|
+
if (normalizeFen(chess.fen()) === target) {
|
|
836
|
+
return i + 1;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return 0;
|
|
840
|
+
}
|
|
841
|
+
function applyUci(chess, uci) {
|
|
842
|
+
const from = uci.slice(0, 2);
|
|
843
|
+
const to = uci.slice(2, 4);
|
|
844
|
+
const promotion = uci.length > 4 ? uci[4] : undefined;
|
|
845
|
+
const move = chess.move({ from, to, promotion });
|
|
846
|
+
if (!move) {
|
|
847
|
+
throw new Error(`Illegal UCI move: ${uci}`);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
function uciFromDrop(fen, sourceSquare, targetSquare, piece) {
|
|
851
|
+
var _a, _b;
|
|
852
|
+
const chess = new chess_js.Chess(fen);
|
|
853
|
+
const pieceType = (_a = piece[1]) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
854
|
+
const legal = chess
|
|
855
|
+
.moves({ square: sourceSquare, verbose: true })
|
|
856
|
+
.find((move) => move.to === targetSquare &&
|
|
857
|
+
(!move.promotion || move.promotion === pieceType));
|
|
858
|
+
if (!legal)
|
|
859
|
+
return null;
|
|
860
|
+
return `${legal.from}${legal.to}${(_b = legal.promotion) !== null && _b !== void 0 ? _b : ""}`;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function useGameReplayTraining({ gameId, startFen, fetchGame, }) {
|
|
864
|
+
var _a;
|
|
865
|
+
const [game, setGame] = react.useState(null);
|
|
866
|
+
const [plyIndex, setPlyIndex] = react.useState(0);
|
|
867
|
+
const [loading, setLoading] = react.useState(true);
|
|
868
|
+
const [error, setError] = react.useState(null);
|
|
869
|
+
const [feedback, setFeedback] = react.useState(null);
|
|
870
|
+
const [lastExpectedSan, setLastExpectedSan] = react.useState(null);
|
|
871
|
+
react.useEffect(() => {
|
|
872
|
+
let cancelled = false;
|
|
873
|
+
setLoading(true);
|
|
874
|
+
setError(null);
|
|
875
|
+
setFeedback(null);
|
|
876
|
+
setLastExpectedSan(null);
|
|
877
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
878
|
+
try {
|
|
879
|
+
const loaded = yield fetchGame(gameId);
|
|
880
|
+
if (cancelled)
|
|
881
|
+
return;
|
|
882
|
+
if (!(loaded === null || loaded === void 0 ? void 0 : loaded.movesUci.length)) {
|
|
883
|
+
setGame(null);
|
|
884
|
+
setError("This game has no move list for replay yet");
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
setGame(loaded);
|
|
888
|
+
setPlyIndex(findPlyIndexForFen(loaded.movesUci, startFen));
|
|
889
|
+
}
|
|
890
|
+
catch (e) {
|
|
891
|
+
if (!cancelled) {
|
|
892
|
+
setError(e instanceof Error ? e.message : "Failed to load game for replay");
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
finally {
|
|
896
|
+
if (!cancelled)
|
|
897
|
+
setLoading(false);
|
|
898
|
+
}
|
|
899
|
+
}))();
|
|
900
|
+
return () => {
|
|
901
|
+
cancelled = true;
|
|
902
|
+
};
|
|
903
|
+
}, [gameId, startFen, fetchGame]);
|
|
904
|
+
const fen = react.useMemo(() => {
|
|
905
|
+
if (!game)
|
|
906
|
+
return startFen;
|
|
907
|
+
return fenAtPly(game.movesUci, plyIndex);
|
|
908
|
+
}, [game, plyIndex, startFen]);
|
|
909
|
+
const complete = game ? plyIndex >= game.movesUci.length : false;
|
|
910
|
+
const expectedUci = game === null || game === void 0 ? void 0 : game.movesUci[plyIndex];
|
|
911
|
+
const expectedSan = game === null || game === void 0 ? void 0 : game.movesSan[plyIndex];
|
|
912
|
+
const handlePieceDrop = react.useCallback((sourceSquare, targetSquare, piece) => {
|
|
913
|
+
if (!game || complete || !expectedUci)
|
|
914
|
+
return false;
|
|
915
|
+
const uci = uciFromDrop(fen, sourceSquare, targetSquare, piece);
|
|
916
|
+
if (!uci)
|
|
917
|
+
return false;
|
|
918
|
+
if (uci === expectedUci) {
|
|
919
|
+
setFeedback("correct");
|
|
920
|
+
setPlyIndex((p) => p + 1);
|
|
921
|
+
setLastExpectedSan(null);
|
|
922
|
+
return true;
|
|
923
|
+
}
|
|
924
|
+
setFeedback("incorrect");
|
|
925
|
+
setLastExpectedSan(expectedSan !== null && expectedSan !== void 0 ? expectedSan : null);
|
|
926
|
+
return false;
|
|
927
|
+
}, [game, complete, expectedUci, expectedSan, fen]);
|
|
928
|
+
const revealMove = react.useCallback(() => {
|
|
929
|
+
if (!game || complete || !expectedSan)
|
|
930
|
+
return;
|
|
931
|
+
setFeedback("incorrect");
|
|
932
|
+
setLastExpectedSan(expectedSan);
|
|
933
|
+
setPlyIndex((p) => p + 1);
|
|
934
|
+
}, [game, complete, expectedSan]);
|
|
935
|
+
return {
|
|
936
|
+
game,
|
|
937
|
+
fen,
|
|
938
|
+
plyIndex,
|
|
939
|
+
totalPlies: (_a = game === null || game === void 0 ? void 0 : game.movesUci.length) !== null && _a !== void 0 ? _a : 0,
|
|
940
|
+
complete,
|
|
941
|
+
loading,
|
|
942
|
+
error,
|
|
943
|
+
feedback,
|
|
944
|
+
lastExpectedSan,
|
|
945
|
+
expectedSan,
|
|
946
|
+
handlePieceDrop,
|
|
947
|
+
revealMove,
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const defaultPanelStyle = {
|
|
952
|
+
fontFamily: "system-ui, sans-serif",
|
|
953
|
+
fontSize: 13,
|
|
954
|
+
padding: 12,
|
|
955
|
+
display: "flex",
|
|
956
|
+
flexDirection: "column",
|
|
957
|
+
gap: 8,
|
|
958
|
+
};
|
|
959
|
+
const defaultButtonStyle = {
|
|
960
|
+
fontSize: 13,
|
|
961
|
+
padding: "6px 12px",
|
|
962
|
+
cursor: "pointer",
|
|
963
|
+
};
|
|
964
|
+
const GameReplayTrainer = ({ gameId, startFen, fetchGame, onExit, theme = "dark", boardWidth = DEFAULT_REFERENCE_LAYOUT.boardWidth, renderStatus, }) => {
|
|
965
|
+
const { game, fen, plyIndex, totalPlies, complete, loading, error, feedback, lastExpectedSan, handlePieceDrop, revealMove, } = useGameReplayTraining({ gameId, startFen, fetchGame });
|
|
966
|
+
const status = renderStatus === null || renderStatus === void 0 ? void 0 : renderStatus({
|
|
967
|
+
loading,
|
|
968
|
+
error,
|
|
969
|
+
complete,
|
|
970
|
+
feedback,
|
|
971
|
+
lastExpectedSan,
|
|
972
|
+
plyIndex,
|
|
973
|
+
totalPlies,
|
|
974
|
+
game,
|
|
975
|
+
});
|
|
976
|
+
return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, children: jsxRuntime.jsxs("div", { style: defaultPanelStyle, children: [jsxRuntime.jsxs("div", { style: {
|
|
977
|
+
display: "flex",
|
|
978
|
+
justifyContent: "space-between",
|
|
979
|
+
alignItems: "center",
|
|
980
|
+
gap: 8,
|
|
981
|
+
}, children: [jsxRuntime.jsx("strong", { children: "Replay training" }), onExit && (jsxRuntime.jsx("button", { type: "button", style: defaultButtonStyle, onClick: onExit, children: "Back to explorer" }))] }), game && (jsxRuntime.jsxs("div", { style: { opacity: 0.85 }, children: [game.white, " vs ", game.black, " \u00B7 ", game.result] })), status !== null && status !== void 0 ? status : (jsxRuntime.jsxs("div", { style: { minHeight: 20 }, children: [loading && "Loading game…", error && jsxRuntime.jsx("span", { style: { color: "#c62828" }, children: error }), !loading && !error && complete && "Game complete.", !loading && !error && !complete && feedback === "correct" && (jsxRuntime.jsx("span", { style: { color: "#2e7d32" }, children: "Correct!" })), !loading &&
|
|
982
|
+
!error &&
|
|
983
|
+
!complete &&
|
|
984
|
+
feedback === "incorrect" &&
|
|
985
|
+
lastExpectedSan && (jsxRuntime.jsxs("span", { style: { color: "#ef6c00" }, children: ["Expected ", lastExpectedSan] })), !loading && !error && !complete && feedback === null && (jsxRuntime.jsxs("span", { children: ["Guess move ", plyIndex + 1, " of ", totalPlies] }))] })), jsxRuntime.jsx(reactChessboard.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, position: fen, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }) }), !complete && !loading && !error && (jsxRuntime.jsx("button", { type: "button", style: defaultButtonStyle, onClick: revealMove, children: "Show move" }))] }) }));
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
const mockPosition = {
|
|
989
|
+
positionKey: "mock-start",
|
|
990
|
+
fen: EXPLORER_START_FEN,
|
|
991
|
+
totalGames: 1250000,
|
|
992
|
+
moves: [
|
|
993
|
+
{
|
|
994
|
+
san: "e4",
|
|
995
|
+
uci: "e2e4",
|
|
996
|
+
games: 520000,
|
|
997
|
+
whiteWins: 180000,
|
|
998
|
+
draws: 140000,
|
|
999
|
+
blackWins: 200000,
|
|
1000
|
+
avgElo: 2450,
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
san: "d4",
|
|
1004
|
+
uci: "d2d4",
|
|
1005
|
+
games: 410000,
|
|
1006
|
+
whiteWins: 150000,
|
|
1007
|
+
draws: 120000,
|
|
1008
|
+
blackWins: 140000,
|
|
1009
|
+
avgElo: 2440,
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
san: "Nf3",
|
|
1013
|
+
uci: "g1f3",
|
|
1014
|
+
games: 180000,
|
|
1015
|
+
whiteWins: 62000,
|
|
1016
|
+
draws: 55000,
|
|
1017
|
+
blackWins: 63000,
|
|
1018
|
+
avgElo: 2435,
|
|
1019
|
+
},
|
|
1020
|
+
],
|
|
1021
|
+
sampleGameIds: ["abc123", "def456"],
|
|
1022
|
+
};
|
|
1023
|
+
const mockGames = {
|
|
1024
|
+
positionKey: "mock-start",
|
|
1025
|
+
fen: EXPLORER_START_FEN,
|
|
1026
|
+
minElo: 2200,
|
|
1027
|
+
maxElo: 2800,
|
|
1028
|
+
topOnly: false,
|
|
1029
|
+
games: [
|
|
1030
|
+
{
|
|
1031
|
+
gameId: "abc123",
|
|
1032
|
+
url: "https://lichess.org/abc123",
|
|
1033
|
+
white: "Carlsen,M",
|
|
1034
|
+
black: "Caruana,F",
|
|
1035
|
+
whiteElo: 2882,
|
|
1036
|
+
blackElo: 2803,
|
|
1037
|
+
result: "1/2-1/2",
|
|
1038
|
+
date: "2024.01.15",
|
|
1039
|
+
nextSan: "e4",
|
|
1040
|
+
nextUci: "e2e4",
|
|
1041
|
+
avgElo: 2843,
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
gameId: "def456",
|
|
1045
|
+
url: "https://lichess.org/def456",
|
|
1046
|
+
white: "Firouzja,A",
|
|
1047
|
+
black: "Nepo,I",
|
|
1048
|
+
whiteElo: 2765,
|
|
1049
|
+
blackElo: 2790,
|
|
1050
|
+
result: "1-0",
|
|
1051
|
+
date: "2024.02.01",
|
|
1052
|
+
nextSan: "d4",
|
|
1053
|
+
nextUci: "d2d4",
|
|
1054
|
+
avgElo: 2778,
|
|
1055
|
+
},
|
|
1056
|
+
],
|
|
1057
|
+
};
|
|
1058
|
+
function mockFetchPosition(fen) {
|
|
1059
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1060
|
+
return mockPosition;
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
function mockVariationLines(params, position) {
|
|
1064
|
+
var _a, _b;
|
|
1065
|
+
const lineCount = (_a = params.lineCount) !== null && _a !== void 0 ? _a : 8;
|
|
1066
|
+
const starters = [...position.moves]
|
|
1067
|
+
.sort((a, b) => b.games - a.games)
|
|
1068
|
+
.slice(0, lineCount);
|
|
1069
|
+
const lines = starters.map((move) => ({
|
|
1070
|
+
key: move.uci,
|
|
1071
|
+
label: params.mode === "popularity" ? `1.${move.san}` : `1.${move.san} 1...e5`,
|
|
1072
|
+
moves: [move],
|
|
1073
|
+
uciPath: [move.uci],
|
|
1074
|
+
games: move.games,
|
|
1075
|
+
scorePercent: 54,
|
|
1076
|
+
lastPlayedYear: 2024,
|
|
1077
|
+
avgElo: move.avgElo,
|
|
1078
|
+
}));
|
|
1079
|
+
return {
|
|
1080
|
+
positionKey: position.positionKey,
|
|
1081
|
+
fen: params.fen,
|
|
1082
|
+
mode: params.mode,
|
|
1083
|
+
depth: params.mode === "popularity" ? 1 : ((_b = params.depth) !== null && _b !== void 0 ? _b : 4),
|
|
1084
|
+
lineCount,
|
|
1085
|
+
minElo: params.minElo,
|
|
1086
|
+
maxElo: params.maxElo,
|
|
1087
|
+
lines,
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function mockFetchPositionVariations(params) {
|
|
1091
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1092
|
+
const position = yield mockFetchPosition(params.fen);
|
|
1093
|
+
if (!position)
|
|
1094
|
+
return null;
|
|
1095
|
+
return mockVariationLines(params, position);
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
function mockFetchPositionGames(params) {
|
|
1099
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1100
|
+
const filtered = params.uci
|
|
1101
|
+
? mockGames.games.filter((g) => g.nextUci === params.uci)
|
|
1102
|
+
: mockGames.games;
|
|
1103
|
+
return Object.assign(Object.assign({}, mockGames), { fen: params.fen, minElo: params.minElo, maxElo: params.maxElo, uci: params.uci, topOnly: params.topOnly, games: params.topOnly ? filtered.filter((g) => g.avgElo >= 2500) : filtered });
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
exports.DEFAULT_REFERENCE_LAYOUT = DEFAULT_REFERENCE_LAYOUT;
|
|
1108
|
+
exports.DefaultBoardNav = DefaultBoardNav;
|
|
1109
|
+
exports.DefaultReferenceLayout = DefaultReferenceLayout;
|
|
1110
|
+
exports.DefaultReferencePanel = DefaultReferencePanel;
|
|
1111
|
+
exports.DefaultVariationsStrip = DefaultVariationsStrip;
|
|
1112
|
+
exports.EXPLORER_START_FEN = EXPLORER_START_FEN;
|
|
1113
|
+
exports.ExplorerPlaceholder = ExplorerPlaceholder;
|
|
1114
|
+
exports.GameReplayTrainer = GameReplayTrainer;
|
|
1115
|
+
exports.PositionReferenceExplorer = PositionReferenceExplorer;
|
|
1116
|
+
exports.PositionReferenceExplorerCore = PositionReferenceExplorerCore;
|
|
1117
|
+
exports.applyLineSans = applyLineSans;
|
|
1118
|
+
exports.defaultRenderBoardNav = defaultRenderBoardNav;
|
|
1119
|
+
exports.defaultRenderGamesPanel = defaultRenderGamesPanel;
|
|
1120
|
+
exports.defaultRenderLayout = defaultRenderLayout;
|
|
1121
|
+
exports.defaultRenderMoveStats = defaultRenderMoveStats;
|
|
1122
|
+
exports.defaultRenderStatus = defaultRenderStatus;
|
|
1123
|
+
exports.defaultRenderVariationsStrip = defaultRenderVariationsStrip;
|
|
1124
|
+
exports.fenAfterUci = fenAfterUci;
|
|
1125
|
+
exports.fenAtPly = fenAtPly;
|
|
1126
|
+
exports.findPlyIndexForFen = findPlyIndexForFen;
|
|
1127
|
+
exports.isVariationLineActive = isVariationLineActive;
|
|
1128
|
+
exports.mockFetchPosition = mockFetchPosition;
|
|
1129
|
+
exports.mockFetchPositionGames = mockFetchPositionGames;
|
|
1130
|
+
exports.mockFetchPositionVariations = mockFetchPositionVariations;
|
|
1131
|
+
exports.normalizeFen = normalizeFen;
|
|
1132
|
+
exports.useGameReplayTraining = useGameReplayTraining;
|
|
1133
|
+
exports.whiteScorePercent = whiteScorePercent;
|