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.
@@ -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 { useCorrectMoveFeedback, createExpectedMoveDropHandler, fenAfterUci, ThemeProvider, ChessboardDnDProvider, HighlightChessboard, PlyNavigation, AnalysisErrorBoundary, AnalysisBoard } from 'react-chess-core';
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('both');
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, clearCorrectMoveFeedback, isShowingCorrectMove, } = useCorrectMoveFeedback();
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('both');
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('both');
105
- trainColorRef.current = 'both';
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
- clearCorrectMoveFeedback();
150
- }, [clearCorrectMoveFeedback]);
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 = 'both') => {
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) => createExpectedMoveDropHandler({
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(uci.slice(2, 4), () => {
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
- colors.primary,
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: null, correctMoveSquare: state.correctMoveSquare, position: state.displayFen, boardOrientation: boardOrientation, arePiecesDraggable: draggable && !state.correctMoveSquare, isDraggablePiece: ({ piece }) => {
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('both');
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, clearCorrectMoveFeedback, isShowingCorrectMove, } = reactChessCore.useCorrectMoveFeedback();
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('both');
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('both');
106
- trainColorRef.current = 'both';
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
- clearCorrectMoveFeedback();
151
- }, [clearCorrectMoveFeedback]);
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 = 'both') => {
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) => reactChessCore.createExpectedMoveDropHandler({
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(uci.slice(2, 4), () => {
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
- colors.primary,
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: null, correctMoveSquare: state.correctMoveSquare, position: state.displayFen, boardOrientation: boardOrientation, arePiecesDraggable: draggable && !state.correctMoveSquare, isDraggablePiece: ({ piece }) => {
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.4",
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",