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 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
- ## Usage
21
-
22
- ```tsx
23
- import { ReplayTrainer } from 'react-chess-replay-trainer';
24
-
25
- <ReplayTrainer
26
- gameId={gameId}
27
- startFen={fenWhereUserWasBrowsing}
28
- fetchGame={fetchGame}
29
- onMiss={(miss) => enrollMissedPosition(miss)}
30
- onExit={() => setTraining(null)}
31
- theme="dark"
32
- engine={{ depth: 18, multiPv: 3 }}
33
- />;
34
- ```
35
-
36
- For a custom shell, use `useReplayTrainer` with `AnalysisBoard` from the same package:
37
-
38
- ```tsx
39
- import {
40
- useReplayTrainer,
41
- buildReplayAnalysisContext,
42
- AnalysisBoard,
43
- } from 'react-chess-replay-trainer';
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
- setPlyIndex((p) => p + 1);
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, fen, game, recordMiss, sideToMove$1]);
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.fen, boardOrientation: boardOrientation, arePiecesDraggable: draggable, isDraggablePiece: ({ piece }) => {
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
- setPlyIndex((p) => p + 1);
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, fen, game, recordMiss, sideToMove$1]);
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.fen, boardOrientation: boardOrientation, arePiecesDraggable: draggable, isDraggablePiece: ({ piece }) => {
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.2",
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'>;