react-chess-replay-trainer 0.0.5 → 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,6 +30,8 @@ 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;
35
37
  /** UCI of the move that produced the current board position. */
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, lastMoveUciAtPly, 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);
@@ -153,8 +155,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
153
155
  setExpectedSan(null);
154
156
  setExpectedUci(null);
155
157
  setFeedbackFen(null);
156
- clearCorrectMoveFeedback();
157
- }, [clearCorrectMoveFeedback]);
158
+ clearMoveFeedback();
159
+ }, [clearMoveFeedback]);
158
160
  const stopAutoplay = useCallback(() => {
159
161
  setAutoplayActive(false);
160
162
  }, []);
@@ -184,7 +186,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
184
186
  }
185
187
  startAutoplay();
186
188
  }, [autoplayActive, startAutoplay, stopAutoplay]);
187
- const startTraining = useCallback((color = 'both') => {
189
+ const startTraining = useCallback((color = 'white') => {
188
190
  setAutoplayActive(false);
189
191
  setTrainColor(color);
190
192
  trainColorRef.current = color;
@@ -229,14 +231,15 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
229
231
  setFeedback('incorrect');
230
232
  recordMiss(plyIndex);
231
233
  }, [complete, game, movesUci, plyIndex, recordMiss, isUserTurn]);
232
- const handleDrop = useCallback((source, target, piece) => createExpectedMoveDropHandler({
234
+ const handleDrop = useCallback((source, target, piece) => createDropHandler({
233
235
  fen,
234
236
  expectedUci: movesUci[plyIndex],
235
237
  enabled: modeRef.current === 'train' &&
236
238
  !complete &&
237
239
  !showingCorrectMoveRef.current &&
240
+ !showingIncorrectMoveRef.current &&
238
241
  isTrainSideToMove(trainColorRef.current, sideToMove$1),
239
- onCorrect: (uci) => {
242
+ onCorrect: ({ uci, targetSquare }) => {
240
243
  setFeedback('correct');
241
244
  setExpectedSan(null);
242
245
  setExpectedUci(null);
@@ -244,7 +247,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
244
247
  if (nextFen) {
245
248
  setFeedbackFen(nextFen);
246
249
  }
247
- showCorrectMove(uci.slice(2, 4), () => {
250
+ showCorrectMove(targetSquare, () => {
248
251
  setFeedbackFen(null);
249
252
  setFeedback(null);
250
253
  setPlyIndex((p) => p + 1);
@@ -261,7 +264,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
261
264
  setExpectedUci(expectedUci);
262
265
  recordMiss(plyIndex);
263
266
  },
264
- })(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]);
265
268
  useEffect(() => {
266
269
  var _a;
267
270
  if (mode === 'train' && complete && !completedFiredRef.current) {
@@ -290,7 +293,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
290
293
  if (mode !== 'train' || complete || trainColor === 'both' || isUserTurn) {
291
294
  return;
292
295
  }
293
- if (isShowingCorrectMove) {
296
+ if (isShowingCorrectMove || isShowingIncorrectMove) {
294
297
  return;
295
298
  }
296
299
  const id = setTimeout(() => {
@@ -298,7 +301,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
298
301
  clearTransient();
299
302
  }, OPPONENT_MOVE_DELAY_MS);
300
303
  return () => clearTimeout(id);
301
- }, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove]);
304
+ }, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove, isShowingIncorrectMove]);
302
305
  return {
303
306
  game,
304
307
  loading,
@@ -315,6 +318,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
315
318
  expectedSan,
316
319
  expectedUci,
317
320
  correctMoveSquare,
321
+ incorrectMoveSquare,
318
322
  displayFen,
319
323
  lastMoveUci,
320
324
  canPrev: plyIndex > 0,
@@ -461,12 +465,14 @@ const ReplayTrainer = ({ gameId, fetchGame, startFen, onMiss, onComplete, onExit
461
465
  [
462
466
  state.expectedUci.slice(0, 2),
463
467
  state.expectedUci.slice(2, 4),
464
- colors.primary,
468
+ DEFAULT_ANSWER_ARROW_COLOR,
465
469
  ],
466
470
  ]
467
471
  : [];
468
472
  const draggable = training && !state.complete;
469
- 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 }) => {
470
476
  if (state.trainColor === 'white')
471
477
  return piece[0] === 'w';
472
478
  if (state.trainColor === 'black')
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);
@@ -154,8 +156,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
154
156
  setExpectedSan(null);
155
157
  setExpectedUci(null);
156
158
  setFeedbackFen(null);
157
- clearCorrectMoveFeedback();
158
- }, [clearCorrectMoveFeedback]);
159
+ clearMoveFeedback();
160
+ }, [clearMoveFeedback]);
159
161
  const stopAutoplay = react.useCallback(() => {
160
162
  setAutoplayActive(false);
161
163
  }, []);
@@ -185,7 +187,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
185
187
  }
186
188
  startAutoplay();
187
189
  }, [autoplayActive, startAutoplay, stopAutoplay]);
188
- const startTraining = react.useCallback((color = 'both') => {
190
+ const startTraining = react.useCallback((color = 'white') => {
189
191
  setAutoplayActive(false);
190
192
  setTrainColor(color);
191
193
  trainColorRef.current = color;
@@ -230,14 +232,15 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
230
232
  setFeedback('incorrect');
231
233
  recordMiss(plyIndex);
232
234
  }, [complete, game, movesUci, plyIndex, recordMiss, isUserTurn]);
233
- const handleDrop = react.useCallback((source, target, piece) => reactChessCore.createExpectedMoveDropHandler({
235
+ const handleDrop = react.useCallback((source, target, piece) => createDropHandler({
234
236
  fen,
235
237
  expectedUci: movesUci[plyIndex],
236
238
  enabled: modeRef.current === 'train' &&
237
239
  !complete &&
238
240
  !showingCorrectMoveRef.current &&
241
+ !showingIncorrectMoveRef.current &&
239
242
  isTrainSideToMove(trainColorRef.current, sideToMove$1),
240
- onCorrect: (uci) => {
243
+ onCorrect: ({ uci, targetSquare }) => {
241
244
  setFeedback('correct');
242
245
  setExpectedSan(null);
243
246
  setExpectedUci(null);
@@ -245,7 +248,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
245
248
  if (nextFen) {
246
249
  setFeedbackFen(nextFen);
247
250
  }
248
- showCorrectMove(uci.slice(2, 4), () => {
251
+ showCorrectMove(targetSquare, () => {
249
252
  setFeedbackFen(null);
250
253
  setFeedback(null);
251
254
  setPlyIndex((p) => p + 1);
@@ -262,7 +265,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
262
265
  setExpectedUci(expectedUci);
263
266
  recordMiss(plyIndex);
264
267
  },
265
- })(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]);
266
269
  react.useEffect(() => {
267
270
  var _a;
268
271
  if (mode === 'train' && complete && !completedFiredRef.current) {
@@ -291,7 +294,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
291
294
  if (mode !== 'train' || complete || trainColor === 'both' || isUserTurn) {
292
295
  return;
293
296
  }
294
- if (isShowingCorrectMove) {
297
+ if (isShowingCorrectMove || isShowingIncorrectMove) {
295
298
  return;
296
299
  }
297
300
  const id = setTimeout(() => {
@@ -299,7 +302,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
299
302
  clearTransient();
300
303
  }, OPPONENT_MOVE_DELAY_MS);
301
304
  return () => clearTimeout(id);
302
- }, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove]);
305
+ }, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove, isShowingIncorrectMove]);
303
306
  return {
304
307
  game,
305
308
  loading,
@@ -316,6 +319,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
316
319
  expectedSan,
317
320
  expectedUci,
318
321
  correctMoveSquare,
322
+ incorrectMoveSquare,
319
323
  displayFen,
320
324
  lastMoveUci,
321
325
  canPrev: plyIndex > 0,
@@ -462,12 +466,14 @@ const ReplayTrainer = ({ gameId, fetchGame, startFen, onMiss, onComplete, onExit
462
466
  [
463
467
  state.expectedUci.slice(0, 2),
464
468
  state.expectedUci.slice(2, 4),
465
- colors.primary,
469
+ reactChessCore.DEFAULT_ANSWER_ARROW_COLOR,
466
470
  ],
467
471
  ]
468
472
  : [];
469
473
  const draggable = training && !state.complete;
470
- 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 }) => {
471
477
  if (state.trainColor === 'white')
472
478
  return piece[0] === 'w';
473
479
  if (state.trainColor === 'black')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-chess-replay-trainer",
3
- "version": "0.0.5",
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",