react-chess-puzzle-kit 1.0.1 → 1.0.3
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/LICENSE +21 -21
- package/README.md +331 -331
- package/dist/features/board/LineBoard.d.ts +2 -1
- package/dist/features/board/PuzzleBoardWithControls.d.ts +7 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +83 -34
- package/dist/index.js +82 -33
- package/package.json +87 -87
|
@@ -4,6 +4,7 @@ export interface LineBoardProps {
|
|
|
4
4
|
orientation: 'white' | 'black';
|
|
5
5
|
trainSide: LineTrainSide;
|
|
6
6
|
draggable: boolean;
|
|
7
|
+
correctMoveSquare?: string | null;
|
|
7
8
|
onPieceDrop: (source: string, target: string, piece: string) => boolean;
|
|
8
9
|
boardWidth: number;
|
|
9
10
|
}
|
|
@@ -12,4 +13,4 @@ export interface LineBoardProps {
|
|
|
12
13
|
* side's pieces be dragged. Move validation and sequencing live in
|
|
13
14
|
* {@link LineBoardWithControls}.
|
|
14
15
|
*/
|
|
15
|
-
export declare const LineBoard: ({ fen, orientation, trainSide, draggable, onPieceDrop, boardWidth, }: LineBoardProps) => import("react").JSX.Element;
|
|
16
|
+
export declare const LineBoard: ({ fen, orientation, trainSide, draggable, correctMoveSquare, onPieceDrop, boardWidth, }: LineBoardProps) => import("react").JSX.Element;
|
|
@@ -29,6 +29,8 @@ export type BoardCaptionRenderProps = {
|
|
|
29
29
|
answerArrowVisible?: boolean;
|
|
30
30
|
/** True when the card finished after a wrong move, hint, or solution reveal. */
|
|
31
31
|
completedAfterMiss?: boolean;
|
|
32
|
+
/** True when the user requested a hint on the current card. */
|
|
33
|
+
hintUsed?: boolean;
|
|
32
34
|
};
|
|
33
35
|
export type BoardFeedbackRenderProps = {
|
|
34
36
|
resultStatus: Extract<PuzzleResultStatus, 'complete' | 'incorrect'>;
|
|
@@ -42,6 +44,8 @@ export type BoardFeedbackRenderProps = {
|
|
|
42
44
|
answerArrowVisible?: boolean;
|
|
43
45
|
/** True when the card finished after a wrong move, hint, or solution reveal. */
|
|
44
46
|
completedAfterMiss?: boolean;
|
|
47
|
+
/** True when the user requested a hint on the current card. */
|
|
48
|
+
hintUsed?: boolean;
|
|
45
49
|
};
|
|
46
50
|
export type PuzzleFetchResult = {
|
|
47
51
|
fen: string;
|
|
@@ -84,6 +88,8 @@ export interface PuzzleBoardWithControlsProps {
|
|
|
84
88
|
puzzleBoardWidth?: number;
|
|
85
89
|
/** Board + sidebar grid sizes when analysis is open. */
|
|
86
90
|
analysisLayout?: AnalysisLayoutConfig;
|
|
91
|
+
/** Chessboard pixel width in analysis (defaults to {@link analysisLayout}.boardWidth). */
|
|
92
|
+
analysisBoardWidth?: number;
|
|
87
93
|
/** Custom board/sidebar placement (overrides {@link analysisLayout} grid). */
|
|
88
94
|
renderAnalysisMain?: (props: AnalysisMainRenderProps) => React.ReactNode;
|
|
89
95
|
engine?: AnalysisEngineOptions;
|
|
@@ -105,4 +111,4 @@ export interface PuzzleBoardWithControlsProps {
|
|
|
105
111
|
refutationEngine?: AnalysisEngineOptions;
|
|
106
112
|
answerArrowColor?: string;
|
|
107
113
|
}
|
|
108
|
-
export declare const PuzzleBoardWithControls: ({ theme, boardTheme, apiProxy, renderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth, analysisLayout, renderAnalysisMain, engine, autoAdvanceOnComplete, autoAdvanceOnCompleteAfterIncorrect, revealAnswerOnIncorrect, showAnswerArrowOnIncorrect, allowRetryOnIncorrect, showRefutationOnIncorrect, autoShowWrongMoves, refutationEngine, answerArrowColor, }: PuzzleBoardWithControlsProps) => React.JSX.Element;
|
|
114
|
+
export declare const PuzzleBoardWithControls: ({ theme, boardTheme, apiProxy, renderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth, analysisLayout, analysisBoardWidth, renderAnalysisMain, engine, autoAdvanceOnComplete, autoAdvanceOnCompleteAfterIncorrect, revealAnswerOnIncorrect, showAnswerArrowOnIncorrect, allowRetryOnIncorrect, showRefutationOnIncorrect, autoShowWrongMoves, refutationEngine, answerArrowColor, }: PuzzleBoardWithControlsProps) => React.JSX.Element;
|
package/dist/index.d.ts
CHANGED
|
@@ -23,4 +23,7 @@ export declare const squareHighlightColors: {
|
|
|
23
23
|
readonly check: "rgba(255, 127, 127, 0.8)";
|
|
24
24
|
readonly hint: "rgba(119, 177, 212, 0.75)";
|
|
25
25
|
readonly incorrect: "rgba(140, 38, 38, 0.82)";
|
|
26
|
+
readonly selected: "rgba(255, 255, 0, 0.45)";
|
|
27
|
+
readonly moveTarget: "radial-gradient(circle, rgba(0, 0, 0, 0.18) 22%, transparent 22%)";
|
|
28
|
+
readonly captureTarget: "radial-gradient(circle, rgba(0, 0, 0, 0.18) 72%, transparent 72%)";
|
|
26
29
|
};
|
package/dist/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
|
2
2
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
-
import { useBoardRevision, useMissBoard, ChessboardDnDProvider, HighlightChessboard, uciFromDrop, ThemeProvider, AnalysisErrorBoundary, AnalysisBoardCore, AnalysisBoardLayout, AnalysisBoard, DEFAULT_ANALYSIS_LAYOUT, evaluateExpectedMoveDrop, boardSquareHighlightColors, analysisBoardHighlightColors } from 'react-chess-core';
|
|
3
|
+
import { useBoardRevision, useCorrectMoveFeedback, useMissBoard, ChessboardDnDProvider, HighlightChessboard, uciFromDrop, ThemeProvider, AnalysisErrorBoundary, AnalysisBoardCore, AnalysisBoardLayout, AnalysisBoard, DEFAULT_ANALYSIS_LAYOUT, evaluateExpectedMoveDrop, fenAfterUci, boardSquareHighlightColors, analysisBoardHighlightColors } from 'react-chess-core';
|
|
4
4
|
export { DEFAULT_ANALYSIS_LAYOUT } from 'react-chess-core';
|
|
5
5
|
import { Chess } from 'chess.js';
|
|
6
6
|
|
|
@@ -65,7 +65,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
65
65
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
66
66
|
const [showAnswerArrow, setShowAnswerArrow] = useState(false);
|
|
67
67
|
const [incorrectActive, setIncorrectActive] = useState(false);
|
|
68
|
+
const attemptMissedRef = useRef(false);
|
|
68
69
|
const { revision, bumpRevision } = useBoardRevision();
|
|
70
|
+
const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, } = useCorrectMoveFeedback();
|
|
69
71
|
const boardOrientationRef = useRef('white');
|
|
70
72
|
const boardFenRef = useRef(EMPTY_BOARD_FEN);
|
|
71
73
|
const notifyHost = () => {
|
|
@@ -91,7 +93,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
91
93
|
expectedUci: expectedUci || null,
|
|
92
94
|
positionFen,
|
|
93
95
|
answerArrowColor,
|
|
94
|
-
|
|
96
|
+
// Refutation + answer-arrow flows must run the full wrong→refutation→answer
|
|
97
|
+
// sequence; the replay "retry without arrow" setting does not apply here.
|
|
98
|
+
autoShowWrongMoves: useRefutation ? true : autoShowWrongMoves,
|
|
95
99
|
engineOptions: refutationEngine,
|
|
96
100
|
});
|
|
97
101
|
const missPhase = (_c = missBoard.missSequence.sequence) === null || _c === void 0 ? void 0 : _c.phase;
|
|
@@ -101,8 +105,10 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
101
105
|
useEffect(() => {
|
|
102
106
|
setShowAnswerArrow(false);
|
|
103
107
|
setIncorrectActive(false);
|
|
108
|
+
attemptMissedRef.current = false;
|
|
109
|
+
clearCorrectMoveFeedback();
|
|
104
110
|
onMissFeedbackChange === null || onMissFeedbackChange === void 0 ? void 0 : onMissFeedbackChange(null);
|
|
105
|
-
}, [onMissFeedbackChange, position]);
|
|
111
|
+
}, [clearCorrectMoveFeedback, onMissFeedbackChange, position]);
|
|
106
112
|
useEffect(() => {
|
|
107
113
|
var _a, _b;
|
|
108
114
|
if (!onMissFeedbackChange) {
|
|
@@ -160,7 +166,10 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
160
166
|
(missBoard.boardAnimating ||
|
|
161
167
|
missPhase === 'wrong' ||
|
|
162
168
|
missPhase === 'refutation');
|
|
163
|
-
const arePiecesDraggable = position !== null &&
|
|
169
|
+
const arePiecesDraggable = position !== null &&
|
|
170
|
+
!positionLocked &&
|
|
171
|
+
!missLocked &&
|
|
172
|
+
correctMoveSquare === null;
|
|
164
173
|
const onPieceDrop = (sourceSquare, targetSquare, piece) => {
|
|
165
174
|
if (!position || positionLocked || position.isSolutionRevealed()) {
|
|
166
175
|
return false;
|
|
@@ -182,6 +191,7 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
182
191
|
recordIfIncorrect: !(answerArrowVisible && !allowRetryOnIncorrect),
|
|
183
192
|
});
|
|
184
193
|
if (!guess.accepted) {
|
|
194
|
+
attemptMissedRef.current = true;
|
|
185
195
|
onFeedback({
|
|
186
196
|
index: position.getIndex(),
|
|
187
197
|
guess: { sourceSquare, targetSquare, piece },
|
|
@@ -224,38 +234,47 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
224
234
|
setIncorrectActive(false);
|
|
225
235
|
missBoard.missSequence.clearSequence();
|
|
226
236
|
onMissFeedbackChange === null || onMissFeedbackChange === void 0 ? void 0 : onMissFeedbackChange(null);
|
|
227
|
-
|
|
237
|
+
clearCorrectMoveFeedback();
|
|
238
|
+
const assistedByAnswerArrow = answerArrowVisible && attemptMissedRef.current;
|
|
239
|
+
const guessPayload = {
|
|
228
240
|
index: position.getIndex(),
|
|
229
241
|
guess: { sourceSquare, targetSquare, piece },
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return true;
|
|
242
|
+
};
|
|
243
|
+
if (assistedByAnswerArrow) {
|
|
244
|
+
// Miss feedback for this ply is already saved; dragging along the answer
|
|
245
|
+
// arrow only continues the line — it must not count as a clean solve.
|
|
246
|
+
if (guess.finished) {
|
|
247
|
+
onFeedback(Object.assign(Object.assign({}, guessPayload), { isCorrect: false, isFinished: true }));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
onFeedback(Object.assign(Object.assign({}, guessPayload), { isCorrect: true, isFinished: guess.finished }));
|
|
241
252
|
}
|
|
242
253
|
position.next();
|
|
254
|
+
boardFenRef.current = position.fen();
|
|
243
255
|
notifyHost();
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
256
|
+
const finishCorrectFeedback = () => {
|
|
257
|
+
position.resetInteractions();
|
|
258
|
+
notifyHost();
|
|
259
|
+
if (position.isAlternativeCheckmate()) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (position.hasResumeConfig()) {
|
|
263
|
+
onResumeCorrect === null || onResumeCorrect === void 0 ? void 0 : onResumeCorrect(position);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
249
266
|
if (!position.isFinished()) {
|
|
250
267
|
position.next();
|
|
268
|
+
boardFenRef.current = position.fen();
|
|
251
269
|
}
|
|
252
270
|
notifyHost();
|
|
253
|
-
}
|
|
271
|
+
};
|
|
272
|
+
showCorrectMove(targetSquare, finishCorrectFeedback);
|
|
254
273
|
return true;
|
|
255
274
|
};
|
|
256
275
|
return (jsx(ChessboardDnDProvider, { children: hasBoard ? (jsx(HighlightChessboard, { boardWidth: boardWidth, checkSquare: (_e = position === null || position === void 0 ? void 0 : position.getCheckSquare()) !== null && _e !== void 0 ? _e : '', hintSquare: (_f = position === null || position === void 0 ? void 0 : position.getHintSquare()) !== null && _f !== void 0 ? _f : null, incorrectMoveSquare: showAnswerArrowOnIncorrect
|
|
257
276
|
? null
|
|
258
|
-
: ((_g = position === null || position === void 0 ? void 0 : position.getIncorrectMoveSquare()) !== null && _g !== void 0 ? _g : null), customArrows: customArrows, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: 0 }, revision)) : null }));
|
|
277
|
+
: ((_g = position === null || position === void 0 ? void 0 : position.getIncorrectMoveSquare()) !== null && _g !== void 0 ? _g : null), correctMoveSquare: correctMoveSquare, customArrows: customArrows, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: 0 }, revision)) : null }));
|
|
259
278
|
};
|
|
260
279
|
|
|
261
280
|
const PuzzleBoard = ({ position, onFeedback, incInteractionNum, boardWidth, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, answerArrowColor, }) => (jsx(PuzzlePlaySurface, { position: position, onFeedback: onFeedback, incInteractionNum: incInteractionNum, boardWidth: boardWidth, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, answerArrowColor: answerArrowColor }));
|
|
@@ -720,7 +739,7 @@ const puzzlePositionFromFetch = (fen, moves, resume) => {
|
|
|
720
739
|
const AUTO_ADVANCE_ON_COMPLETE_DELAY_MS = 700;
|
|
721
740
|
const SOLUTION_STEP_MS = 500;
|
|
722
741
|
const RESUME_AUTO_STEP_MS = 500;
|
|
723
|
-
const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = DEFAULT_ANALYSIS_LAYOUT, renderAnalysisMain, engine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
|
|
742
|
+
const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = DEFAULT_ANALYSIS_LAYOUT, analysisBoardWidth, renderAnalysisMain, engine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
|
|
724
743
|
var _a, _b, _c, _d;
|
|
725
744
|
const refutationOnIncorrect = showRefutationOnIncorrect !== null && showRefutationOnIncorrect !== void 0 ? showRefutationOnIncorrect : showAnswerArrowOnIncorrect;
|
|
726
745
|
const stackControlsBelow = useStackPuzzleControlsBelow();
|
|
@@ -732,6 +751,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
732
751
|
const [loadingNextPuzzle, setLoadingNextPuzzle] = useState(true);
|
|
733
752
|
const [puzzleNum, setPuzzleNum] = useState(0);
|
|
734
753
|
const [hasIncorrectAttempt, setHasIncorrectAttempt] = useState(false);
|
|
754
|
+
const [hintUsed, setHintUsed] = useState(false);
|
|
735
755
|
const [puzzleComplete, setPuzzleComplete] = useState(false);
|
|
736
756
|
const [completedAfterMiss, setCompletedAfterMiss] = useState(false);
|
|
737
757
|
const [missFeedback, setMissFeedback] = useState(null);
|
|
@@ -757,6 +777,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
757
777
|
let cancelled = false;
|
|
758
778
|
setLoadingNextPuzzle(true);
|
|
759
779
|
setHasIncorrectAttempt(false);
|
|
780
|
+
setHintUsed(false);
|
|
760
781
|
setPuzzleComplete(false);
|
|
761
782
|
setCompletedAfterMiss(false);
|
|
762
783
|
setMissFeedback(null);
|
|
@@ -793,12 +814,18 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
793
814
|
const incorrectThisFeedback = feedbackData.hintRequested ||
|
|
794
815
|
feedbackData.solutionShown ||
|
|
795
816
|
feedbackData.isCorrect === false;
|
|
817
|
+
if (feedbackData.hintRequested) {
|
|
818
|
+
setHintUsed(true);
|
|
819
|
+
}
|
|
796
820
|
if (incorrectThisFeedback) {
|
|
797
821
|
setHasIncorrectAttempt(true);
|
|
798
822
|
}
|
|
799
823
|
if (feedbackData.isFinished) {
|
|
800
824
|
setPuzzleComplete(true);
|
|
801
|
-
setCompletedAfterMiss(
|
|
825
|
+
setCompletedAfterMiss((prev) => prev ||
|
|
826
|
+
hasIncorrectAttempt ||
|
|
827
|
+
incorrectThisFeedback ||
|
|
828
|
+
feedbackData.hintRequested === true);
|
|
802
829
|
}
|
|
803
830
|
onFeedback(feedbackData);
|
|
804
831
|
};
|
|
@@ -1002,10 +1029,11 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1002
1029
|
};
|
|
1003
1030
|
const analysis = usePuzzleAnalysis(position, resultStatus, puzzleNum);
|
|
1004
1031
|
const analysisSnapshot = analysis.isOpen && analysis.snapshot ? analysis.snapshot : null;
|
|
1032
|
+
const resolvedAnalysisBoardWidth = analysisBoardWidth !== null && analysisBoardWidth !== void 0 ? analysisBoardWidth : analysisLayout.boardWidth;
|
|
1005
1033
|
const useHostAnalysisUi = Boolean(renderAnalysisSidebar &&
|
|
1006
1034
|
renderAnalysisContainer &&
|
|
1007
1035
|
(renderEngineEvaluation || (engine === null || engine === void 0 ? void 0 : engine.enabled) === false));
|
|
1008
|
-
return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsx(AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsx(AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth:
|
|
1036
|
+
return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsx(AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsx(AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth: resolvedAnalysisBoardWidth, engine: engine, renderMain: renderAnalysisMain !== null && renderAnalysisMain !== void 0 ? renderAnalysisMain : (({ board, sidebar, model }) => (jsx(AnalysisBoardLayout, { layout: analysisLayout, model: model, board: board, sidebar: sidebar }))), renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation !== null && renderEngineEvaluation !== void 0 ? renderEngineEvaluation : (() => null) })) : (jsx(AnalysisBoard, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, layout: analysisLayout, engine: engine, renderMain: renderAnalysisMain, renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation })) })) : (jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxs("div", { style: puzzleBoardColumnStyle(puzzleBoardWidth, controlsPlacement), children: [jsx("div", { style: puzzleBoardSlotWrapperStyle(), children: jsx("div", { style: puzzleBoardSlotStyle(), children: jsx(PuzzlePlaySurface, { position: position, boardWidth: puzzleBoardWidth, onFeedback: handleFeedback, incInteractionNum: incInteractionNum, onResumeCorrect: runResumeAutoAdvance, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, showRefutationOnIncorrect: refutationOnIncorrect, autoShowWrongMoves: autoShowWrongMoves, refutationEngine: refutationEngine !== null && refutationEngine !== void 0 ? refutationEngine : engine, answerArrowColor: answerArrowColor, positionLocked: loadingNextPuzzle, onMissFeedbackChange: setMissFeedback }) }) }), renderBoardCaption && (jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
|
|
1009
1037
|
sideToMove: (_a = position === null || position === void 0 ? void 0 : position.getSideToMove()) !== null && _a !== void 0 ? _a : null,
|
|
1010
1038
|
playerColor: position
|
|
1011
1039
|
? position.getPlayerColor()
|
|
@@ -1017,6 +1045,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1017
1045
|
missPhase: (_c = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase) !== null && _c !== void 0 ? _c : null,
|
|
1018
1046
|
answerArrowVisible: (_d = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.answerArrowVisible) !== null && _d !== void 0 ? _d : false,
|
|
1019
1047
|
completedAfterMiss,
|
|
1048
|
+
hintUsed,
|
|
1020
1049
|
}) }))] }), jsxs("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: [renderControls(handleHintRequest, handleShowSolution, handleNextPuzzle, resultStatus, {
|
|
1021
1050
|
visible: analysis.canOpen,
|
|
1022
1051
|
openAnalysis: analysis.openAnalysis,
|
|
@@ -1025,6 +1054,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1025
1054
|
cleanSolve: !hasIncorrectAttempt,
|
|
1026
1055
|
refutationSan: missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.refutationSan,
|
|
1027
1056
|
missPhase: missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase,
|
|
1057
|
+
hintUsed,
|
|
1028
1058
|
}) }))] })] })) }));
|
|
1029
1059
|
};
|
|
1030
1060
|
|
|
@@ -1033,7 +1063,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1033
1063
|
* side's pieces be dragged. Move validation and sequencing live in
|
|
1034
1064
|
* {@link LineBoardWithControls}.
|
|
1035
1065
|
*/
|
|
1036
|
-
const LineBoard = ({ fen, orientation, trainSide, draggable, onPieceDrop, boardWidth, }) => (jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, position: fen, boardOrientation: orientation, arePiecesDraggable: draggable, isDraggablePiece: ({ piece }) => piece[0] === trainSide, onPieceDrop: (source, target, piece) => onPieceDrop(source, target, piece), autoPromoteToQueen: true, areArrowsAllowed: false, customBoardStyle: { borderRadius: 4 } }) }));
|
|
1066
|
+
const LineBoard = ({ fen, orientation, trainSide, draggable, correctMoveSquare = null, onPieceDrop, boardWidth, }) => (jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, correctMoveSquare: correctMoveSquare, position: fen, boardOrientation: orientation, arePiecesDraggable: draggable, isDraggablePiece: ({ piece }) => piece[0] === trainSide, onPieceDrop: (source, target, piece) => onPieceDrop(source, target, piece), autoPromoteToQueen: true, areArrowsAllowed: false, customBoardStyle: { borderRadius: 4 } }) }));
|
|
1037
1067
|
|
|
1038
1068
|
/** Library default line-drill status controls (unstyled). */
|
|
1039
1069
|
const DefaultLineControls = ({ moveNumber, total, finished, isUserTurn, feedback, }) => (jsxs("div", { style: rowStyle, children: [jsx("span", { style: statusStyle, children: finished ? 'Line complete' : `Move ${moveNumber} of ${total}` }), feedback && !finished && (jsx("span", { style: Object.assign(Object.assign({}, statusStyle), { color: feedback.isCorrect ? '#2e7d32' : '#c62828' }), children: feedback.isCorrect
|
|
@@ -1080,6 +1110,8 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1080
1110
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
1081
1111
|
const [finished, setFinished] = useState(false);
|
|
1082
1112
|
const [feedback, setFeedback] = useState(null);
|
|
1113
|
+
const [displayFen, setDisplayFen] = useState(null);
|
|
1114
|
+
const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, isShowingCorrectMove, } = useCorrectMoveFeedback();
|
|
1083
1115
|
const total = line.movesUci.length;
|
|
1084
1116
|
const orientation = boardOrientationForLine(line.trainSide);
|
|
1085
1117
|
const applyMove = useCallback((index) => {
|
|
@@ -1104,7 +1136,7 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1104
1136
|
}, [line.movesUci]);
|
|
1105
1137
|
// Auto-play opponent moves and detect the end of the line.
|
|
1106
1138
|
useEffect(() => {
|
|
1107
|
-
if (finished) {
|
|
1139
|
+
if (finished || isShowingCorrectMove) {
|
|
1108
1140
|
return;
|
|
1109
1141
|
}
|
|
1110
1142
|
if (currentIndex >= total) {
|
|
@@ -1123,6 +1155,7 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1123
1155
|
line.trainSide,
|
|
1124
1156
|
applyMove,
|
|
1125
1157
|
opponentMoveDelayMs,
|
|
1158
|
+
isShowingCorrectMove,
|
|
1126
1159
|
]);
|
|
1127
1160
|
// Emit the completion event exactly once.
|
|
1128
1161
|
useEffect(() => {
|
|
@@ -1134,15 +1167,16 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1134
1167
|
}, [finished]);
|
|
1135
1168
|
const handleDrop = (source, target, piece) => {
|
|
1136
1169
|
var _a, _b, _c;
|
|
1137
|
-
if (finished) {
|
|
1170
|
+
if (finished || isShowingCorrectMove) {
|
|
1138
1171
|
return false;
|
|
1139
1172
|
}
|
|
1140
|
-
|
|
1173
|
+
const setupFen = displayFen !== null && displayFen !== void 0 ? displayFen : chessRef.current.fen();
|
|
1174
|
+
if (turnFromFen(setupFen) !== line.trainSide) {
|
|
1141
1175
|
return false;
|
|
1142
1176
|
}
|
|
1143
1177
|
const index = currentIndex;
|
|
1144
1178
|
const expected = line.movesUci[index];
|
|
1145
|
-
const dropResult = evaluateExpectedMoveDrop(
|
|
1179
|
+
const dropResult = evaluateExpectedMoveDrop(setupFen, source, target, piece, expected, true);
|
|
1146
1180
|
if (dropResult.kind === 'illegal' || dropResult.kind === 'ignored') {
|
|
1147
1181
|
return false;
|
|
1148
1182
|
}
|
|
@@ -1152,16 +1186,31 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1152
1186
|
const moveFeedback = { index, isCorrect, expectedSan };
|
|
1153
1187
|
setFeedback(moveFeedback);
|
|
1154
1188
|
(_c = onMoveRef.current) === null || _c === void 0 ? void 0 : _c.call(onMoveRef, moveFeedback);
|
|
1155
|
-
|
|
1189
|
+
if (isCorrect) {
|
|
1190
|
+
const nextFen = fenAfterUci(setupFen, dropResult.uci);
|
|
1191
|
+
if (nextFen) {
|
|
1192
|
+
setDisplayFen(nextFen);
|
|
1193
|
+
}
|
|
1194
|
+
showCorrectMove(target, () => {
|
|
1195
|
+
setDisplayFen(null);
|
|
1196
|
+
setFeedback(null);
|
|
1197
|
+
clearCorrectMoveFeedback();
|
|
1198
|
+
applyMove(index);
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1156
1201
|
return isCorrect;
|
|
1157
1202
|
};
|
|
1203
|
+
const boardFen = displayFen !== null && displayFen !== void 0 ? displayFen : fen;
|
|
1158
1204
|
const moveNumber = Math.min(currentIndex + 1, total);
|
|
1159
|
-
const isUserTurn = !finished &&
|
|
1205
|
+
const isUserTurn = !finished &&
|
|
1206
|
+
!isShowingCorrectMove &&
|
|
1207
|
+
turnFromFen(boardFen) === line.trainSide &&
|
|
1208
|
+
currentIndex < total;
|
|
1160
1209
|
const stackControlsBelow = useStackPuzzleControlsBelow();
|
|
1161
1210
|
const controlsPlacement = stackControlsBelow
|
|
1162
1211
|
? 'below'
|
|
1163
1212
|
: 'beside';
|
|
1164
|
-
return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: jsx("div", { style: puzzleBoardSlotStyle(), children: jsx(LineBoard, { fen:
|
|
1213
|
+
return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: jsx("div", { style: puzzleBoardSlotStyle(), children: jsx(LineBoard, { fen: boardFen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, correctMoveSquare: correctMoveSquare, onPieceDrop: handleDrop, boardWidth: boardWidth }) }) }), jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
|
|
1165
1214
|
trainSide: line.trainSide,
|
|
1166
1215
|
moveNumber,
|
|
1167
1216
|
total,
|