react-chess-replay-trainer 0.0.2 → 0.0.4
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/README.md +46 -44
- package/dist/features/replay/hooks/useReplayTrainer.d.ts +4 -0
- package/dist/index.esm.js +28 -7
- package/dist/index.js +27 -6
- package/package.json +56 -56
- package/dist/features/replay/miss/replayMissDisplay.d.ts +0 -16
- package/dist/features/replay/miss/useReplayMissBoard.d.ts +0 -28
- package/dist/features/replay/miss/useReplayMissSequence.d.ts +0 -10
- package/dist/features/replay/miss/useReplayRefutation.d.ts +0 -3
- package/dist/features/replay/refutation/replayRefutation.d.ts +0 -19
package/README.md
CHANGED
|
@@ -1,44 +1,46 @@
|
|
|
1
|
-
# react-chess-replay-trainer
|
|
2
|
-
|
|
3
|
-
A React component for replaying a chess game move-by-move and drilling it.
|
|
4
|
-
|
|
5
|
-
- **Browse** freely through the game (first / prev / next / last / jump) without
|
|
6
|
-
recording anything, so you can pick the part of the game you want to study.
|
|
7
|
-
- **Train White / Train Black / Train Both** start a drill at the current ply:
|
|
8
|
-
you guess each move, and every mistake is reported via `onMiss` so the host can
|
|
9
|
-
(for example) enroll the missed position into a spaced-repetition deck.
|
|
10
|
-
- With **Train White** or **Train Black** you only guess that side's moves; the
|
|
11
|
-
opponent's reply is played automatically after each correct guess, and the
|
|
12
|
-
board is rotated so the trained side is on the bottom.
|
|
13
|
-
- With **Train Both** you drill every ply for both colors.
|
|
14
|
-
- **Analyze** opens the built-in analysis board (`AnalysisBoard` from
|
|
15
|
-
`react-chess-core`) at the current position.
|
|
16
|
-
|
|
17
|
-
Depends on `react-chess-core` (board, engine, analysis board), `react-chessboard`,
|
|
18
|
-
and `chess.js`.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1
|
+
# react-chess-replay-trainer
|
|
2
|
+
|
|
3
|
+
A React component for replaying a chess game move-by-move and drilling it.
|
|
4
|
+
|
|
5
|
+
- **Browse** freely through the game (first / prev / next / last / jump) without
|
|
6
|
+
recording anything, so you can pick the part of the game you want to study.
|
|
7
|
+
- **Train White / Train Black / Train Both** start a drill at the current ply:
|
|
8
|
+
you guess each move, and every mistake is reported via `onMiss` so the host can
|
|
9
|
+
(for example) enroll the missed position into a spaced-repetition deck.
|
|
10
|
+
- With **Train White** or **Train Black** you only guess that side's moves; the
|
|
11
|
+
opponent's reply is played automatically after each correct guess, and the
|
|
12
|
+
board is rotated so the trained side is on the bottom.
|
|
13
|
+
- With **Train Both** you drill every ply for both colors.
|
|
14
|
+
- **Analyze** opens the built-in analysis board (`AnalysisBoard` from
|
|
15
|
+
`react-chess-core`) at the current position.
|
|
16
|
+
|
|
17
|
+
Depends on `react-chess-core` (board, engine, analysis board), `react-chessboard`,
|
|
18
|
+
and `chess.js`.
|
|
19
|
+
|
|
20
|
+
Used in production at [endchess.com](https://endchess.com).
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { ReplayTrainer } from 'react-chess-replay-trainer';
|
|
26
|
+
|
|
27
|
+
<ReplayTrainer
|
|
28
|
+
gameId={gameId}
|
|
29
|
+
startFen={fenWhereUserWasBrowsing}
|
|
30
|
+
fetchGame={fetchGame}
|
|
31
|
+
onMiss={(miss) => enrollMissedPosition(miss)}
|
|
32
|
+
onExit={() => setTraining(null)}
|
|
33
|
+
theme="dark"
|
|
34
|
+
engine={{ depth: 18, multiPv: 3 }}
|
|
35
|
+
/>;
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For a custom shell, use `useReplayTrainer` with `AnalysisBoard` from the same package:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import {
|
|
42
|
+
useReplayTrainer,
|
|
43
|
+
buildReplayAnalysisContext,
|
|
44
|
+
AnalysisBoard,
|
|
45
|
+
} from 'react-chess-replay-trainer';
|
|
46
|
+
```
|
|
@@ -28,6 +28,10 @@ export interface ReplayTrainerState {
|
|
|
28
28
|
/** Revealed/expected move at the current ply (set after a miss or reveal). */
|
|
29
29
|
expectedSan: string | null;
|
|
30
30
|
expectedUci: string | null;
|
|
31
|
+
/** Destination square of the last correct guess (green check overlay). */
|
|
32
|
+
correctMoveSquare: string | null;
|
|
33
|
+
/** FEN shown on the board (includes a pending correct-move ply). */
|
|
34
|
+
displayFen: string;
|
|
31
35
|
canPrev: boolean;
|
|
32
36
|
canNext: boolean;
|
|
33
37
|
goFirst: () => void;
|
package/dist/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
3
|
-
import { createExpectedMoveDropHandler, ThemeProvider, ChessboardDnDProvider, HighlightChessboard, PlyNavigation, AnalysisErrorBoundary, AnalysisBoard } from 'react-chess-core';
|
|
3
|
+
import { useCorrectMoveFeedback, createExpectedMoveDropHandler, fenAfterUci, ThemeProvider, ChessboardDnDProvider, HighlightChessboard, PlyNavigation, AnalysisErrorBoundary, AnalysisBoard } from 'react-chess-core';
|
|
4
4
|
export { AnalysisBoard, AnalysisBoardCore, AnalysisErrorBoundary, DEFAULT_ANALYSIS_LAYOUT, DefaultPlyNavigation, PlyNavigation, MISS_MOVE_ANIMATION_MS as REPLAY_MISS_MOVE_ANIMATION_MS, MISS_REFUTATION_MAX_WAIT_MS as REPLAY_MISS_REFUTATION_MAX_WAIT_MS, MISS_REFUTATION_PAUSE_MS as REPLAY_MISS_REFUTATION_PAUSE_MS, MISS_WRONG_PAUSE_MS as REPLAY_MISS_WRONG_PAUSE_MS, REFUTATION_EVAL_GAP_CP as REPLAY_REFUTATION_EVAL_GAP_CP, REFUTATION_EVAL_GAP_PAWNS as REPLAY_REFUTATION_EVAL_GAP_PAWNS, defaultRenderPlyNavigation, fenAfterUci, getMissDisplay as getReplayMissDisplay, refutationEvalGapCp, refutationFromEvaluation, refutationEngineOptions as replayRefutationEngineOptions, uciFromDrop, useMissBoard as useReplayMissBoard, useMissSequence as useReplayMissSequence, useMissRefutation as useReplayRefutation } from 'react-chess-core';
|
|
5
5
|
import { Chess } from 'chess.js';
|
|
6
6
|
|
|
@@ -78,6 +78,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
78
78
|
const [expectedSan, setExpectedSan] = useState(null);
|
|
79
79
|
const [autoplayActive, setAutoplayActive] = useState(false);
|
|
80
80
|
const [expectedUci, setExpectedUci] = useState(null);
|
|
81
|
+
const [feedbackFen, setFeedbackFen] = useState(null);
|
|
82
|
+
const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, isShowingCorrectMove, } = useCorrectMoveFeedback();
|
|
81
83
|
const fetchGameRef = useRef(fetchGame);
|
|
82
84
|
fetchGameRef.current = fetchGame;
|
|
83
85
|
const onMissRef = useRef(onMiss);
|
|
@@ -88,6 +90,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
88
90
|
const completedFiredRef = useRef(false);
|
|
89
91
|
const modeRef = useRef('browse');
|
|
90
92
|
const trainColorRef = useRef('both');
|
|
93
|
+
const showingCorrectMoveRef = useRef(false);
|
|
94
|
+
showingCorrectMoveRef.current = isShowingCorrectMove;
|
|
91
95
|
useEffect(() => {
|
|
92
96
|
let cancelled = false;
|
|
93
97
|
setLoading(true);
|
|
@@ -131,6 +135,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
131
135
|
const movesUci = useMemo(() => { var _a; return (_a = game === null || game === void 0 ? void 0 : game.movesUci) !== null && _a !== void 0 ? _a : []; }, [game]);
|
|
132
136
|
const totalPly = movesUci.length;
|
|
133
137
|
const fen = useMemo(() => fenAtPly(movesUci, plyIndex), [movesUci, plyIndex]);
|
|
138
|
+
const displayFen = feedbackFen !== null && feedbackFen !== void 0 ? feedbackFen : fen;
|
|
134
139
|
const complete = plyIndex >= totalPly && totalPly > 0;
|
|
135
140
|
const sideToMove$1 = sideToMove(fen);
|
|
136
141
|
const isUserTurn = isTrainSideToMove(trainColor, sideToMove$1);
|
|
@@ -140,7 +145,9 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
140
145
|
setFeedback(null);
|
|
141
146
|
setExpectedSan(null);
|
|
142
147
|
setExpectedUci(null);
|
|
143
|
-
|
|
148
|
+
setFeedbackFen(null);
|
|
149
|
+
clearCorrectMoveFeedback();
|
|
150
|
+
}, [clearCorrectMoveFeedback]);
|
|
144
151
|
const stopAutoplay = useCallback(() => {
|
|
145
152
|
setAutoplayActive(false);
|
|
146
153
|
}, []);
|
|
@@ -220,12 +227,21 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
220
227
|
expectedUci: movesUci[plyIndex],
|
|
221
228
|
enabled: modeRef.current === 'train' &&
|
|
222
229
|
!complete &&
|
|
230
|
+
!showingCorrectMoveRef.current &&
|
|
223
231
|
isTrainSideToMove(trainColorRef.current, sideToMove$1),
|
|
224
|
-
onCorrect: () => {
|
|
232
|
+
onCorrect: (uci) => {
|
|
225
233
|
setFeedback('correct');
|
|
226
234
|
setExpectedSan(null);
|
|
227
235
|
setExpectedUci(null);
|
|
228
|
-
|
|
236
|
+
const nextFen = fenAfterUci(fen, uci);
|
|
237
|
+
if (nextFen) {
|
|
238
|
+
setFeedbackFen(nextFen);
|
|
239
|
+
}
|
|
240
|
+
showCorrectMove(uci.slice(2, 4), () => {
|
|
241
|
+
setFeedbackFen(null);
|
|
242
|
+
setFeedback(null);
|
|
243
|
+
setPlyIndex((p) => p + 1);
|
|
244
|
+
});
|
|
229
245
|
},
|
|
230
246
|
onIncorrect: () => {
|
|
231
247
|
var _a, _b;
|
|
@@ -238,7 +254,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
238
254
|
setExpectedUci(expectedUci);
|
|
239
255
|
recordMiss(plyIndex);
|
|
240
256
|
},
|
|
241
|
-
})(source, target, piece), [complete, movesUci, plyIndex,
|
|
257
|
+
})(source, target, piece), [complete, fen, movesUci, plyIndex, game, recordMiss, sideToMove$1, showCorrectMove]);
|
|
242
258
|
useEffect(() => {
|
|
243
259
|
var _a;
|
|
244
260
|
if (mode === 'train' && complete && !completedFiredRef.current) {
|
|
@@ -267,12 +283,15 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
267
283
|
if (mode !== 'train' || complete || trainColor === 'both' || isUserTurn) {
|
|
268
284
|
return;
|
|
269
285
|
}
|
|
286
|
+
if (isShowingCorrectMove) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
270
289
|
const id = setTimeout(() => {
|
|
271
290
|
setPlyIndex((p) => (p < totalPly ? p + 1 : p));
|
|
272
291
|
clearTransient();
|
|
273
292
|
}, OPPONENT_MOVE_DELAY_MS);
|
|
274
293
|
return () => clearTimeout(id);
|
|
275
|
-
}, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient]);
|
|
294
|
+
}, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove]);
|
|
276
295
|
return {
|
|
277
296
|
game,
|
|
278
297
|
loading,
|
|
@@ -288,6 +307,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
288
307
|
feedback,
|
|
289
308
|
expectedSan,
|
|
290
309
|
expectedUci,
|
|
310
|
+
correctMoveSquare,
|
|
311
|
+
displayFen,
|
|
291
312
|
canPrev: plyIndex > 0,
|
|
292
313
|
canNext: plyIndex < totalPly,
|
|
293
314
|
goFirst,
|
|
@@ -437,7 +458,7 @@ const ReplayTrainer = ({ gameId, fetchGame, startFen, onMiss, onComplete, onExit
|
|
|
437
458
|
]
|
|
438
459
|
: [];
|
|
439
460
|
const draggable = training && !state.complete;
|
|
440
|
-
return (jsxs(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: [jsxs("div", { style: mainContainerStyle(boardWidth, colors), children: [jsxs("div", { style: headerStyle, children: [jsxs("span", { style: playerNameStyle, children: [((_b = game.white) !== null && _b !== void 0 ? _b : 'White'), " vs ", ((_c = game.black) !== null && _c !== void 0 ? _c : 'Black')] }), game.result && jsx("span", { style: subtleTextStyle(colors), children: game.result })] }), jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, position: state.
|
|
461
|
+
return (jsxs(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: [jsxs("div", { style: mainContainerStyle(boardWidth, colors), children: [jsxs("div", { style: headerStyle, children: [jsxs("span", { style: playerNameStyle, children: [((_b = game.white) !== null && _b !== void 0 ? _b : 'White'), " vs ", ((_c = game.black) !== null && _c !== void 0 ? _c : 'Black')] }), game.result && jsx("span", { style: subtleTextStyle(colors), children: game.result })] }), jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, correctMoveSquare: state.correctMoveSquare, position: state.displayFen, boardOrientation: boardOrientation, arePiecesDraggable: draggable && !state.correctMoveSquare, isDraggablePiece: ({ piece }) => {
|
|
441
462
|
if (state.trainColor === 'white')
|
|
442
463
|
return piece[0] === 'w';
|
|
443
464
|
if (state.trainColor === 'black')
|
package/dist/index.js
CHANGED
|
@@ -79,6 +79,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
79
79
|
const [expectedSan, setExpectedSan] = react.useState(null);
|
|
80
80
|
const [autoplayActive, setAutoplayActive] = react.useState(false);
|
|
81
81
|
const [expectedUci, setExpectedUci] = react.useState(null);
|
|
82
|
+
const [feedbackFen, setFeedbackFen] = react.useState(null);
|
|
83
|
+
const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, isShowingCorrectMove, } = reactChessCore.useCorrectMoveFeedback();
|
|
82
84
|
const fetchGameRef = react.useRef(fetchGame);
|
|
83
85
|
fetchGameRef.current = fetchGame;
|
|
84
86
|
const onMissRef = react.useRef(onMiss);
|
|
@@ -89,6 +91,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
89
91
|
const completedFiredRef = react.useRef(false);
|
|
90
92
|
const modeRef = react.useRef('browse');
|
|
91
93
|
const trainColorRef = react.useRef('both');
|
|
94
|
+
const showingCorrectMoveRef = react.useRef(false);
|
|
95
|
+
showingCorrectMoveRef.current = isShowingCorrectMove;
|
|
92
96
|
react.useEffect(() => {
|
|
93
97
|
let cancelled = false;
|
|
94
98
|
setLoading(true);
|
|
@@ -132,6 +136,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
132
136
|
const movesUci = react.useMemo(() => { var _a; return (_a = game === null || game === void 0 ? void 0 : game.movesUci) !== null && _a !== void 0 ? _a : []; }, [game]);
|
|
133
137
|
const totalPly = movesUci.length;
|
|
134
138
|
const fen = react.useMemo(() => fenAtPly(movesUci, plyIndex), [movesUci, plyIndex]);
|
|
139
|
+
const displayFen = feedbackFen !== null && feedbackFen !== void 0 ? feedbackFen : fen;
|
|
135
140
|
const complete = plyIndex >= totalPly && totalPly > 0;
|
|
136
141
|
const sideToMove$1 = sideToMove(fen);
|
|
137
142
|
const isUserTurn = isTrainSideToMove(trainColor, sideToMove$1);
|
|
@@ -141,7 +146,9 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
141
146
|
setFeedback(null);
|
|
142
147
|
setExpectedSan(null);
|
|
143
148
|
setExpectedUci(null);
|
|
144
|
-
|
|
149
|
+
setFeedbackFen(null);
|
|
150
|
+
clearCorrectMoveFeedback();
|
|
151
|
+
}, [clearCorrectMoveFeedback]);
|
|
145
152
|
const stopAutoplay = react.useCallback(() => {
|
|
146
153
|
setAutoplayActive(false);
|
|
147
154
|
}, []);
|
|
@@ -221,12 +228,21 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
221
228
|
expectedUci: movesUci[plyIndex],
|
|
222
229
|
enabled: modeRef.current === 'train' &&
|
|
223
230
|
!complete &&
|
|
231
|
+
!showingCorrectMoveRef.current &&
|
|
224
232
|
isTrainSideToMove(trainColorRef.current, sideToMove$1),
|
|
225
|
-
onCorrect: () => {
|
|
233
|
+
onCorrect: (uci) => {
|
|
226
234
|
setFeedback('correct');
|
|
227
235
|
setExpectedSan(null);
|
|
228
236
|
setExpectedUci(null);
|
|
229
|
-
|
|
237
|
+
const nextFen = reactChessCore.fenAfterUci(fen, uci);
|
|
238
|
+
if (nextFen) {
|
|
239
|
+
setFeedbackFen(nextFen);
|
|
240
|
+
}
|
|
241
|
+
showCorrectMove(uci.slice(2, 4), () => {
|
|
242
|
+
setFeedbackFen(null);
|
|
243
|
+
setFeedback(null);
|
|
244
|
+
setPlyIndex((p) => p + 1);
|
|
245
|
+
});
|
|
230
246
|
},
|
|
231
247
|
onIncorrect: () => {
|
|
232
248
|
var _a, _b;
|
|
@@ -239,7 +255,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
239
255
|
setExpectedUci(expectedUci);
|
|
240
256
|
recordMiss(plyIndex);
|
|
241
257
|
},
|
|
242
|
-
})(source, target, piece), [complete, movesUci, plyIndex,
|
|
258
|
+
})(source, target, piece), [complete, fen, movesUci, plyIndex, game, recordMiss, sideToMove$1, showCorrectMove]);
|
|
243
259
|
react.useEffect(() => {
|
|
244
260
|
var _a;
|
|
245
261
|
if (mode === 'train' && complete && !completedFiredRef.current) {
|
|
@@ -268,12 +284,15 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
268
284
|
if (mode !== 'train' || complete || trainColor === 'both' || isUserTurn) {
|
|
269
285
|
return;
|
|
270
286
|
}
|
|
287
|
+
if (isShowingCorrectMove) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
271
290
|
const id = setTimeout(() => {
|
|
272
291
|
setPlyIndex((p) => (p < totalPly ? p + 1 : p));
|
|
273
292
|
clearTransient();
|
|
274
293
|
}, OPPONENT_MOVE_DELAY_MS);
|
|
275
294
|
return () => clearTimeout(id);
|
|
276
|
-
}, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient]);
|
|
295
|
+
}, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove]);
|
|
277
296
|
return {
|
|
278
297
|
game,
|
|
279
298
|
loading,
|
|
@@ -289,6 +308,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
289
308
|
feedback,
|
|
290
309
|
expectedSan,
|
|
291
310
|
expectedUci,
|
|
311
|
+
correctMoveSquare,
|
|
312
|
+
displayFen,
|
|
292
313
|
canPrev: plyIndex > 0,
|
|
293
314
|
canNext: plyIndex < totalPly,
|
|
294
315
|
goFirst,
|
|
@@ -438,7 +459,7 @@ const ReplayTrainer = ({ gameId, fetchGame, startFen, onMiss, onComplete, onExit
|
|
|
438
459
|
]
|
|
439
460
|
: [];
|
|
440
461
|
const draggable = training && !state.complete;
|
|
441
|
-
return (jsxRuntime.jsxs(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: [jsxRuntime.jsxs("div", { style: mainContainerStyle(boardWidth, colors), children: [jsxRuntime.jsxs("div", { style: headerStyle, children: [jsxRuntime.jsxs("span", { style: playerNameStyle, children: [((_b = game.white) !== null && _b !== void 0 ? _b : 'White'), " vs ", ((_c = game.black) !== null && _c !== void 0 ? _c : 'Black')] }), game.result && jsxRuntime.jsx("span", { style: subtleTextStyle(colors), children: game.result })] }), jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, position: state.
|
|
462
|
+
return (jsxRuntime.jsxs(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: [jsxRuntime.jsxs("div", { style: mainContainerStyle(boardWidth, colors), children: [jsxRuntime.jsxs("div", { style: headerStyle, children: [jsxRuntime.jsxs("span", { style: playerNameStyle, children: [((_b = game.white) !== null && _b !== void 0 ? _b : 'White'), " vs ", ((_c = game.black) !== null && _c !== void 0 ? _c : 'Black')] }), game.result && jsxRuntime.jsx("span", { style: subtleTextStyle(colors), children: game.result })] }), jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, correctMoveSquare: state.correctMoveSquare, position: state.displayFen, boardOrientation: boardOrientation, arePiecesDraggable: draggable && !state.correctMoveSquare, isDraggablePiece: ({ piece }) => {
|
|
442
463
|
if (state.trainColor === 'white')
|
|
443
464
|
return piece[0] === 'w';
|
|
444
465
|
if (state.trainColor === 'black')
|
package/package.json
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "react-chess-replay-trainer",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "React component for browsing a chess game and drilling its moves, reporting misses (uses react-chess-core for board and analysis)",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "Robert Blackwell",
|
|
7
|
-
"main": "dist/index.js",
|
|
8
|
-
"module": "dist/index.esm.js",
|
|
9
|
-
"types": "dist/index.d.ts",
|
|
10
|
-
"files": [
|
|
11
|
-
"dist"
|
|
12
|
-
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "rollup -c",
|
|
15
|
-
"prepublishOnly": "npm run build",
|
|
16
|
-
"storybook": "storybook dev -p 6008",
|
|
17
|
-
"build-storybook": "storybook build"
|
|
18
|
-
},
|
|
19
|
-
"dependencies": {
|
|
20
|
-
"rollup": "^4.22.2",
|
|
21
|
-
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
22
|
-
"typescript": "^5.6.2"
|
|
23
|
-
},
|
|
24
|
-
"peerDependencies": {
|
|
25
|
-
"chess.js": "^1.0.0-beta.8",
|
|
26
|
-
"react": "^18.3.1",
|
|
27
|
-
"react-chess-core": "^0.1.1",
|
|
28
|
-
"react-chessboard": "^4.7.1"
|
|
29
|
-
},
|
|
30
|
-
"devDependencies": {
|
|
31
|
-
"@chromatic-com/storybook": "^1.9.0",
|
|
32
|
-
"@rollup/plugin-commonjs": "^26.0.1",
|
|
33
|
-
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
34
|
-
"@rollup/plugin-typescript": "^12.3.0",
|
|
35
|
-
"@storybook/addon-essentials": "^8.2.9",
|
|
36
|
-
"@storybook/addon-interactions": "^8.2.9",
|
|
37
|
-
"@storybook/addon-links": "^8.2.9",
|
|
38
|
-
"@storybook/addon-onboarding": "^8.2.9",
|
|
39
|
-
"@storybook/blocks": "^8.2.9",
|
|
40
|
-
"@storybook/preset-typescript": "^3.0.0",
|
|
41
|
-
"@storybook/react": "^8.2.9",
|
|
42
|
-
"@storybook/react-vite": "^8.2.9",
|
|
43
|
-
"@storybook/test": "^8.2.9",
|
|
44
|
-
"@types/react": "^18.3.12",
|
|
45
|
-
"@types/react-dom": "^18.3.1",
|
|
46
|
-
"@vitejs/plugin-react": "^4.3.1",
|
|
47
|
-
"chess.js": "^1.0.0-beta.8",
|
|
48
|
-
"react": "^18.3.1",
|
|
49
|
-
"react-chess-core": "^0.1.1",
|
|
50
|
-
"react-chessboard": "^4.7.1",
|
|
51
|
-
"react-dom": "^18.3.1",
|
|
52
|
-
"storybook": "^8.2.9",
|
|
53
|
-
"tslib": "^2.8.1",
|
|
54
|
-
"vite-tsconfig-paths": "^5.0.1"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "react-chess-replay-trainer",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "React component for browsing a chess game and drilling its moves, reporting misses (uses react-chess-core for board and analysis)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Robert Blackwell",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.esm.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "rollup -c",
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
16
|
+
"storybook": "storybook dev -p 6008",
|
|
17
|
+
"build-storybook": "storybook build"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"rollup": "^4.22.2",
|
|
21
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
22
|
+
"typescript": "^5.6.2"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"chess.js": "^1.0.0-beta.8",
|
|
26
|
+
"react": "^18.3.1",
|
|
27
|
+
"react-chess-core": "^0.1.1",
|
|
28
|
+
"react-chessboard": "^4.7.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@chromatic-com/storybook": "^1.9.0",
|
|
32
|
+
"@rollup/plugin-commonjs": "^26.0.1",
|
|
33
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
34
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
35
|
+
"@storybook/addon-essentials": "^8.2.9",
|
|
36
|
+
"@storybook/addon-interactions": "^8.2.9",
|
|
37
|
+
"@storybook/addon-links": "^8.2.9",
|
|
38
|
+
"@storybook/addon-onboarding": "^8.2.9",
|
|
39
|
+
"@storybook/blocks": "^8.2.9",
|
|
40
|
+
"@storybook/preset-typescript": "^3.0.0",
|
|
41
|
+
"@storybook/react": "^8.2.9",
|
|
42
|
+
"@storybook/react-vite": "^8.2.9",
|
|
43
|
+
"@storybook/test": "^8.2.9",
|
|
44
|
+
"@types/react": "^18.3.12",
|
|
45
|
+
"@types/react-dom": "^18.3.1",
|
|
46
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
47
|
+
"chess.js": "^1.0.0-beta.8",
|
|
48
|
+
"react": "^18.3.1",
|
|
49
|
+
"react-chess-core": "^0.1.1",
|
|
50
|
+
"react-chessboard": "^4.7.1",
|
|
51
|
+
"react-dom": "^18.3.1",
|
|
52
|
+
"storybook": "^8.2.9",
|
|
53
|
+
"tslib": "^2.8.1",
|
|
54
|
+
"vite-tsconfig-paths": "^5.0.1"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type MissSequencePhase = 'wrong' | 'refutation' | 'answer' | 'retry';
|
|
2
|
-
export type MissSequenceState = {
|
|
3
|
-
setupFen: string;
|
|
4
|
-
attemptedUci: string;
|
|
5
|
-
phase: MissSequencePhase;
|
|
6
|
-
};
|
|
7
|
-
export type ReplayMissDisplay = {
|
|
8
|
-
fen: string | null;
|
|
9
|
-
arrows: [string, string, string][];
|
|
10
|
-
animating: boolean;
|
|
11
|
-
};
|
|
12
|
-
export declare const REPLAY_MISS_WRONG_PAUSE_MS = 450;
|
|
13
|
-
export declare const REPLAY_MISS_REFUTATION_PAUSE_MS = 900;
|
|
14
|
-
export declare const REPLAY_MISS_REFUTATION_MAX_WAIT_MS = 4000;
|
|
15
|
-
export declare const REPLAY_MISS_MOVE_ANIMATION_MS = 220;
|
|
16
|
-
export declare function getReplayMissDisplay(sequence: MissSequenceState | null, expectedUci: string | null, refutationUci: string | null, answerArrowColor: string): ReplayMissDisplay;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { AnalysisEngineOptions } from 'react-chess-core';
|
|
2
|
-
type ReplayMissFeedback = 'correct' | 'incorrect' | null;
|
|
3
|
-
export declare function useReplayMissBoard({ feedback, expectedUci, positionFen, answerArrowColor, autoShowWrongMoves, engineOptions, }: {
|
|
4
|
-
feedback: ReplayMissFeedback;
|
|
5
|
-
expectedUci: string | null;
|
|
6
|
-
positionFen: string;
|
|
7
|
-
answerArrowColor: string;
|
|
8
|
-
autoShowWrongMoves?: boolean;
|
|
9
|
-
engineOptions?: AnalysisEngineOptions;
|
|
10
|
-
}): {
|
|
11
|
-
missSequence: {
|
|
12
|
-
sequence: import("./replayMissDisplay").MissSequenceState | null;
|
|
13
|
-
refutation: import("..").ReplayRefutationResult;
|
|
14
|
-
display: import("./replayMissDisplay").ReplayMissDisplay;
|
|
15
|
-
startSequence: (setupFen: string, attemptedUci: string) => void;
|
|
16
|
-
clearSequence: () => void;
|
|
17
|
-
};
|
|
18
|
-
refutation: import("..").ReplayRefutationResult;
|
|
19
|
-
customArrows: [string, string, string][];
|
|
20
|
-
boardPosition: string;
|
|
21
|
-
boardAnimating: boolean;
|
|
22
|
-
wrapDropHandler: (onDrop: (source: string, target: string, piece: string) => boolean, { enabled, dropFen, expectedMoveUci, }: {
|
|
23
|
-
enabled: boolean;
|
|
24
|
-
dropFen?: string;
|
|
25
|
-
expectedMoveUci?: string | null;
|
|
26
|
-
}) => (source: string, target: string, piece: string) => boolean;
|
|
27
|
-
};
|
|
28
|
-
export {};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { AnalysisEngineOptions } from 'react-chess-core';
|
|
2
|
-
import { type MissSequencePhase, type MissSequenceState, type ReplayMissDisplay } from './replayMissDisplay';
|
|
3
|
-
export type { MissSequencePhase, ReplayMissDisplay };
|
|
4
|
-
export declare function useReplayMissSequence(feedback: 'correct' | 'incorrect' | null, expectedUci: string | null, engineOptions: AnalysisEngineOptions, answerArrowColor: string, autoShowWrongMoves: boolean): {
|
|
5
|
-
sequence: MissSequenceState | null;
|
|
6
|
-
refutation: import("..").ReplayRefutationResult;
|
|
7
|
-
display: ReplayMissDisplay;
|
|
8
|
-
startSequence: (setupFen: string, attemptedUci: string) => void;
|
|
9
|
-
clearSequence: () => void;
|
|
10
|
-
};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { type AnalysisEngineOptions } from 'react-chess-core';
|
|
2
|
-
import { type ReplayRefutationResult } from '../refutation/replayRefutation';
|
|
3
|
-
export declare function useReplayRefutation(setupFen: string | null, attemptedUci: string | null, expectedUci: string | null, enabled: boolean, engineOptions: AnalysisEngineOptions): ReplayRefutationResult;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type AnalysisEngineOptions, type EngineEvaluation, type EngineLine } from 'react-chess-core';
|
|
2
|
-
/** Minimum eval loss (pawns) from the wrong move before showing a refutation. */
|
|
3
|
-
export declare const REPLAY_REFUTATION_EVAL_GAP_PAWNS = 0.5;
|
|
4
|
-
export declare const REPLAY_REFUTATION_EVAL_GAP_CP: number;
|
|
5
|
-
export type ReplayRefutationResult = {
|
|
6
|
-
fenAfterWrong: string | null;
|
|
7
|
-
refutationUci: string | null;
|
|
8
|
-
refutationSan: string | null;
|
|
9
|
-
refutationLine: string | null;
|
|
10
|
-
loading: boolean;
|
|
11
|
-
error: string | null;
|
|
12
|
-
};
|
|
13
|
-
export declare const replayRefutationEngineOptions: AnalysisEngineOptions;
|
|
14
|
-
export declare function fenAfterUci(fen: string, uci: string): string | null;
|
|
15
|
-
/** Centipawn score from side to move, comparable across sibling positions. */
|
|
16
|
-
export declare function lineEvalCpForGap(line: EngineLine | undefined): number | null;
|
|
17
|
-
/** How much better the opponent's eval is after the wrong move vs the correct one. */
|
|
18
|
-
export declare function refutationEvalGapCp(evalAfterWrong: EngineEvaluation, evalAfterCorrect: EngineEvaluation): number | null;
|
|
19
|
-
export declare function refutationFromEvaluation(fenAfterWrong: string, evaluation: EngineEvaluation, evalGapCp: number | null, evalGapApplies: boolean, evalGapLoading: boolean): Omit<ReplayRefutationResult, 'fenAfterWrong'>;
|