react-chess-replay-trainer 0.0.1

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 ADDED
@@ -0,0 +1,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
+ ## 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
+ ```
@@ -0,0 +1,31 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type AnalysisEngineOptions, type PlyNavigationRenderProps } from 'react-chess-core';
3
+ import type { ReplayGame, ReplayMiss } from './types';
4
+ export interface ReplayTrainerProps {
5
+ gameId: string;
6
+ fetchGame: (gameId: string) => Promise<ReplayGame | null>;
7
+ /** Position to open at; browse mode starts here. Defaults to the game start. */
8
+ startFen?: string;
9
+ /** Reported once per ply when the user plays the wrong move or reveals it. */
10
+ onMiss?: (miss: ReplayMiss) => void;
11
+ /** Called when a drill reaches the end of the game. */
12
+ onComplete?: () => void;
13
+ /** Called when the user leaves the trainer. */
14
+ onExit?: () => void;
15
+ theme?: 'light' | 'dark';
16
+ boardWidth?: number;
17
+ /** Side shown at the bottom of the board. Defaults to white. */
18
+ orientation?: 'white' | 'black';
19
+ /** Stockfish options for the built-in analysis board. Set `enabled: false` to hide engine lines. */
20
+ engine?: AnalysisEngineOptions;
21
+ /** Custom ply navigation UI (e.g. MUI). Omit for the core default with scrubber. */
22
+ renderPlyNavigation?: (props: PlyNavigationRenderProps) => ReactNode;
23
+ /** Range scrubber on ply navigation. Default true. */
24
+ showPlyScrubber?: boolean;
25
+ }
26
+ /**
27
+ * Browse a game freely, then drill it from any point. Browsing (first / prev /
28
+ * next / last / slider) never records anything; once the user hits "Train from
29
+ * here" each wrong move is reported through {@link ReplayTrainerProps.onMiss}.
30
+ */
31
+ export declare const ReplayTrainer: ({ gameId, fetchGame, startFen, onMiss, onComplete, onExit, theme, boardWidth, orientation, engine, renderPlyNavigation, showPlyScrubber, }: ReplayTrainerProps) => import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { AnalysisEngineOptions } from 'react-chess-core';
3
+ import { type UseReplayAnalysisBoardArgs } from './hooks/useReplayAnalysisBoard';
4
+ import type { ReplayAnalysisContext } from './types';
5
+ export declare const DEFAULT_ANALYSIS_BOARD_WIDTH = 400;
6
+ export declare const DEFAULT_ANALYSIS_SIDEBAR_WIDTH = 280;
7
+ export declare const DEFAULT_ANALYSIS_COLUMN_GAP = 16;
8
+ export interface ReplayAnalysisBoardProps extends Omit<UseReplayAnalysisBoardArgs, 'boardWidth'> {
9
+ boardWidth?: number;
10
+ sidebarWidth?: number;
11
+ columnGap?: number;
12
+ }
13
+ /**
14
+ * Full-screen modal analysis board: draggable exploration, move list,
15
+ * variations, and Stockfish evaluation via react-chess-core.
16
+ */
17
+ export declare const ReplayAnalysisBoard: ({ boardWidth, sidebarWidth, columnGap, ...modelArgs }: ReplayAnalysisBoardProps) => React.JSX.Element;
18
+ export type { ReplayAnalysisContext, AnalysisEngineOptions };
@@ -0,0 +1,32 @@
1
+ import type { ReplayAnalysisContext, ReplayAnalysisHistoryRow, ReplaySolutionMoveDisplay } from './types';
2
+ export declare class ReplayAnalysisPosition {
3
+ private chess;
4
+ private readonly initialFen;
5
+ private readonly solutionMoves;
6
+ private readonly solutionSans;
7
+ private mainPly;
8
+ private variation;
9
+ private variationCursor;
10
+ constructor(context: ReplayAnalysisContext);
11
+ private static buildSolutionSans;
12
+ private fenAtMainPly;
13
+ private rebuildChess;
14
+ private findLegalMove;
15
+ private uciFromVerboseMove;
16
+ private matchesMainMove;
17
+ getNavPly(): number;
18
+ getMaxNavPly(): number;
19
+ getSolutionSans(): ReplaySolutionMoveDisplay[];
20
+ getHistoryRows(): ReplayAnalysisHistoryRow[];
21
+ isHistoryRowSelected(row: ReplayAnalysisHistoryRow): boolean;
22
+ selectHistoryRow(row: ReplayAnalysisHistoryRow): void;
23
+ selectMainLine(ply: number): void;
24
+ goToNavPly(navPly: number): void;
25
+ tryPlayMove(sourceSquare: string, targetSquare: string, piece: string): boolean;
26
+ getLastMoveSquares(): {
27
+ from: string;
28
+ to: string;
29
+ } | null;
30
+ fen(): string;
31
+ getCheckSquare(): string;
32
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { type EngineEvaluation } from 'react-chess-core';
3
+ export interface ReplayEngineEvaluationPanelProps {
4
+ fen: string;
5
+ evaluation: EngineEvaluation;
6
+ theme: 'light' | 'dark';
7
+ }
8
+ export declare const ReplayEngineEvaluationPanel: ({ fen, evaluation, theme, }: ReplayEngineEvaluationPanelProps) => React.JSX.Element;
@@ -0,0 +1,63 @@
1
+ export declare const analysisBoardHighlightColors: {
2
+ readonly lastMove: {
3
+ readonly light: "rgba(253, 216, 53, 0.55)";
4
+ readonly dark: "rgba(144, 202, 249, 0.5)";
5
+ };
6
+ };
7
+ export declare const getLastMoveSquareStyles: (from: string, to: string, theme: "light" | "dark") => Record<string, {
8
+ backgroundColor: string;
9
+ }>;
10
+ export declare const analysisSidebarColors: {
11
+ readonly activeMove: {
12
+ readonly light: "rgba(58, 123, 213, 0.25)";
13
+ readonly dark: "rgba(58, 123, 213, 0.35)";
14
+ };
15
+ readonly mainBand: {
16
+ readonly light: readonly ["rgba(0,0,0,0.02)", "rgba(0,0,0,0.05)"];
17
+ readonly dark: readonly ["rgba(255,255,255,0.03)", "rgba(255,255,255,0.06)"];
18
+ };
19
+ readonly variationBand: {
20
+ readonly light: readonly ["rgba(0,0,0,0.04)", "rgba(0,0,0,0.07)"];
21
+ readonly dark: readonly ["rgba(255,255,255,0.05)", "rgba(255,255,255,0.08)"];
22
+ };
23
+ };
24
+ export declare function getSidebarRowBackground(theme: 'light' | 'dark', row: {
25
+ kind: 'start' | 'main' | 'variation';
26
+ }, bandCounters: {
27
+ main: number;
28
+ variation: number;
29
+ }): string;
30
+ export declare function createSidebarRowBandCounters(): {
31
+ main: number;
32
+ variation: number;
33
+ };
34
+ export declare const analysisModalColors: {
35
+ readonly light: {
36
+ readonly panel: {
37
+ readonly backgroundColor: "#fafafa";
38
+ readonly color: "#1a1a1a";
39
+ };
40
+ readonly title: {
41
+ readonly color: "#1a1a1a";
42
+ };
43
+ readonly closeButton: {
44
+ readonly backgroundColor: "#eee";
45
+ readonly border: "1px solid #ccc";
46
+ readonly color: "#333";
47
+ };
48
+ };
49
+ readonly dark: {
50
+ readonly panel: {
51
+ readonly backgroundColor: "#2a2a2a";
52
+ readonly color: "#e8e8e8";
53
+ };
54
+ readonly title: {
55
+ readonly color: "#e8e8e8";
56
+ };
57
+ readonly closeButton: {
58
+ readonly backgroundColor: "#3a3a3a";
59
+ readonly border: "1px solid #555";
60
+ readonly color: "#e8e8e8";
61
+ };
62
+ };
63
+ };
@@ -0,0 +1,4 @@
1
+ import type { ReplayGame } from '../types';
2
+ import type { ReplayAnalysisContext } from './types';
3
+ /** Build analysis context from the current replay browse position. */
4
+ export declare function buildReplayAnalysisContext(game: ReplayGame, plyIndex: number, boardOrientation: 'white' | 'black'): ReplayAnalysisContext;
@@ -0,0 +1,34 @@
1
+ import { AnalysisEngineOptions, EngineEvaluation } from 'react-chess-core';
2
+ import type { ReplayAnalysisContext, ReplayAnalysisHistoryRow, ReplaySolutionMoveDisplay } from '../types';
3
+ export type UseReplayAnalysisBoardArgs = {
4
+ analysisContext: ReplayAnalysisContext;
5
+ onClose: () => void;
6
+ theme: 'light' | 'dark';
7
+ boardWidth: number;
8
+ engine?: AnalysisEngineOptions;
9
+ };
10
+ export type ReplayAnalysisBoardModel = {
11
+ theme: 'light' | 'dark';
12
+ boardWidth: number;
13
+ analysisContext: ReplayAnalysisContext;
14
+ fen: string;
15
+ ply: number;
16
+ maxPly: number;
17
+ historyRows: ReplayAnalysisHistoryRow[];
18
+ solutionSans: ReplaySolutionMoveDisplay[];
19
+ boardOrientation: 'white' | 'black';
20
+ engineEvaluation: EngineEvaluation;
21
+ engineEnabled: boolean;
22
+ lastMove: {
23
+ from: string;
24
+ to: string;
25
+ } | null;
26
+ checkSquare: string | null;
27
+ onSelectPly: (ply: number) => void;
28
+ onSelectHistoryRow: (row: ReplayAnalysisHistoryRow) => void;
29
+ isHistoryRowSelected: (row: ReplayAnalysisHistoryRow) => boolean;
30
+ onPieceDrop: (sourceSquare: string, targetSquare: string, piece: string) => boolean;
31
+ onBackdropMouseDown: () => void;
32
+ onClose: () => void;
33
+ };
34
+ export declare function useReplayAnalysisBoard({ analysisContext, onClose, theme, boardWidth, engine, }: UseReplayAnalysisBoardArgs): ReplayAnalysisBoardModel;
@@ -0,0 +1,6 @@
1
+ export { ReplayAnalysisBoard, DEFAULT_ANALYSIS_BOARD_WIDTH, DEFAULT_ANALYSIS_SIDEBAR_WIDTH, DEFAULT_ANALYSIS_COLUMN_GAP, type ReplayAnalysisBoardProps, } from './ReplayAnalysisBoard';
2
+ export { ReplayEngineEvaluationPanel } from './ReplayEngineEvaluationPanel';
3
+ export { buildReplayAnalysisContext } from './buildReplayAnalysisContext';
4
+ export { ReplayAnalysisPosition } from './ReplayAnalysisPosition';
5
+ export { useReplayAnalysisBoard, type UseReplayAnalysisBoardArgs, type ReplayAnalysisBoardModel, } from './hooks/useReplayAnalysisBoard';
6
+ export type { ReplayAnalysisContext, ReplayAnalysisHistoryRow, ReplaySolutionMoveDisplay, } from './types';
@@ -0,0 +1,3 @@
1
+ import { Chess } from 'chess.js';
2
+ export declare function tryApplyUci(chess: Chess, uci: string): boolean;
3
+ export declare function getCheckSquareFromChess(chess: Chess): string;
@@ -0,0 +1,22 @@
1
+ /** Context for opening the replay analysis board at a point in the game. */
2
+ export type ReplayAnalysisContext = {
3
+ initialFen: string;
4
+ /** Full game main line in UCI notation. */
5
+ solutionMoves: string[];
6
+ /** Ply index to open at (0 = start position). */
7
+ startPly: number;
8
+ boardOrientation: 'white' | 'black';
9
+ };
10
+ export type ReplayAnalysisHistoryRow = {
11
+ key: string;
12
+ label: string;
13
+ indent: number;
14
+ kind: 'start' | 'main' | 'variation';
15
+ mainPly: number;
16
+ variationIndex: number;
17
+ };
18
+ export type ReplaySolutionMoveDisplay = {
19
+ ply: number;
20
+ uci: string;
21
+ san: string;
22
+ };
@@ -0,0 +1,4 @@
1
+ import type { AnalysisContext } from 'react-chess-core';
2
+ import type { ReplayGame } from './types';
3
+ /** Build a core {@link AnalysisContext} for the current replay browse position. */
4
+ export declare function buildReplayAnalysisContext(game: ReplayGame, plyIndex: number, boardOrientation: 'white' | 'black'): AnalysisContext;
@@ -0,0 +1,3 @@
1
+ /** Standard chess starting position; game move lists replay from here. */
2
+ export declare const REPLAY_START_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
3
+ export declare const DEFAULT_BOARD_WIDTH = 480;
@@ -0,0 +1,43 @@
1
+ import type { ReplayFeedback, ReplayGame, ReplayMiss, ReplayMode, ReplaySide, TrainColor } from '../types';
2
+ export interface UseReplayTrainerOptions {
3
+ gameId: string;
4
+ /** Position to open at (browse mode starts here). Defaults to the start. */
5
+ startFen?: string;
6
+ fetchGame: (gameId: string) => Promise<ReplayGame | null>;
7
+ onMiss?: (miss: ReplayMiss) => void;
8
+ onComplete?: () => void;
9
+ }
10
+ export interface ReplayTrainerState {
11
+ game: ReplayGame | null;
12
+ loading: boolean;
13
+ error: string | null;
14
+ mode: ReplayMode;
15
+ /** Current FEN shown on the board. */
16
+ fen: string;
17
+ /** 0-based ply index = number of moves played from the start. */
18
+ plyIndex: number;
19
+ /** Total half-moves (plies) in the loaded game. */
20
+ totalPly: number;
21
+ complete: boolean;
22
+ sideToMove: ReplaySide;
23
+ /** Which side(s) the user is drilling. */
24
+ trainColor: TrainColor;
25
+ /** True when the side to move is the user's (i.e. they should guess now). */
26
+ isUserTurn: boolean;
27
+ feedback: ReplayFeedback;
28
+ /** Revealed/expected move at the current ply (set after a miss or reveal). */
29
+ expectedSan: string | null;
30
+ expectedUci: string | null;
31
+ canPrev: boolean;
32
+ canNext: boolean;
33
+ goFirst: () => void;
34
+ goPrev: () => void;
35
+ goNext: () => void;
36
+ goLast: () => void;
37
+ goTo: (ply: number) => void;
38
+ startTraining: (color?: TrainColor) => void;
39
+ stopTraining: () => void;
40
+ revealMove: () => void;
41
+ handleDrop: (source: string, target: string, piece: string) => boolean;
42
+ }
43
+ export declare function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, }: UseReplayTrainerOptions): ReplayTrainerState;
@@ -0,0 +1,7 @@
1
+ export { ReplayTrainer, type ReplayTrainerProps } from './ReplayTrainer';
2
+ export { useReplayTrainer, type UseReplayTrainerOptions, type ReplayTrainerState, } from './hooks/useReplayTrainer';
3
+ export type { ReplayGame, ReplayMiss, ReplayMode, ReplayFeedback, ReplaySide, TrainColor, } from './types';
4
+ export { REPLAY_START_FEN, DEFAULT_BOARD_WIDTH } from './constants';
5
+ export { fenAtPly, findPlyIndexForFen, sideToMove, uciFromDrop, normalizeFen, } from './replayUtils';
6
+ export { buildReplayAnalysisContext } from './buildReplayAnalysisContext';
7
+ export { AnalysisBoard, AnalysisBoardCore, AnalysisErrorBoundary, DEFAULT_ANALYSIS_LAYOUT, DefaultPlyNavigation, PlyNavigation, defaultRenderPlyNavigation, type AnalysisContext, type AnalysisBoardProps, type AnalysisLayoutConfig, type PlyNavigationModel, type PlyNavigationProps, type PlyNavigationRenderProps, } from 'react-chess-core';
@@ -0,0 +1,28 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { TrainColor } from './types';
3
+ export type ReplayTheme = 'light' | 'dark';
4
+ export type ReplayPalette = {
5
+ text: string;
6
+ subtle: string;
7
+ border: string;
8
+ surface: string;
9
+ primary: string;
10
+ success: string;
11
+ error: string;
12
+ };
13
+ export type ButtonVariant = 'primary' | 'ghost' | 'nav';
14
+ export declare const TRAIN_COLOR_LABEL: Record<TrainColor, string>;
15
+ export declare const columnStyle: CSSProperties;
16
+ export declare const centerStyle: CSSProperties;
17
+ export declare const headerStyle: CSSProperties;
18
+ export declare const controlsRowStyle: CSSProperties;
19
+ export declare const playerNameStyle: CSSProperties;
20
+ export declare const feedbackContainerStyle: CSSProperties;
21
+ export declare const customBoardStyle: CSSProperties;
22
+ export declare function palette(theme: ReplayTheme): ReplayPalette;
23
+ export declare function mainContainerStyle(boardWidth: number, colors: ReplayPalette): CSSProperties;
24
+ export declare function centerContainerStyle(boardWidth: number, color: string): CSSProperties;
25
+ export declare function subtleTextStyle(colors: ReplayPalette): CSSProperties;
26
+ export declare function statusLineStyle(colors: ReplayPalette): CSSProperties;
27
+ export declare function feedbackMessageStyle(colors: ReplayPalette, tone: 'success' | 'error'): CSSProperties;
28
+ export declare function buttonStyle(colors: ReplayPalette, variant: ButtonVariant): CSSProperties;
@@ -0,0 +1,12 @@
1
+ import { Chess } from 'chess.js';
2
+ import type { ReplaySide } from './types';
3
+ /** Compare positions ignoring move clocks (first four FEN fields). */
4
+ export declare function normalizeFen(fen: string): string;
5
+ export declare function applyUci(chess: Chess, uci: string): void;
6
+ /** FEN after applying the first `ply` moves from the standard start. */
7
+ export declare function fenAtPly(movesUci: string[], ply: number): string;
8
+ /** Index of the next move to play to reach `targetFen`, or 0 if not found. */
9
+ export declare function findPlyIndexForFen(movesUci: string[], targetFen: string): number;
10
+ export declare function sideToMove(fen: string): ReplaySide;
11
+ /** Resolve a board drag into a legal UCI string, or null when illegal. */
12
+ export declare function uciFromDrop(fen: string, sourceSquare: string, targetSquare: string, piece: string): string | null;
@@ -0,0 +1,36 @@
1
+ export type ReplaySide = 'w' | 'b';
2
+ /** A game to replay/drill. Move lists are parallel (UCI + optional SAN). */
3
+ export type ReplayGame = {
4
+ gameId?: string;
5
+ white?: string;
6
+ black?: string;
7
+ whiteElo?: number;
8
+ blackElo?: number;
9
+ result?: string;
10
+ timeControl?: string;
11
+ timeClass?: string;
12
+ movesUci: string[];
13
+ movesSan?: string[];
14
+ };
15
+ /** Reported when the user plays the wrong move (or reveals) during a drill. */
16
+ export type ReplayMiss = {
17
+ /** 0-based ply index into {@link ReplayGame.movesUci}. */
18
+ index: number;
19
+ /** FEN of the missed position (the trainer is to move here). */
20
+ fen: string;
21
+ /** Correct move at this position. */
22
+ expectedUci: string;
23
+ expectedSan: string;
24
+ /** Side to move at {@link fen}. */
25
+ sideToMove: ReplaySide;
26
+ /** Opponent's prior ply that produced {@link fen}, when one exists. */
27
+ setupFen?: string;
28
+ setupUci?: string;
29
+ };
30
+ export type ReplayMode = 'browse' | 'train';
31
+ export type ReplayFeedback = 'correct' | 'incorrect' | null;
32
+ /**
33
+ * Which side the user drills. With a single color the opponent's replies are
34
+ * played automatically after each correct guess; `'both'` drills every ply.
35
+ */
36
+ export type TrainColor = 'white' | 'black' | 'both';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Replay a chess game move-by-move and drill it, reporting misses.
3
+ * Depends on react-chess-core only (not the puzzle kit or explorer).
4
+ */
5
+ export * from './features/replay';