react-chess-replay-trainer 0.0.4 → 0.0.6
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/features/replay/hooks/useReplayTrainer.d.ts +4 -0
- package/dist/index.esm.js +32 -18
- package/dist/index.js +31 -17
- package/package.json +1 -1
|
@@ -30,8 +30,12 @@ export interface ReplayTrainerState {
|
|
|
30
30
|
expectedUci: string | null;
|
|
31
31
|
/** Destination square of the last correct guess (green check overlay). */
|
|
32
32
|
correctMoveSquare: string | null;
|
|
33
|
+
/** Origin square of the last rejected guess (red X overlay). */
|
|
34
|
+
incorrectMoveSquare: string | null;
|
|
33
35
|
/** FEN shown on the board (includes a pending correct-move ply). */
|
|
34
36
|
displayFen: string;
|
|
37
|
+
/** UCI of the move that produced the current board position. */
|
|
38
|
+
lastMoveUci: string | null;
|
|
35
39
|
canPrev: boolean;
|
|
36
40
|
canNext: boolean;
|
|
37
41
|
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 {
|
|
3
|
+
import { useTrainingMoveFeedback, lastMoveUciAtPly, fenAfterUci, DEFAULT_ANSWER_ARROW_COLOR, 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
|
|
|
@@ -72,14 +72,14 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
72
72
|
const [loading, setLoading] = useState(true);
|
|
73
73
|
const [error, setError] = useState(null);
|
|
74
74
|
const [mode, setMode] = useState('browse');
|
|
75
|
-
const [trainColor, setTrainColor] = useState('
|
|
75
|
+
const [trainColor, setTrainColor] = useState('white');
|
|
76
76
|
const [plyIndex, setPlyIndex] = useState(0);
|
|
77
77
|
const [feedback, setFeedback] = useState(null);
|
|
78
78
|
const [expectedSan, setExpectedSan] = useState(null);
|
|
79
79
|
const [autoplayActive, setAutoplayActive] = useState(false);
|
|
80
80
|
const [expectedUci, setExpectedUci] = useState(null);
|
|
81
81
|
const [feedbackFen, setFeedbackFen] = useState(null);
|
|
82
|
-
const { correctMoveSquare, showCorrectMove,
|
|
82
|
+
const { correctMoveSquare, incorrectMoveSquare, showCorrectMove, clearMoveFeedback, isShowingCorrectMove, isShowingIncorrectMove, createDropHandler, } = useTrainingMoveFeedback();
|
|
83
83
|
const fetchGameRef = useRef(fetchGame);
|
|
84
84
|
fetchGameRef.current = fetchGame;
|
|
85
85
|
const onMissRef = useRef(onMiss);
|
|
@@ -89,9 +89,11 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
89
89
|
const recordedRef = useRef(new Set());
|
|
90
90
|
const completedFiredRef = useRef(false);
|
|
91
91
|
const modeRef = useRef('browse');
|
|
92
|
-
const trainColorRef = useRef('
|
|
92
|
+
const trainColorRef = useRef('white');
|
|
93
93
|
const showingCorrectMoveRef = useRef(false);
|
|
94
94
|
showingCorrectMoveRef.current = isShowingCorrectMove;
|
|
95
|
+
const showingIncorrectMoveRef = useRef(false);
|
|
96
|
+
showingIncorrectMoveRef.current = isShowingIncorrectMove;
|
|
95
97
|
useEffect(() => {
|
|
96
98
|
let cancelled = false;
|
|
97
99
|
setLoading(true);
|
|
@@ -101,8 +103,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
101
103
|
completedFiredRef.current = false;
|
|
102
104
|
setMode('browse');
|
|
103
105
|
modeRef.current = 'browse';
|
|
104
|
-
setTrainColor('
|
|
105
|
-
trainColorRef.current = '
|
|
106
|
+
setTrainColor('white');
|
|
107
|
+
trainColorRef.current = 'white';
|
|
106
108
|
setFeedback(null);
|
|
107
109
|
setExpectedSan(null);
|
|
108
110
|
setExpectedUci(null);
|
|
@@ -136,6 +138,13 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
136
138
|
const totalPly = movesUci.length;
|
|
137
139
|
const fen = useMemo(() => fenAtPly(movesUci, plyIndex), [movesUci, plyIndex]);
|
|
138
140
|
const displayFen = feedbackFen !== null && feedbackFen !== void 0 ? feedbackFen : fen;
|
|
141
|
+
const lastMoveUci = useMemo(() => {
|
|
142
|
+
var _a;
|
|
143
|
+
if (feedbackFen) {
|
|
144
|
+
return (_a = movesUci[plyIndex]) !== null && _a !== void 0 ? _a : null;
|
|
145
|
+
}
|
|
146
|
+
return lastMoveUciAtPly(movesUci, plyIndex);
|
|
147
|
+
}, [feedbackFen, movesUci, plyIndex]);
|
|
139
148
|
const complete = plyIndex >= totalPly && totalPly > 0;
|
|
140
149
|
const sideToMove$1 = sideToMove(fen);
|
|
141
150
|
const isUserTurn = isTrainSideToMove(trainColor, sideToMove$1);
|
|
@@ -146,8 +155,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
146
155
|
setExpectedSan(null);
|
|
147
156
|
setExpectedUci(null);
|
|
148
157
|
setFeedbackFen(null);
|
|
149
|
-
|
|
150
|
-
}, [
|
|
158
|
+
clearMoveFeedback();
|
|
159
|
+
}, [clearMoveFeedback]);
|
|
151
160
|
const stopAutoplay = useCallback(() => {
|
|
152
161
|
setAutoplayActive(false);
|
|
153
162
|
}, []);
|
|
@@ -177,7 +186,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
177
186
|
}
|
|
178
187
|
startAutoplay();
|
|
179
188
|
}, [autoplayActive, startAutoplay, stopAutoplay]);
|
|
180
|
-
const startTraining = useCallback((color = '
|
|
189
|
+
const startTraining = useCallback((color = 'white') => {
|
|
181
190
|
setAutoplayActive(false);
|
|
182
191
|
setTrainColor(color);
|
|
183
192
|
trainColorRef.current = color;
|
|
@@ -222,14 +231,15 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
222
231
|
setFeedback('incorrect');
|
|
223
232
|
recordMiss(plyIndex);
|
|
224
233
|
}, [complete, game, movesUci, plyIndex, recordMiss, isUserTurn]);
|
|
225
|
-
const handleDrop = useCallback((source, target, piece) =>
|
|
234
|
+
const handleDrop = useCallback((source, target, piece) => createDropHandler({
|
|
226
235
|
fen,
|
|
227
236
|
expectedUci: movesUci[plyIndex],
|
|
228
237
|
enabled: modeRef.current === 'train' &&
|
|
229
238
|
!complete &&
|
|
230
239
|
!showingCorrectMoveRef.current &&
|
|
240
|
+
!showingIncorrectMoveRef.current &&
|
|
231
241
|
isTrainSideToMove(trainColorRef.current, sideToMove$1),
|
|
232
|
-
onCorrect: (uci) => {
|
|
242
|
+
onCorrect: ({ uci, targetSquare }) => {
|
|
233
243
|
setFeedback('correct');
|
|
234
244
|
setExpectedSan(null);
|
|
235
245
|
setExpectedUci(null);
|
|
@@ -237,7 +247,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
237
247
|
if (nextFen) {
|
|
238
248
|
setFeedbackFen(nextFen);
|
|
239
249
|
}
|
|
240
|
-
showCorrectMove(
|
|
250
|
+
showCorrectMove(targetSquare, () => {
|
|
241
251
|
setFeedbackFen(null);
|
|
242
252
|
setFeedback(null);
|
|
243
253
|
setPlyIndex((p) => p + 1);
|
|
@@ -254,7 +264,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
254
264
|
setExpectedUci(expectedUci);
|
|
255
265
|
recordMiss(plyIndex);
|
|
256
266
|
},
|
|
257
|
-
})(source, target, piece), [complete, fen, movesUci, plyIndex, game, recordMiss, sideToMove$1, showCorrectMove]);
|
|
267
|
+
})(source, target, piece), [complete, createDropHandler, fen, movesUci, plyIndex, game, recordMiss, sideToMove$1, showCorrectMove]);
|
|
258
268
|
useEffect(() => {
|
|
259
269
|
var _a;
|
|
260
270
|
if (mode === 'train' && complete && !completedFiredRef.current) {
|
|
@@ -283,7 +293,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
283
293
|
if (mode !== 'train' || complete || trainColor === 'both' || isUserTurn) {
|
|
284
294
|
return;
|
|
285
295
|
}
|
|
286
|
-
if (isShowingCorrectMove) {
|
|
296
|
+
if (isShowingCorrectMove || isShowingIncorrectMove) {
|
|
287
297
|
return;
|
|
288
298
|
}
|
|
289
299
|
const id = setTimeout(() => {
|
|
@@ -291,7 +301,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
291
301
|
clearTransient();
|
|
292
302
|
}, OPPONENT_MOVE_DELAY_MS);
|
|
293
303
|
return () => clearTimeout(id);
|
|
294
|
-
}, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove]);
|
|
304
|
+
}, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove, isShowingIncorrectMove]);
|
|
295
305
|
return {
|
|
296
306
|
game,
|
|
297
307
|
loading,
|
|
@@ -308,7 +318,9 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
308
318
|
expectedSan,
|
|
309
319
|
expectedUci,
|
|
310
320
|
correctMoveSquare,
|
|
321
|
+
incorrectMoveSquare,
|
|
311
322
|
displayFen,
|
|
323
|
+
lastMoveUci,
|
|
312
324
|
canPrev: plyIndex > 0,
|
|
313
325
|
canNext: plyIndex < totalPly,
|
|
314
326
|
goFirst,
|
|
@@ -453,18 +465,20 @@ const ReplayTrainer = ({ gameId, fetchGame, startFen, onMiss, onComplete, onExit
|
|
|
453
465
|
[
|
|
454
466
|
state.expectedUci.slice(0, 2),
|
|
455
467
|
state.expectedUci.slice(2, 4),
|
|
456
|
-
|
|
468
|
+
DEFAULT_ANSWER_ARROW_COLOR,
|
|
457
469
|
],
|
|
458
470
|
]
|
|
459
471
|
: [];
|
|
460
472
|
const draggable = training && !state.complete;
|
|
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:
|
|
473
|
+
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: state.incorrectMoveSquare, correctMoveSquare: state.correctMoveSquare, position: state.displayFen, boardOrientation: boardOrientation, arePiecesDraggable: draggable &&
|
|
474
|
+
!state.correctMoveSquare &&
|
|
475
|
+
!state.incorrectMoveSquare, isDraggablePiece: ({ piece }) => {
|
|
462
476
|
if (state.trainColor === 'white')
|
|
463
477
|
return piece[0] === 'w';
|
|
464
478
|
if (state.trainColor === 'black')
|
|
465
479
|
return piece[0] === 'b';
|
|
466
480
|
return piece[0] === state.sideToMove;
|
|
467
|
-
}, onPieceDrop: (source, target, piece) => state.handleDrop(source, target, piece), customArrows: customArrows, promotionDialogVariant: "modal", areArrowsAllowed: false, customBoardStyle: customBoardStyle }) }), jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4 }, children: [jsx(PlyNavigation, { plyIndex: state.plyIndex, totalPly: state.totalPly, canPrev: state.canPrev, canNext: state.canNext, onGoFirst: state.goFirst, onGoPrev: state.goPrev, onGoNext: state.goNext, onGoLast: state.goLast, onGoTo: state.goTo, theme: theme, showScrubber: showPlyScrubber, renderPlyNavigation: renderPlyNavigation }), jsx("button", { type: "button", onClick: state.toggleAutoplay, disabled: !state.autoplayActive && !state.canNext, style: buttonStyle(colors, state.autoplayActive ? 'ghost' : 'primary'), "aria-label": state.autoplayActive ? 'Stop autoplay' : 'Autoplay game', children: state.autoplayActive ? 'Stop' : 'Play' })] }), jsxs("div", { style: statusLineStyle(colors), children: ["Half move ", Math.min(state.plyIndex + (state.complete ? 0 : 1), state.totalPly), " of", ' ', state.totalPly, training && !state.complete && (jsxs(Fragment, { children: [` · ${TRAIN_COLOR_LABEL[state.trainColor]}`, state.trainColor === 'both'
|
|
481
|
+
}, onPieceDrop: (source, target, piece) => state.handleDrop(source, target, piece), customArrows: customArrows, lastMoveUci: state.lastMoveUci, promotionDialogVariant: "modal", areArrowsAllowed: false, customBoardStyle: customBoardStyle }) }), jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4 }, children: [jsx(PlyNavigation, { plyIndex: state.plyIndex, totalPly: state.totalPly, canPrev: state.canPrev, canNext: state.canNext, onGoFirst: state.goFirst, onGoPrev: state.goPrev, onGoNext: state.goNext, onGoLast: state.goLast, onGoTo: state.goTo, theme: theme, showScrubber: showPlyScrubber, renderPlyNavigation: renderPlyNavigation }), jsx("button", { type: "button", onClick: state.toggleAutoplay, disabled: !state.autoplayActive && !state.canNext, style: buttonStyle(colors, state.autoplayActive ? 'ghost' : 'primary'), "aria-label": state.autoplayActive ? 'Stop autoplay' : 'Autoplay game', children: state.autoplayActive ? 'Stop' : 'Play' })] }), jsxs("div", { style: statusLineStyle(colors), children: ["Half move ", Math.min(state.plyIndex + (state.complete ? 0 : 1), state.totalPly), " of", ' ', state.totalPly, training && !state.complete && (jsxs(Fragment, { children: [` · ${TRAIN_COLOR_LABEL[state.trainColor]}`, state.trainColor === 'both'
|
|
468
482
|
? ` · ${state.sideToMove === 'b' ? 'Black' : 'White'} to move`
|
|
469
483
|
: state.isUserTurn
|
|
470
484
|
? ' · Your move'
|
package/dist/index.js
CHANGED
|
@@ -73,14 +73,14 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
73
73
|
const [loading, setLoading] = react.useState(true);
|
|
74
74
|
const [error, setError] = react.useState(null);
|
|
75
75
|
const [mode, setMode] = react.useState('browse');
|
|
76
|
-
const [trainColor, setTrainColor] = react.useState('
|
|
76
|
+
const [trainColor, setTrainColor] = react.useState('white');
|
|
77
77
|
const [plyIndex, setPlyIndex] = react.useState(0);
|
|
78
78
|
const [feedback, setFeedback] = react.useState(null);
|
|
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
82
|
const [feedbackFen, setFeedbackFen] = react.useState(null);
|
|
83
|
-
const { correctMoveSquare, showCorrectMove,
|
|
83
|
+
const { correctMoveSquare, incorrectMoveSquare, showCorrectMove, clearMoveFeedback, isShowingCorrectMove, isShowingIncorrectMove, createDropHandler, } = reactChessCore.useTrainingMoveFeedback();
|
|
84
84
|
const fetchGameRef = react.useRef(fetchGame);
|
|
85
85
|
fetchGameRef.current = fetchGame;
|
|
86
86
|
const onMissRef = react.useRef(onMiss);
|
|
@@ -90,9 +90,11 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
90
90
|
const recordedRef = react.useRef(new Set());
|
|
91
91
|
const completedFiredRef = react.useRef(false);
|
|
92
92
|
const modeRef = react.useRef('browse');
|
|
93
|
-
const trainColorRef = react.useRef('
|
|
93
|
+
const trainColorRef = react.useRef('white');
|
|
94
94
|
const showingCorrectMoveRef = react.useRef(false);
|
|
95
95
|
showingCorrectMoveRef.current = isShowingCorrectMove;
|
|
96
|
+
const showingIncorrectMoveRef = react.useRef(false);
|
|
97
|
+
showingIncorrectMoveRef.current = isShowingIncorrectMove;
|
|
96
98
|
react.useEffect(() => {
|
|
97
99
|
let cancelled = false;
|
|
98
100
|
setLoading(true);
|
|
@@ -102,8 +104,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
102
104
|
completedFiredRef.current = false;
|
|
103
105
|
setMode('browse');
|
|
104
106
|
modeRef.current = 'browse';
|
|
105
|
-
setTrainColor('
|
|
106
|
-
trainColorRef.current = '
|
|
107
|
+
setTrainColor('white');
|
|
108
|
+
trainColorRef.current = 'white';
|
|
107
109
|
setFeedback(null);
|
|
108
110
|
setExpectedSan(null);
|
|
109
111
|
setExpectedUci(null);
|
|
@@ -137,6 +139,13 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
137
139
|
const totalPly = movesUci.length;
|
|
138
140
|
const fen = react.useMemo(() => fenAtPly(movesUci, plyIndex), [movesUci, plyIndex]);
|
|
139
141
|
const displayFen = feedbackFen !== null && feedbackFen !== void 0 ? feedbackFen : fen;
|
|
142
|
+
const lastMoveUci = react.useMemo(() => {
|
|
143
|
+
var _a;
|
|
144
|
+
if (feedbackFen) {
|
|
145
|
+
return (_a = movesUci[plyIndex]) !== null && _a !== void 0 ? _a : null;
|
|
146
|
+
}
|
|
147
|
+
return reactChessCore.lastMoveUciAtPly(movesUci, plyIndex);
|
|
148
|
+
}, [feedbackFen, movesUci, plyIndex]);
|
|
140
149
|
const complete = plyIndex >= totalPly && totalPly > 0;
|
|
141
150
|
const sideToMove$1 = sideToMove(fen);
|
|
142
151
|
const isUserTurn = isTrainSideToMove(trainColor, sideToMove$1);
|
|
@@ -147,8 +156,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
147
156
|
setExpectedSan(null);
|
|
148
157
|
setExpectedUci(null);
|
|
149
158
|
setFeedbackFen(null);
|
|
150
|
-
|
|
151
|
-
}, [
|
|
159
|
+
clearMoveFeedback();
|
|
160
|
+
}, [clearMoveFeedback]);
|
|
152
161
|
const stopAutoplay = react.useCallback(() => {
|
|
153
162
|
setAutoplayActive(false);
|
|
154
163
|
}, []);
|
|
@@ -178,7 +187,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
178
187
|
}
|
|
179
188
|
startAutoplay();
|
|
180
189
|
}, [autoplayActive, startAutoplay, stopAutoplay]);
|
|
181
|
-
const startTraining = react.useCallback((color = '
|
|
190
|
+
const startTraining = react.useCallback((color = 'white') => {
|
|
182
191
|
setAutoplayActive(false);
|
|
183
192
|
setTrainColor(color);
|
|
184
193
|
trainColorRef.current = color;
|
|
@@ -223,14 +232,15 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
223
232
|
setFeedback('incorrect');
|
|
224
233
|
recordMiss(plyIndex);
|
|
225
234
|
}, [complete, game, movesUci, plyIndex, recordMiss, isUserTurn]);
|
|
226
|
-
const handleDrop = react.useCallback((source, target, piece) =>
|
|
235
|
+
const handleDrop = react.useCallback((source, target, piece) => createDropHandler({
|
|
227
236
|
fen,
|
|
228
237
|
expectedUci: movesUci[plyIndex],
|
|
229
238
|
enabled: modeRef.current === 'train' &&
|
|
230
239
|
!complete &&
|
|
231
240
|
!showingCorrectMoveRef.current &&
|
|
241
|
+
!showingIncorrectMoveRef.current &&
|
|
232
242
|
isTrainSideToMove(trainColorRef.current, sideToMove$1),
|
|
233
|
-
onCorrect: (uci) => {
|
|
243
|
+
onCorrect: ({ uci, targetSquare }) => {
|
|
234
244
|
setFeedback('correct');
|
|
235
245
|
setExpectedSan(null);
|
|
236
246
|
setExpectedUci(null);
|
|
@@ -238,7 +248,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
238
248
|
if (nextFen) {
|
|
239
249
|
setFeedbackFen(nextFen);
|
|
240
250
|
}
|
|
241
|
-
showCorrectMove(
|
|
251
|
+
showCorrectMove(targetSquare, () => {
|
|
242
252
|
setFeedbackFen(null);
|
|
243
253
|
setFeedback(null);
|
|
244
254
|
setPlyIndex((p) => p + 1);
|
|
@@ -255,7 +265,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
255
265
|
setExpectedUci(expectedUci);
|
|
256
266
|
recordMiss(plyIndex);
|
|
257
267
|
},
|
|
258
|
-
})(source, target, piece), [complete, fen, movesUci, plyIndex, game, recordMiss, sideToMove$1, showCorrectMove]);
|
|
268
|
+
})(source, target, piece), [complete, createDropHandler, fen, movesUci, plyIndex, game, recordMiss, sideToMove$1, showCorrectMove]);
|
|
259
269
|
react.useEffect(() => {
|
|
260
270
|
var _a;
|
|
261
271
|
if (mode === 'train' && complete && !completedFiredRef.current) {
|
|
@@ -284,7 +294,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
284
294
|
if (mode !== 'train' || complete || trainColor === 'both' || isUserTurn) {
|
|
285
295
|
return;
|
|
286
296
|
}
|
|
287
|
-
if (isShowingCorrectMove) {
|
|
297
|
+
if (isShowingCorrectMove || isShowingIncorrectMove) {
|
|
288
298
|
return;
|
|
289
299
|
}
|
|
290
300
|
const id = setTimeout(() => {
|
|
@@ -292,7 +302,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
292
302
|
clearTransient();
|
|
293
303
|
}, OPPONENT_MOVE_DELAY_MS);
|
|
294
304
|
return () => clearTimeout(id);
|
|
295
|
-
}, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove]);
|
|
305
|
+
}, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove, isShowingIncorrectMove]);
|
|
296
306
|
return {
|
|
297
307
|
game,
|
|
298
308
|
loading,
|
|
@@ -309,7 +319,9 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
|
|
|
309
319
|
expectedSan,
|
|
310
320
|
expectedUci,
|
|
311
321
|
correctMoveSquare,
|
|
322
|
+
incorrectMoveSquare,
|
|
312
323
|
displayFen,
|
|
324
|
+
lastMoveUci,
|
|
313
325
|
canPrev: plyIndex > 0,
|
|
314
326
|
canNext: plyIndex < totalPly,
|
|
315
327
|
goFirst,
|
|
@@ -454,18 +466,20 @@ const ReplayTrainer = ({ gameId, fetchGame, startFen, onMiss, onComplete, onExit
|
|
|
454
466
|
[
|
|
455
467
|
state.expectedUci.slice(0, 2),
|
|
456
468
|
state.expectedUci.slice(2, 4),
|
|
457
|
-
|
|
469
|
+
reactChessCore.DEFAULT_ANSWER_ARROW_COLOR,
|
|
458
470
|
],
|
|
459
471
|
]
|
|
460
472
|
: [];
|
|
461
473
|
const draggable = training && !state.complete;
|
|
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:
|
|
474
|
+
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: state.incorrectMoveSquare, correctMoveSquare: state.correctMoveSquare, position: state.displayFen, boardOrientation: boardOrientation, arePiecesDraggable: draggable &&
|
|
475
|
+
!state.correctMoveSquare &&
|
|
476
|
+
!state.incorrectMoveSquare, isDraggablePiece: ({ piece }) => {
|
|
463
477
|
if (state.trainColor === 'white')
|
|
464
478
|
return piece[0] === 'w';
|
|
465
479
|
if (state.trainColor === 'black')
|
|
466
480
|
return piece[0] === 'b';
|
|
467
481
|
return piece[0] === state.sideToMove;
|
|
468
|
-
}, onPieceDrop: (source, target, piece) => state.handleDrop(source, target, piece), customArrows: customArrows, promotionDialogVariant: "modal", areArrowsAllowed: false, customBoardStyle: customBoardStyle }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4 }, children: [jsxRuntime.jsx(reactChessCore.PlyNavigation, { plyIndex: state.plyIndex, totalPly: state.totalPly, canPrev: state.canPrev, canNext: state.canNext, onGoFirst: state.goFirst, onGoPrev: state.goPrev, onGoNext: state.goNext, onGoLast: state.goLast, onGoTo: state.goTo, theme: theme, showScrubber: showPlyScrubber, renderPlyNavigation: renderPlyNavigation }), jsxRuntime.jsx("button", { type: "button", onClick: state.toggleAutoplay, disabled: !state.autoplayActive && !state.canNext, style: buttonStyle(colors, state.autoplayActive ? 'ghost' : 'primary'), "aria-label": state.autoplayActive ? 'Stop autoplay' : 'Autoplay game', children: state.autoplayActive ? 'Stop' : 'Play' })] }), jsxRuntime.jsxs("div", { style: statusLineStyle(colors), children: ["Half move ", Math.min(state.plyIndex + (state.complete ? 0 : 1), state.totalPly), " of", ' ', state.totalPly, training && !state.complete && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [` · ${TRAIN_COLOR_LABEL[state.trainColor]}`, state.trainColor === 'both'
|
|
482
|
+
}, onPieceDrop: (source, target, piece) => state.handleDrop(source, target, piece), customArrows: customArrows, lastMoveUci: state.lastMoveUci, promotionDialogVariant: "modal", areArrowsAllowed: false, customBoardStyle: customBoardStyle }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4 }, children: [jsxRuntime.jsx(reactChessCore.PlyNavigation, { plyIndex: state.plyIndex, totalPly: state.totalPly, canPrev: state.canPrev, canNext: state.canNext, onGoFirst: state.goFirst, onGoPrev: state.goPrev, onGoNext: state.goNext, onGoLast: state.goLast, onGoTo: state.goTo, theme: theme, showScrubber: showPlyScrubber, renderPlyNavigation: renderPlyNavigation }), jsxRuntime.jsx("button", { type: "button", onClick: state.toggleAutoplay, disabled: !state.autoplayActive && !state.canNext, style: buttonStyle(colors, state.autoplayActive ? 'ghost' : 'primary'), "aria-label": state.autoplayActive ? 'Stop autoplay' : 'Autoplay game', children: state.autoplayActive ? 'Stop' : 'Play' })] }), jsxRuntime.jsxs("div", { style: statusLineStyle(colors), children: ["Half move ", Math.min(state.plyIndex + (state.complete ? 0 : 1), state.totalPly), " of", ' ', state.totalPly, training && !state.complete && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [` · ${TRAIN_COLOR_LABEL[state.trainColor]}`, state.trainColor === 'both'
|
|
469
483
|
? ` · ${state.sideToMove === 'b' ? 'Black' : 'White'} to move`
|
|
470
484
|
: state.isUserTurn
|
|
471
485
|
? ' · Your move'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-chess-replay-trainer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
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
5
|
"license": "MIT",
|
|
6
6
|
"author": "Robert Blackwell",
|