react-chess-puzzle-kit 1.0.2 → 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.
|
@@ -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
|
|
|
@@ -67,6 +67,7 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
67
67
|
const [incorrectActive, setIncorrectActive] = useState(false);
|
|
68
68
|
const attemptMissedRef = useRef(false);
|
|
69
69
|
const { revision, bumpRevision } = useBoardRevision();
|
|
70
|
+
const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, } = useCorrectMoveFeedback();
|
|
70
71
|
const boardOrientationRef = useRef('white');
|
|
71
72
|
const boardFenRef = useRef(EMPTY_BOARD_FEN);
|
|
72
73
|
const notifyHost = () => {
|
|
@@ -92,7 +93,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
92
93
|
expectedUci: expectedUci || null,
|
|
93
94
|
positionFen,
|
|
94
95
|
answerArrowColor,
|
|
95
|
-
|
|
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,
|
|
96
99
|
engineOptions: refutationEngine,
|
|
97
100
|
});
|
|
98
101
|
const missPhase = (_c = missBoard.missSequence.sequence) === null || _c === void 0 ? void 0 : _c.phase;
|
|
@@ -103,8 +106,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
103
106
|
setShowAnswerArrow(false);
|
|
104
107
|
setIncorrectActive(false);
|
|
105
108
|
attemptMissedRef.current = false;
|
|
109
|
+
clearCorrectMoveFeedback();
|
|
106
110
|
onMissFeedbackChange === null || onMissFeedbackChange === void 0 ? void 0 : onMissFeedbackChange(null);
|
|
107
|
-
}, [onMissFeedbackChange, position]);
|
|
111
|
+
}, [clearCorrectMoveFeedback, onMissFeedbackChange, position]);
|
|
108
112
|
useEffect(() => {
|
|
109
113
|
var _a, _b;
|
|
110
114
|
if (!onMissFeedbackChange) {
|
|
@@ -162,7 +166,10 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
162
166
|
(missBoard.boardAnimating ||
|
|
163
167
|
missPhase === 'wrong' ||
|
|
164
168
|
missPhase === 'refutation');
|
|
165
|
-
const arePiecesDraggable = position !== null &&
|
|
169
|
+
const arePiecesDraggable = position !== null &&
|
|
170
|
+
!positionLocked &&
|
|
171
|
+
!missLocked &&
|
|
172
|
+
correctMoveSquare === null;
|
|
166
173
|
const onPieceDrop = (sourceSquare, targetSquare, piece) => {
|
|
167
174
|
if (!position || positionLocked || position.isSolutionRevealed()) {
|
|
168
175
|
return false;
|
|
@@ -227,6 +234,7 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
227
234
|
setIncorrectActive(false);
|
|
228
235
|
missBoard.missSequence.clearSequence();
|
|
229
236
|
onMissFeedbackChange === null || onMissFeedbackChange === void 0 ? void 0 : onMissFeedbackChange(null);
|
|
237
|
+
clearCorrectMoveFeedback();
|
|
230
238
|
const assistedByAnswerArrow = answerArrowVisible && attemptMissedRef.current;
|
|
231
239
|
const guessPayload = {
|
|
232
240
|
index: position.getIndex(),
|
|
@@ -242,32 +250,31 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
242
250
|
else {
|
|
243
251
|
onFeedback(Object.assign(Object.assign({}, guessPayload), { isCorrect: true, isFinished: guess.finished }));
|
|
244
252
|
}
|
|
253
|
+
position.next();
|
|
254
|
+
boardFenRef.current = position.fen();
|
|
245
255
|
notifyHost();
|
|
246
|
-
|
|
256
|
+
const finishCorrectFeedback = () => {
|
|
247
257
|
position.resetInteractions();
|
|
248
258
|
notifyHost();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (position.hasResumeConfig()) {
|
|
257
|
-
onResumeCorrect === null || onResumeCorrect === void 0 ? void 0 : onResumeCorrect(position);
|
|
258
|
-
return true;
|
|
259
|
-
}
|
|
260
|
-
setTimeout(() => {
|
|
259
|
+
if (position.isAlternativeCheckmate()) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (position.hasResumeConfig()) {
|
|
263
|
+
onResumeCorrect === null || onResumeCorrect === void 0 ? void 0 : onResumeCorrect(position);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
261
266
|
if (!position.isFinished()) {
|
|
262
267
|
position.next();
|
|
268
|
+
boardFenRef.current = position.fen();
|
|
263
269
|
}
|
|
264
270
|
notifyHost();
|
|
265
|
-
}
|
|
271
|
+
};
|
|
272
|
+
showCorrectMove(targetSquare, finishCorrectFeedback);
|
|
266
273
|
return true;
|
|
267
274
|
};
|
|
268
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
|
|
269
276
|
? null
|
|
270
|
-
: ((_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 }));
|
|
271
278
|
};
|
|
272
279
|
|
|
273
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 }));
|
|
@@ -732,7 +739,7 @@ const puzzlePositionFromFetch = (fen, moves, resume) => {
|
|
|
732
739
|
const AUTO_ADVANCE_ON_COMPLETE_DELAY_MS = 700;
|
|
733
740
|
const SOLUTION_STEP_MS = 500;
|
|
734
741
|
const RESUME_AUTO_STEP_MS = 500;
|
|
735
|
-
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, }) => {
|
|
736
743
|
var _a, _b, _c, _d;
|
|
737
744
|
const refutationOnIncorrect = showRefutationOnIncorrect !== null && showRefutationOnIncorrect !== void 0 ? showRefutationOnIncorrect : showAnswerArrowOnIncorrect;
|
|
738
745
|
const stackControlsBelow = useStackPuzzleControlsBelow();
|
|
@@ -744,6 +751,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
744
751
|
const [loadingNextPuzzle, setLoadingNextPuzzle] = useState(true);
|
|
745
752
|
const [puzzleNum, setPuzzleNum] = useState(0);
|
|
746
753
|
const [hasIncorrectAttempt, setHasIncorrectAttempt] = useState(false);
|
|
754
|
+
const [hintUsed, setHintUsed] = useState(false);
|
|
747
755
|
const [puzzleComplete, setPuzzleComplete] = useState(false);
|
|
748
756
|
const [completedAfterMiss, setCompletedAfterMiss] = useState(false);
|
|
749
757
|
const [missFeedback, setMissFeedback] = useState(null);
|
|
@@ -769,6 +777,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
769
777
|
let cancelled = false;
|
|
770
778
|
setLoadingNextPuzzle(true);
|
|
771
779
|
setHasIncorrectAttempt(false);
|
|
780
|
+
setHintUsed(false);
|
|
772
781
|
setPuzzleComplete(false);
|
|
773
782
|
setCompletedAfterMiss(false);
|
|
774
783
|
setMissFeedback(null);
|
|
@@ -805,12 +814,18 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
805
814
|
const incorrectThisFeedback = feedbackData.hintRequested ||
|
|
806
815
|
feedbackData.solutionShown ||
|
|
807
816
|
feedbackData.isCorrect === false;
|
|
817
|
+
if (feedbackData.hintRequested) {
|
|
818
|
+
setHintUsed(true);
|
|
819
|
+
}
|
|
808
820
|
if (incorrectThisFeedback) {
|
|
809
821
|
setHasIncorrectAttempt(true);
|
|
810
822
|
}
|
|
811
823
|
if (feedbackData.isFinished) {
|
|
812
824
|
setPuzzleComplete(true);
|
|
813
|
-
setCompletedAfterMiss(
|
|
825
|
+
setCompletedAfterMiss((prev) => prev ||
|
|
826
|
+
hasIncorrectAttempt ||
|
|
827
|
+
incorrectThisFeedback ||
|
|
828
|
+
feedbackData.hintRequested === true);
|
|
814
829
|
}
|
|
815
830
|
onFeedback(feedbackData);
|
|
816
831
|
};
|
|
@@ -1014,10 +1029,11 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1014
1029
|
};
|
|
1015
1030
|
const analysis = usePuzzleAnalysis(position, resultStatus, puzzleNum);
|
|
1016
1031
|
const analysisSnapshot = analysis.isOpen && analysis.snapshot ? analysis.snapshot : null;
|
|
1032
|
+
const resolvedAnalysisBoardWidth = analysisBoardWidth !== null && analysisBoardWidth !== void 0 ? analysisBoardWidth : analysisLayout.boardWidth;
|
|
1017
1033
|
const useHostAnalysisUi = Boolean(renderAnalysisSidebar &&
|
|
1018
1034
|
renderAnalysisContainer &&
|
|
1019
1035
|
(renderEngineEvaluation || (engine === null || engine === void 0 ? void 0 : engine.enabled) === false));
|
|
1020
|
-
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({
|
|
1021
1037
|
sideToMove: (_a = position === null || position === void 0 ? void 0 : position.getSideToMove()) !== null && _a !== void 0 ? _a : null,
|
|
1022
1038
|
playerColor: position
|
|
1023
1039
|
? position.getPlayerColor()
|
|
@@ -1029,6 +1045,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1029
1045
|
missPhase: (_c = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase) !== null && _c !== void 0 ? _c : null,
|
|
1030
1046
|
answerArrowVisible: (_d = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.answerArrowVisible) !== null && _d !== void 0 ? _d : false,
|
|
1031
1047
|
completedAfterMiss,
|
|
1048
|
+
hintUsed,
|
|
1032
1049
|
}) }))] }), jsxs("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: [renderControls(handleHintRequest, handleShowSolution, handleNextPuzzle, resultStatus, {
|
|
1033
1050
|
visible: analysis.canOpen,
|
|
1034
1051
|
openAnalysis: analysis.openAnalysis,
|
|
@@ -1037,6 +1054,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1037
1054
|
cleanSolve: !hasIncorrectAttempt,
|
|
1038
1055
|
refutationSan: missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.refutationSan,
|
|
1039
1056
|
missPhase: missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase,
|
|
1057
|
+
hintUsed,
|
|
1040
1058
|
}) }))] })] })) }));
|
|
1041
1059
|
};
|
|
1042
1060
|
|
|
@@ -1045,7 +1063,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1045
1063
|
* side's pieces be dragged. Move validation and sequencing live in
|
|
1046
1064
|
* {@link LineBoardWithControls}.
|
|
1047
1065
|
*/
|
|
1048
|
-
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 } }) }));
|
|
1049
1067
|
|
|
1050
1068
|
/** Library default line-drill status controls (unstyled). */
|
|
1051
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
|
|
@@ -1092,6 +1110,8 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1092
1110
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
1093
1111
|
const [finished, setFinished] = useState(false);
|
|
1094
1112
|
const [feedback, setFeedback] = useState(null);
|
|
1113
|
+
const [displayFen, setDisplayFen] = useState(null);
|
|
1114
|
+
const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, isShowingCorrectMove, } = useCorrectMoveFeedback();
|
|
1095
1115
|
const total = line.movesUci.length;
|
|
1096
1116
|
const orientation = boardOrientationForLine(line.trainSide);
|
|
1097
1117
|
const applyMove = useCallback((index) => {
|
|
@@ -1116,7 +1136,7 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1116
1136
|
}, [line.movesUci]);
|
|
1117
1137
|
// Auto-play opponent moves and detect the end of the line.
|
|
1118
1138
|
useEffect(() => {
|
|
1119
|
-
if (finished) {
|
|
1139
|
+
if (finished || isShowingCorrectMove) {
|
|
1120
1140
|
return;
|
|
1121
1141
|
}
|
|
1122
1142
|
if (currentIndex >= total) {
|
|
@@ -1135,6 +1155,7 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1135
1155
|
line.trainSide,
|
|
1136
1156
|
applyMove,
|
|
1137
1157
|
opponentMoveDelayMs,
|
|
1158
|
+
isShowingCorrectMove,
|
|
1138
1159
|
]);
|
|
1139
1160
|
// Emit the completion event exactly once.
|
|
1140
1161
|
useEffect(() => {
|
|
@@ -1146,15 +1167,16 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1146
1167
|
}, [finished]);
|
|
1147
1168
|
const handleDrop = (source, target, piece) => {
|
|
1148
1169
|
var _a, _b, _c;
|
|
1149
|
-
if (finished) {
|
|
1170
|
+
if (finished || isShowingCorrectMove) {
|
|
1150
1171
|
return false;
|
|
1151
1172
|
}
|
|
1152
|
-
|
|
1173
|
+
const setupFen = displayFen !== null && displayFen !== void 0 ? displayFen : chessRef.current.fen();
|
|
1174
|
+
if (turnFromFen(setupFen) !== line.trainSide) {
|
|
1153
1175
|
return false;
|
|
1154
1176
|
}
|
|
1155
1177
|
const index = currentIndex;
|
|
1156
1178
|
const expected = line.movesUci[index];
|
|
1157
|
-
const dropResult = evaluateExpectedMoveDrop(
|
|
1179
|
+
const dropResult = evaluateExpectedMoveDrop(setupFen, source, target, piece, expected, true);
|
|
1158
1180
|
if (dropResult.kind === 'illegal' || dropResult.kind === 'ignored') {
|
|
1159
1181
|
return false;
|
|
1160
1182
|
}
|
|
@@ -1164,16 +1186,31 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1164
1186
|
const moveFeedback = { index, isCorrect, expectedSan };
|
|
1165
1187
|
setFeedback(moveFeedback);
|
|
1166
1188
|
(_c = onMoveRef.current) === null || _c === void 0 ? void 0 : _c.call(onMoveRef, moveFeedback);
|
|
1167
|
-
|
|
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
|
+
}
|
|
1168
1201
|
return isCorrect;
|
|
1169
1202
|
};
|
|
1203
|
+
const boardFen = displayFen !== null && displayFen !== void 0 ? displayFen : fen;
|
|
1170
1204
|
const moveNumber = Math.min(currentIndex + 1, total);
|
|
1171
|
-
const isUserTurn = !finished &&
|
|
1205
|
+
const isUserTurn = !finished &&
|
|
1206
|
+
!isShowingCorrectMove &&
|
|
1207
|
+
turnFromFen(boardFen) === line.trainSide &&
|
|
1208
|
+
currentIndex < total;
|
|
1172
1209
|
const stackControlsBelow = useStackPuzzleControlsBelow();
|
|
1173
1210
|
const controlsPlacement = stackControlsBelow
|
|
1174
1211
|
? 'below'
|
|
1175
1212
|
: 'beside';
|
|
1176
|
-
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({
|
|
1177
1214
|
trainSide: line.trainSide,
|
|
1178
1215
|
moveNumber,
|
|
1179
1216
|
total,
|
package/dist/index.js
CHANGED
|
@@ -68,6 +68,7 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
68
68
|
const [incorrectActive, setIncorrectActive] = react.useState(false);
|
|
69
69
|
const attemptMissedRef = react.useRef(false);
|
|
70
70
|
const { revision, bumpRevision } = reactChessCore.useBoardRevision();
|
|
71
|
+
const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, } = reactChessCore.useCorrectMoveFeedback();
|
|
71
72
|
const boardOrientationRef = react.useRef('white');
|
|
72
73
|
const boardFenRef = react.useRef(EMPTY_BOARD_FEN);
|
|
73
74
|
const notifyHost = () => {
|
|
@@ -93,7 +94,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
93
94
|
expectedUci: expectedUci || null,
|
|
94
95
|
positionFen,
|
|
95
96
|
answerArrowColor,
|
|
96
|
-
|
|
97
|
+
// Refutation + answer-arrow flows must run the full wrong→refutation→answer
|
|
98
|
+
// sequence; the replay "retry without arrow" setting does not apply here.
|
|
99
|
+
autoShowWrongMoves: useRefutation ? true : autoShowWrongMoves,
|
|
97
100
|
engineOptions: refutationEngine,
|
|
98
101
|
});
|
|
99
102
|
const missPhase = (_c = missBoard.missSequence.sequence) === null || _c === void 0 ? void 0 : _c.phase;
|
|
@@ -104,8 +107,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
104
107
|
setShowAnswerArrow(false);
|
|
105
108
|
setIncorrectActive(false);
|
|
106
109
|
attemptMissedRef.current = false;
|
|
110
|
+
clearCorrectMoveFeedback();
|
|
107
111
|
onMissFeedbackChange === null || onMissFeedbackChange === void 0 ? void 0 : onMissFeedbackChange(null);
|
|
108
|
-
}, [onMissFeedbackChange, position]);
|
|
112
|
+
}, [clearCorrectMoveFeedback, onMissFeedbackChange, position]);
|
|
109
113
|
react.useEffect(() => {
|
|
110
114
|
var _a, _b;
|
|
111
115
|
if (!onMissFeedbackChange) {
|
|
@@ -163,7 +167,10 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
163
167
|
(missBoard.boardAnimating ||
|
|
164
168
|
missPhase === 'wrong' ||
|
|
165
169
|
missPhase === 'refutation');
|
|
166
|
-
const arePiecesDraggable = position !== null &&
|
|
170
|
+
const arePiecesDraggable = position !== null &&
|
|
171
|
+
!positionLocked &&
|
|
172
|
+
!missLocked &&
|
|
173
|
+
correctMoveSquare === null;
|
|
167
174
|
const onPieceDrop = (sourceSquare, targetSquare, piece) => {
|
|
168
175
|
if (!position || positionLocked || position.isSolutionRevealed()) {
|
|
169
176
|
return false;
|
|
@@ -228,6 +235,7 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
228
235
|
setIncorrectActive(false);
|
|
229
236
|
missBoard.missSequence.clearSequence();
|
|
230
237
|
onMissFeedbackChange === null || onMissFeedbackChange === void 0 ? void 0 : onMissFeedbackChange(null);
|
|
238
|
+
clearCorrectMoveFeedback();
|
|
231
239
|
const assistedByAnswerArrow = answerArrowVisible && attemptMissedRef.current;
|
|
232
240
|
const guessPayload = {
|
|
233
241
|
index: position.getIndex(),
|
|
@@ -243,32 +251,31 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
|
|
|
243
251
|
else {
|
|
244
252
|
onFeedback(Object.assign(Object.assign({}, guessPayload), { isCorrect: true, isFinished: guess.finished }));
|
|
245
253
|
}
|
|
254
|
+
position.next();
|
|
255
|
+
boardFenRef.current = position.fen();
|
|
246
256
|
notifyHost();
|
|
247
|
-
|
|
257
|
+
const finishCorrectFeedback = () => {
|
|
248
258
|
position.resetInteractions();
|
|
249
259
|
notifyHost();
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (position.hasResumeConfig()) {
|
|
258
|
-
onResumeCorrect === null || onResumeCorrect === void 0 ? void 0 : onResumeCorrect(position);
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
261
|
-
setTimeout(() => {
|
|
260
|
+
if (position.isAlternativeCheckmate()) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (position.hasResumeConfig()) {
|
|
264
|
+
onResumeCorrect === null || onResumeCorrect === void 0 ? void 0 : onResumeCorrect(position);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
262
267
|
if (!position.isFinished()) {
|
|
263
268
|
position.next();
|
|
269
|
+
boardFenRef.current = position.fen();
|
|
264
270
|
}
|
|
265
271
|
notifyHost();
|
|
266
|
-
}
|
|
272
|
+
};
|
|
273
|
+
showCorrectMove(targetSquare, finishCorrectFeedback);
|
|
267
274
|
return true;
|
|
268
275
|
};
|
|
269
276
|
return (jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: hasBoard ? (jsxRuntime.jsx(reactChessCore.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
|
|
270
277
|
? null
|
|
271
|
-
: ((_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 }));
|
|
278
|
+
: ((_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 }));
|
|
272
279
|
};
|
|
273
280
|
|
|
274
281
|
const PuzzleBoard = ({ position, onFeedback, incInteractionNum, boardWidth, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, answerArrowColor, }) => (jsxRuntime.jsx(PuzzlePlaySurface, { position: position, onFeedback: onFeedback, incInteractionNum: incInteractionNum, boardWidth: boardWidth, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, answerArrowColor: answerArrowColor }));
|
|
@@ -733,7 +740,7 @@ const puzzlePositionFromFetch = (fen, moves, resume) => {
|
|
|
733
740
|
const AUTO_ADVANCE_ON_COMPLETE_DELAY_MS = 700;
|
|
734
741
|
const SOLUTION_STEP_MS = 500;
|
|
735
742
|
const RESUME_AUTO_STEP_MS = 500;
|
|
736
|
-
const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = reactChessCore.DEFAULT_ANALYSIS_LAYOUT, renderAnalysisMain, engine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
|
|
743
|
+
const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = reactChessCore.DEFAULT_ANALYSIS_LAYOUT, analysisBoardWidth, renderAnalysisMain, engine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
|
|
737
744
|
var _a, _b, _c, _d;
|
|
738
745
|
const refutationOnIncorrect = showRefutationOnIncorrect !== null && showRefutationOnIncorrect !== void 0 ? showRefutationOnIncorrect : showAnswerArrowOnIncorrect;
|
|
739
746
|
const stackControlsBelow = useStackPuzzleControlsBelow();
|
|
@@ -745,6 +752,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
745
752
|
const [loadingNextPuzzle, setLoadingNextPuzzle] = react.useState(true);
|
|
746
753
|
const [puzzleNum, setPuzzleNum] = react.useState(0);
|
|
747
754
|
const [hasIncorrectAttempt, setHasIncorrectAttempt] = react.useState(false);
|
|
755
|
+
const [hintUsed, setHintUsed] = react.useState(false);
|
|
748
756
|
const [puzzleComplete, setPuzzleComplete] = react.useState(false);
|
|
749
757
|
const [completedAfterMiss, setCompletedAfterMiss] = react.useState(false);
|
|
750
758
|
const [missFeedback, setMissFeedback] = react.useState(null);
|
|
@@ -770,6 +778,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
770
778
|
let cancelled = false;
|
|
771
779
|
setLoadingNextPuzzle(true);
|
|
772
780
|
setHasIncorrectAttempt(false);
|
|
781
|
+
setHintUsed(false);
|
|
773
782
|
setPuzzleComplete(false);
|
|
774
783
|
setCompletedAfterMiss(false);
|
|
775
784
|
setMissFeedback(null);
|
|
@@ -806,12 +815,18 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
806
815
|
const incorrectThisFeedback = feedbackData.hintRequested ||
|
|
807
816
|
feedbackData.solutionShown ||
|
|
808
817
|
feedbackData.isCorrect === false;
|
|
818
|
+
if (feedbackData.hintRequested) {
|
|
819
|
+
setHintUsed(true);
|
|
820
|
+
}
|
|
809
821
|
if (incorrectThisFeedback) {
|
|
810
822
|
setHasIncorrectAttempt(true);
|
|
811
823
|
}
|
|
812
824
|
if (feedbackData.isFinished) {
|
|
813
825
|
setPuzzleComplete(true);
|
|
814
|
-
setCompletedAfterMiss(
|
|
826
|
+
setCompletedAfterMiss((prev) => prev ||
|
|
827
|
+
hasIncorrectAttempt ||
|
|
828
|
+
incorrectThisFeedback ||
|
|
829
|
+
feedbackData.hintRequested === true);
|
|
815
830
|
}
|
|
816
831
|
onFeedback(feedbackData);
|
|
817
832
|
};
|
|
@@ -1015,10 +1030,11 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1015
1030
|
};
|
|
1016
1031
|
const analysis = usePuzzleAnalysis(position, resultStatus, puzzleNum);
|
|
1017
1032
|
const analysisSnapshot = analysis.isOpen && analysis.snapshot ? analysis.snapshot : null;
|
|
1033
|
+
const resolvedAnalysisBoardWidth = analysisBoardWidth !== null && analysisBoardWidth !== void 0 ? analysisBoardWidth : analysisLayout.boardWidth;
|
|
1018
1034
|
const useHostAnalysisUi = Boolean(renderAnalysisSidebar &&
|
|
1019
1035
|
renderAnalysisContainer &&
|
|
1020
1036
|
(renderEngineEvaluation || (engine === null || engine === void 0 ? void 0 : engine.enabled) === false));
|
|
1021
|
-
return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsxRuntime.jsx(reactChessCore.AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsxRuntime.jsx(reactChessCore.AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth:
|
|
1037
|
+
return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsxRuntime.jsx(reactChessCore.AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsxRuntime.jsx(reactChessCore.AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth: resolvedAnalysisBoardWidth, engine: engine, renderMain: renderAnalysisMain !== null && renderAnalysisMain !== void 0 ? renderAnalysisMain : (({ board, sidebar, model }) => (jsxRuntime.jsx(reactChessCore.AnalysisBoardLayout, { layout: analysisLayout, model: model, board: board, sidebar: sidebar }))), renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation !== null && renderEngineEvaluation !== void 0 ? renderEngineEvaluation : (() => null) })) : (jsxRuntime.jsx(reactChessCore.AnalysisBoard, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, layout: analysisLayout, engine: engine, renderMain: renderAnalysisMain, renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation })) })) : (jsxRuntime.jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxRuntime.jsxs("div", { style: puzzleBoardColumnStyle(puzzleBoardWidth, controlsPlacement), children: [jsxRuntime.jsx("div", { style: puzzleBoardSlotWrapperStyle(), children: jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.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 && (jsxRuntime.jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
|
|
1022
1038
|
sideToMove: (_a = position === null || position === void 0 ? void 0 : position.getSideToMove()) !== null && _a !== void 0 ? _a : null,
|
|
1023
1039
|
playerColor: position
|
|
1024
1040
|
? position.getPlayerColor()
|
|
@@ -1030,6 +1046,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1030
1046
|
missPhase: (_c = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase) !== null && _c !== void 0 ? _c : null,
|
|
1031
1047
|
answerArrowVisible: (_d = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.answerArrowVisible) !== null && _d !== void 0 ? _d : false,
|
|
1032
1048
|
completedAfterMiss,
|
|
1049
|
+
hintUsed,
|
|
1033
1050
|
}) }))] }), jsxRuntime.jsxs("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: [renderControls(handleHintRequest, handleShowSolution, handleNextPuzzle, resultStatus, {
|
|
1034
1051
|
visible: analysis.canOpen,
|
|
1035
1052
|
openAnalysis: analysis.openAnalysis,
|
|
@@ -1038,6 +1055,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1038
1055
|
cleanSolve: !hasIncorrectAttempt,
|
|
1039
1056
|
refutationSan: missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.refutationSan,
|
|
1040
1057
|
missPhase: missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase,
|
|
1058
|
+
hintUsed,
|
|
1041
1059
|
}) }))] })] })) }));
|
|
1042
1060
|
};
|
|
1043
1061
|
|
|
@@ -1046,7 +1064,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
|
|
|
1046
1064
|
* side's pieces be dragged. Move validation and sequencing live in
|
|
1047
1065
|
* {@link LineBoardWithControls}.
|
|
1048
1066
|
*/
|
|
1049
|
-
const LineBoard = ({ fen, orientation, trainSide, draggable, onPieceDrop, boardWidth, }) => (jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.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 } }) }));
|
|
1067
|
+
const LineBoard = ({ fen, orientation, trainSide, draggable, correctMoveSquare = null, onPieceDrop, boardWidth, }) => (jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.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 } }) }));
|
|
1050
1068
|
|
|
1051
1069
|
/** Library default line-drill status controls (unstyled). */
|
|
1052
1070
|
const DefaultLineControls = ({ moveNumber, total, finished, isUserTurn, feedback, }) => (jsxRuntime.jsxs("div", { style: rowStyle, children: [jsxRuntime.jsx("span", { style: statusStyle, children: finished ? 'Line complete' : `Move ${moveNumber} of ${total}` }), feedback && !finished && (jsxRuntime.jsx("span", { style: Object.assign(Object.assign({}, statusStyle), { color: feedback.isCorrect ? '#2e7d32' : '#c62828' }), children: feedback.isCorrect
|
|
@@ -1093,6 +1111,8 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1093
1111
|
const [currentIndex, setCurrentIndex] = react.useState(0);
|
|
1094
1112
|
const [finished, setFinished] = react.useState(false);
|
|
1095
1113
|
const [feedback, setFeedback] = react.useState(null);
|
|
1114
|
+
const [displayFen, setDisplayFen] = react.useState(null);
|
|
1115
|
+
const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, isShowingCorrectMove, } = reactChessCore.useCorrectMoveFeedback();
|
|
1096
1116
|
const total = line.movesUci.length;
|
|
1097
1117
|
const orientation = boardOrientationForLine(line.trainSide);
|
|
1098
1118
|
const applyMove = react.useCallback((index) => {
|
|
@@ -1117,7 +1137,7 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1117
1137
|
}, [line.movesUci]);
|
|
1118
1138
|
// Auto-play opponent moves and detect the end of the line.
|
|
1119
1139
|
react.useEffect(() => {
|
|
1120
|
-
if (finished) {
|
|
1140
|
+
if (finished || isShowingCorrectMove) {
|
|
1121
1141
|
return;
|
|
1122
1142
|
}
|
|
1123
1143
|
if (currentIndex >= total) {
|
|
@@ -1136,6 +1156,7 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1136
1156
|
line.trainSide,
|
|
1137
1157
|
applyMove,
|
|
1138
1158
|
opponentMoveDelayMs,
|
|
1159
|
+
isShowingCorrectMove,
|
|
1139
1160
|
]);
|
|
1140
1161
|
// Emit the completion event exactly once.
|
|
1141
1162
|
react.useEffect(() => {
|
|
@@ -1147,15 +1168,16 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1147
1168
|
}, [finished]);
|
|
1148
1169
|
const handleDrop = (source, target, piece) => {
|
|
1149
1170
|
var _a, _b, _c;
|
|
1150
|
-
if (finished) {
|
|
1171
|
+
if (finished || isShowingCorrectMove) {
|
|
1151
1172
|
return false;
|
|
1152
1173
|
}
|
|
1153
|
-
|
|
1174
|
+
const setupFen = displayFen !== null && displayFen !== void 0 ? displayFen : chessRef.current.fen();
|
|
1175
|
+
if (turnFromFen(setupFen) !== line.trainSide) {
|
|
1154
1176
|
return false;
|
|
1155
1177
|
}
|
|
1156
1178
|
const index = currentIndex;
|
|
1157
1179
|
const expected = line.movesUci[index];
|
|
1158
|
-
const dropResult = reactChessCore.evaluateExpectedMoveDrop(
|
|
1180
|
+
const dropResult = reactChessCore.evaluateExpectedMoveDrop(setupFen, source, target, piece, expected, true);
|
|
1159
1181
|
if (dropResult.kind === 'illegal' || dropResult.kind === 'ignored') {
|
|
1160
1182
|
return false;
|
|
1161
1183
|
}
|
|
@@ -1165,16 +1187,31 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
|
|
|
1165
1187
|
const moveFeedback = { index, isCorrect, expectedSan };
|
|
1166
1188
|
setFeedback(moveFeedback);
|
|
1167
1189
|
(_c = onMoveRef.current) === null || _c === void 0 ? void 0 : _c.call(onMoveRef, moveFeedback);
|
|
1168
|
-
|
|
1190
|
+
if (isCorrect) {
|
|
1191
|
+
const nextFen = reactChessCore.fenAfterUci(setupFen, dropResult.uci);
|
|
1192
|
+
if (nextFen) {
|
|
1193
|
+
setDisplayFen(nextFen);
|
|
1194
|
+
}
|
|
1195
|
+
showCorrectMove(target, () => {
|
|
1196
|
+
setDisplayFen(null);
|
|
1197
|
+
setFeedback(null);
|
|
1198
|
+
clearCorrectMoveFeedback();
|
|
1199
|
+
applyMove(index);
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1169
1202
|
return isCorrect;
|
|
1170
1203
|
};
|
|
1204
|
+
const boardFen = displayFen !== null && displayFen !== void 0 ? displayFen : fen;
|
|
1171
1205
|
const moveNumber = Math.min(currentIndex + 1, total);
|
|
1172
|
-
const isUserTurn = !finished &&
|
|
1206
|
+
const isUserTurn = !finished &&
|
|
1207
|
+
!isShowingCorrectMove &&
|
|
1208
|
+
turnFromFen(boardFen) === line.trainSide &&
|
|
1209
|
+
currentIndex < total;
|
|
1173
1210
|
const stackControlsBelow = useStackPuzzleControlsBelow();
|
|
1174
1211
|
const controlsPlacement = stackControlsBelow
|
|
1175
1212
|
? 'below'
|
|
1176
1213
|
: 'beside';
|
|
1177
|
-
return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxRuntime.jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxRuntime.jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(LineBoard, { fen:
|
|
1214
|
+
return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxRuntime.jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxRuntime.jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(LineBoard, { fen: boardFen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, correctMoveSquare: correctMoveSquare, onPieceDrop: handleDrop, boardWidth: boardWidth }) }) }), jsxRuntime.jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
|
|
1178
1215
|
trainSide: line.trainSide,
|
|
1179
1216
|
moveNumber,
|
|
1180
1217
|
total,
|
package/package.json
CHANGED