react-chess-puzzle-kit 1.0.0 → 1.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/LICENSE +21 -21
- package/README.md +331 -331
- package/dist/features/analysis/analysisContext.d.ts +1 -1
- package/dist/features/board/LineBoardWithControls.d.ts +3 -1
- package/dist/features/board/PuzzleBoard.d.ts +6 -1
- package/dist/features/board/PuzzleBoardWithControls.d.ts +63 -6
- package/dist/features/board/PuzzlePlaySurface.d.ts +27 -3
- package/dist/features/board/puzzleBoardLayout.d.ts +17 -4
- package/dist/features/board/useStackPuzzleControlsBelow.d.ts +2 -0
- package/dist/features/position/Position.d.ts +18 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +416 -96
- package/dist/index.js +416 -94
- package/package.json +87 -87
- package/dist/features/analysis/AnalysisBoard.d.ts +0 -18
- package/dist/features/analysis/AnalysisBoardCore.d.ts +0 -28
- package/dist/features/analysis/AnalysisBoardLayout.d.ts +0 -12
- package/dist/features/analysis/AnalysisChessboardView.d.ts +0 -5
- package/dist/features/analysis/AnalysisPosition.d.ts +0 -53
- package/dist/features/analysis/AnalysisTrigger.d.ts +0 -6
- package/dist/features/analysis/DefaultAnalysisContainer.d.ts +0 -3
- package/dist/features/analysis/DefaultAnalysisSidebar.d.ts +0 -2
- package/dist/features/analysis/analysisBoardHighlightColors.d.ts +0 -12
- package/dist/features/analysis/analysisLayout.d.ts +0 -5
- package/dist/features/analysis/core/AnalysisBoardCore.d.ts +0 -28
- package/dist/features/analysis/core/AnalysisChessboardView.d.ts +0 -5
- package/dist/features/analysis/core/AnalysisErrorBoundary.d.ts +0 -14
- package/dist/features/analysis/core/AnalysisPosition.d.ts +0 -54
- package/dist/features/analysis/core/analysisContext.d.ts +0 -13
- package/dist/features/analysis/core/analysisLayout.d.ts +0 -5
- package/dist/features/analysis/core/analysisLayoutConfig.d.ts +0 -6
- package/dist/features/analysis/core/index.d.ts +0 -9
- package/dist/features/analysis/core/renderProps.d.ts +0 -39
- package/dist/features/analysis/core/useAnalysisBoardModel.d.ts +0 -36
- package/dist/features/analysis/core/usePuzzleAnalysis.d.ts +0 -10
- package/dist/features/analysis/defaults/AnalysisBoard.d.ts +0 -16
- package/dist/features/analysis/defaults/AnalysisBoardLayout.d.ts +0 -14
- package/dist/features/analysis/defaults/DefaultAnalysisContainer.d.ts +0 -4
- package/dist/features/analysis/defaults/DefaultAnalysisSidebar.d.ts +0 -3
- package/dist/features/analysis/defaults/analysisLayout.d.ts +0 -3
- package/dist/features/analysis/defaults/analysisModalStyles.d.ts +0 -7
- package/dist/features/analysis/defaults/analysisSidebarColors.d.ts +0 -37
- package/dist/features/analysis/defaults/analysisSidebarRowStyle.d.ts +0 -8
- package/dist/features/analysis/defaults/index.d.ts +0 -8
- package/dist/features/analysis/renderProps.d.ts +0 -39
- package/dist/features/analysis/useAnalysisBoardModel.d.ts +0 -33
- package/dist/features/board/HighlightChessboard.d.ts +0 -2
- package/dist/features/board/boardSquareHighlightColors.d.ts +0 -1
- package/dist/features/board/chessboardTheme.d.ts +0 -2
- package/dist/features/engine/EngineEvaluationPanel.d.ts +0 -8
- package/dist/features/engine/StockfishBrowserEngine.d.ts +0 -1
- package/dist/features/engine/formatEvaluation.d.ts +0 -1
- package/dist/features/engine/index.d.ts +0 -1
- package/dist/features/engine/isAnalyzableFen.d.ts +0 -1
- package/dist/features/engine/parseUciInfo.d.ts +0 -1
- package/dist/features/engine/stockfishUrls.d.ts +0 -1
- package/dist/features/engine/types.d.ts +0 -1
- package/dist/features/engine/useAnalysisEngine.d.ts +0 -1
- package/dist/features/theme/ThemeContext.d.ts +0 -26
- package/dist/features/theme/ThemeProvider.d.ts +0 -7
- package/dist/features/theme/squareHighlightColors.d.ts +0 -50
- package/dist/stories/AnalysisBoard.stories.d.ts +0 -9
- package/dist/stories/HighlightChessboard.stories.d.ts +0 -9
- package/dist/stories/analysisFixtures.d.ts +0 -4
- package/dist/stories/regressions/StockfishAnalysisRegressions.stories.d.ts +0 -7
- package/dist/stories/regressions/regressionAnalysisContext.d.ts +0 -5
package/dist/index.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
-
var reactChessboard = require('react-chessboard');
|
|
6
5
|
var reactChessCore = require('react-chess-core');
|
|
7
6
|
var chess_js = require('chess.js');
|
|
8
7
|
|
|
@@ -23,18 +22,17 @@ const buildAnalysisContext = (position) => {
|
|
|
23
22
|
boardOrientation: position.getPlayerColor(),
|
|
24
23
|
};
|
|
25
24
|
};
|
|
26
|
-
const isAnalysisAvailable = (position,
|
|
25
|
+
const isAnalysisAvailable = (position, _resultStatus) => {
|
|
27
26
|
if (!position) {
|
|
28
27
|
return false;
|
|
29
28
|
}
|
|
30
|
-
return
|
|
31
|
-
resultStatus !== 'none');
|
|
29
|
+
return buildAnalysisContext(position).solutionMoves.length > 0;
|
|
32
30
|
};
|
|
33
31
|
|
|
34
32
|
const usePuzzleAnalysis = (position, resultStatus, puzzleNum) => {
|
|
35
33
|
const [isOpen, setIsOpen] = react.useState(false);
|
|
36
34
|
const [snapshot, setSnapshot] = react.useState(null);
|
|
37
|
-
const canOpen = isAnalysisAvailable(position
|
|
35
|
+
const canOpen = isAnalysisAvailable(position);
|
|
38
36
|
react.useEffect(() => {
|
|
39
37
|
setIsOpen(false);
|
|
40
38
|
setSnapshot(null);
|
|
@@ -59,68 +57,209 @@ const usePuzzleAnalysis = (position, resultStatus, puzzleNum) => {
|
|
|
59
57
|
};
|
|
60
58
|
|
|
61
59
|
const EMPTY_BOARD_FEN = '8/8/8/8/8/8/8/8 w - - 0 1';
|
|
60
|
+
const DEFAULT_ANSWER_ARROW_COLOR = '#42a5f5';
|
|
62
61
|
/**
|
|
63
|
-
* Single mounted board for puzzle play.
|
|
64
|
-
* position loads so
|
|
62
|
+
* Single mounted board for puzzle play. Keeps the prior board (and orientation)
|
|
63
|
+
* visible while the next position loads so layout and perspective do not flicker.
|
|
65
64
|
*/
|
|
66
|
-
const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth, revealAnswerOnIncorrect = false, }) => {
|
|
67
|
-
var _a, _b, _c, _d, _e;
|
|
65
|
+
const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth, onResumeCorrect, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect = false, autoShowWrongMoves = true, refutationEngine, answerArrowColor = DEFAULT_ANSWER_ARROW_COLOR, positionLocked = false, onMissFeedbackChange, }) => {
|
|
66
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
67
|
+
const [showAnswerArrow, setShowAnswerArrow] = react.useState(false);
|
|
68
|
+
const [incorrectActive, setIncorrectActive] = react.useState(false);
|
|
69
|
+
const { revision, bumpRevision } = reactChessCore.useBoardRevision();
|
|
70
|
+
const boardOrientationRef = react.useRef('white');
|
|
71
|
+
const boardFenRef = react.useRef(EMPTY_BOARD_FEN);
|
|
72
|
+
const notifyHost = () => {
|
|
73
|
+
incInteractionNum();
|
|
74
|
+
};
|
|
75
|
+
const expectedUci = (_a = position === null || position === void 0 ? void 0 : position.getExpectedMoveUci()) !== null && _a !== void 0 ? _a : null;
|
|
76
|
+
const positionFen = (_b = position === null || position === void 0 ? void 0 : position.fen()) !== null && _b !== void 0 ? _b : boardFenRef.current;
|
|
77
|
+
const useRefutation = showRefutationOnIncorrect && showAnswerArrowOnIncorrect;
|
|
78
|
+
/**
|
|
79
|
+
* Force a chessboard remount after a rejected drop so pieces snap back.
|
|
80
|
+
* Skip when refutation feedback drives `displayFen` — remounting blinks the
|
|
81
|
+
* whole board without helping snap-back.
|
|
82
|
+
*/
|
|
83
|
+
const snapBoardBack = () => {
|
|
84
|
+
if (useRefutation) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
bumpRevision();
|
|
88
|
+
incInteractionNum();
|
|
89
|
+
};
|
|
90
|
+
const missBoard = reactChessCore.useMissBoard({
|
|
91
|
+
feedback: useRefutation && incorrectActive ? 'incorrect' : null,
|
|
92
|
+
expectedUci: expectedUci || null,
|
|
93
|
+
positionFen,
|
|
94
|
+
answerArrowColor,
|
|
95
|
+
autoShowWrongMoves,
|
|
96
|
+
engineOptions: refutationEngine,
|
|
97
|
+
});
|
|
98
|
+
const missPhase = (_c = missBoard.missSequence.sequence) === null || _c === void 0 ? void 0 : _c.phase;
|
|
99
|
+
const answerArrowVisible = useRefutation
|
|
100
|
+
? incorrectActive && missPhase === 'answer'
|
|
101
|
+
: showAnswerArrow;
|
|
102
|
+
react.useEffect(() => {
|
|
103
|
+
setShowAnswerArrow(false);
|
|
104
|
+
setIncorrectActive(false);
|
|
105
|
+
onMissFeedbackChange === null || onMissFeedbackChange === void 0 ? void 0 : onMissFeedbackChange(null);
|
|
106
|
+
}, [onMissFeedbackChange, position]);
|
|
107
|
+
react.useEffect(() => {
|
|
108
|
+
var _a, _b;
|
|
109
|
+
if (!onMissFeedbackChange) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (useRefutation && incorrectActive) {
|
|
113
|
+
onMissFeedbackChange({
|
|
114
|
+
refutationSan: missBoard.refutation.refutationSan,
|
|
115
|
+
phase: (_b = (_a = missBoard.missSequence.sequence) === null || _a === void 0 ? void 0 : _a.phase) !== null && _b !== void 0 ? _b : null,
|
|
116
|
+
answerArrowVisible,
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (showAnswerArrow) {
|
|
121
|
+
onMissFeedbackChange({
|
|
122
|
+
refutationSan: null,
|
|
123
|
+
phase: null,
|
|
124
|
+
answerArrowVisible: true,
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
onMissFeedbackChange(null);
|
|
129
|
+
}, [
|
|
130
|
+
answerArrowVisible,
|
|
131
|
+
incorrectActive,
|
|
132
|
+
(_d = missBoard.missSequence.sequence) === null || _d === void 0 ? void 0 : _d.phase,
|
|
133
|
+
missBoard.refutation.refutationSan,
|
|
134
|
+
onMissFeedbackChange,
|
|
135
|
+
showAnswerArrow,
|
|
136
|
+
useRefutation,
|
|
137
|
+
]);
|
|
138
|
+
if (position) {
|
|
139
|
+
boardOrientationRef.current = position.getPlayerColor();
|
|
140
|
+
boardFenRef.current = position.fen();
|
|
141
|
+
}
|
|
142
|
+
const boardOrientation = boardOrientationRef.current;
|
|
143
|
+
const boardFen = boardFenRef.current;
|
|
144
|
+
const hasBoard = boardFen !== EMPTY_BOARD_FEN;
|
|
145
|
+
const simpleArrows = react.useMemo(() => {
|
|
146
|
+
if (!showAnswerArrow || !position || useRefutation) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const moveUci = position.getExpectedMoveUci();
|
|
150
|
+
if (moveUci.length < 4) {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
return [[moveUci.slice(0, 2), moveUci.slice(2, 4), answerArrowColor]];
|
|
154
|
+
}, [showAnswerArrow, position, answerArrowColor, useRefutation]);
|
|
155
|
+
const customArrows = useRefutation && incorrectActive
|
|
156
|
+
? missBoard.customArrows
|
|
157
|
+
: simpleArrows;
|
|
158
|
+
const displayFen = useRefutation && incorrectActive ? missBoard.boardPosition : boardFen;
|
|
159
|
+
const missLocked = useRefutation &&
|
|
160
|
+
incorrectActive &&
|
|
161
|
+
(missBoard.boardAnimating ||
|
|
162
|
+
missPhase === 'wrong' ||
|
|
163
|
+
missPhase === 'refutation');
|
|
164
|
+
const arePiecesDraggable = position !== null && !positionLocked && !missLocked;
|
|
68
165
|
const onPieceDrop = (sourceSquare, targetSquare, piece) => {
|
|
69
|
-
if (!position || position.isSolutionRevealed()) {
|
|
166
|
+
if (!position || positionLocked || position.isSolutionRevealed()) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
if (position.hasResumeConfig() && !position.isQuizIndex()) {
|
|
70
170
|
return false;
|
|
71
171
|
}
|
|
72
172
|
if (!position.isLegalMove(sourceSquare, targetSquare)) {
|
|
73
173
|
return false;
|
|
74
174
|
}
|
|
75
|
-
|
|
175
|
+
if (answerArrowVisible &&
|
|
176
|
+
!allowRetryOnIncorrect &&
|
|
177
|
+
!position.isExpectedGuess(sourceSquare, targetSquare)) {
|
|
178
|
+
position.resetInteractions();
|
|
179
|
+
snapBoardBack();
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
const guess = position.tryGuess(sourceSquare, targetSquare, piece, {
|
|
183
|
+
recordIfIncorrect: !(answerArrowVisible && !allowRetryOnIncorrect),
|
|
184
|
+
});
|
|
76
185
|
if (!guess.accepted) {
|
|
77
186
|
onFeedback({
|
|
78
187
|
index: position.getIndex(),
|
|
79
188
|
guess: { sourceSquare, targetSquare, piece },
|
|
80
189
|
isCorrect: false,
|
|
81
190
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
191
|
+
if (useRefutation) {
|
|
192
|
+
const setupFen = position.fen();
|
|
193
|
+
const attemptedUci = reactChessCore.uciFromDrop(setupFen, sourceSquare, targetSquare, piece);
|
|
194
|
+
setIncorrectActive(true);
|
|
195
|
+
if (attemptedUci) {
|
|
196
|
+
missBoard.missSequence.startSequence(setupFen, attemptedUci);
|
|
197
|
+
}
|
|
198
|
+
position.resetInteractions();
|
|
199
|
+
snapBoardBack();
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
const revealIncorrectFeedback = () => {
|
|
203
|
+
if (showAnswerArrowOnIncorrect) {
|
|
204
|
+
position.resetInteractions();
|
|
205
|
+
setShowAnswerArrow(true);
|
|
206
|
+
}
|
|
207
|
+
else if (revealAnswerOnIncorrect) {
|
|
85
208
|
position.resetInteractions();
|
|
86
209
|
position.revealCorrectMove();
|
|
87
210
|
}
|
|
88
211
|
else {
|
|
89
212
|
position.resetInteractions();
|
|
90
213
|
}
|
|
91
|
-
|
|
92
|
-
}
|
|
214
|
+
snapBoardBack();
|
|
215
|
+
};
|
|
216
|
+
if (showAnswerArrowOnIncorrect && !allowRetryOnIncorrect) {
|
|
217
|
+
revealIncorrectFeedback();
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
setTimeout(revealIncorrectFeedback, 500);
|
|
221
|
+
}
|
|
93
222
|
return false;
|
|
94
223
|
}
|
|
224
|
+
setShowAnswerArrow(false);
|
|
225
|
+
setIncorrectActive(false);
|
|
226
|
+
missBoard.missSequence.clearSequence();
|
|
227
|
+
onMissFeedbackChange === null || onMissFeedbackChange === void 0 ? void 0 : onMissFeedbackChange(null);
|
|
95
228
|
onFeedback({
|
|
96
229
|
index: position.getIndex(),
|
|
97
230
|
guess: { sourceSquare, targetSquare, piece },
|
|
98
231
|
isCorrect: true,
|
|
99
232
|
isFinished: guess.finished,
|
|
100
233
|
});
|
|
101
|
-
|
|
234
|
+
notifyHost();
|
|
102
235
|
setTimeout(() => {
|
|
103
236
|
position.resetInteractions();
|
|
104
|
-
|
|
237
|
+
notifyHost();
|
|
105
238
|
}, 500);
|
|
106
239
|
if (position.isAlternativeCheckmate()) {
|
|
107
|
-
|
|
240
|
+
notifyHost();
|
|
108
241
|
return true;
|
|
109
242
|
}
|
|
110
243
|
position.next();
|
|
111
|
-
|
|
244
|
+
notifyHost();
|
|
245
|
+
if (position.hasResumeConfig()) {
|
|
246
|
+
onResumeCorrect === null || onResumeCorrect === void 0 ? void 0 : onResumeCorrect(position);
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
112
249
|
setTimeout(() => {
|
|
113
250
|
if (!position.isFinished()) {
|
|
114
251
|
position.next();
|
|
115
252
|
}
|
|
116
|
-
|
|
253
|
+
notifyHost();
|
|
117
254
|
}, 500);
|
|
118
255
|
return true;
|
|
119
256
|
};
|
|
120
|
-
return (jsxRuntime.jsx(
|
|
257
|
+
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
|
|
258
|
+
? null
|
|
259
|
+
: ((_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 }));
|
|
121
260
|
};
|
|
122
261
|
|
|
123
|
-
const PuzzleBoard = ({ position, onFeedback, incInteractionNum, boardWidth, revealAnswerOnIncorrect = false, }) => (jsxRuntime.jsx(PuzzlePlaySurface, { position: position, onFeedback: onFeedback, incInteractionNum: incInteractionNum, boardWidth: boardWidth, revealAnswerOnIncorrect: revealAnswerOnIncorrect }));
|
|
262
|
+
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 }));
|
|
124
263
|
|
|
125
264
|
const isAttemptFinished = (resultStatus) => resultStatus === 'complete' || resultStatus === 'incorrect';
|
|
126
265
|
/** Library default hint / next / analysis / result controls (unstyled buttons). */
|
|
@@ -128,12 +267,10 @@ const DefaultPuzzleControls = ({ showHint, showSolution, nextPuzzle, resultStatu
|
|
|
128
267
|
const defaultRenderControls = (showHint, showSolution, nextPuzzle, resultStatus, analysis, controlState) => (jsxRuntime.jsx(DefaultPuzzleControls, { showHint: showHint, showSolution: showSolution, nextPuzzle: nextPuzzle, resultStatus: resultStatus, analysis: analysis, controlState: controlState }));
|
|
129
268
|
const rowStyle$1 = {
|
|
130
269
|
display: 'flex',
|
|
131
|
-
|
|
132
|
-
alignItems: '
|
|
270
|
+
flexDirection: 'column',
|
|
271
|
+
alignItems: 'stretch',
|
|
133
272
|
gap: 8,
|
|
134
273
|
width: '100%',
|
|
135
|
-
minHeight: 96,
|
|
136
|
-
alignContent: 'flex-start',
|
|
137
274
|
};
|
|
138
275
|
const buttonStyle = {
|
|
139
276
|
cursor: 'pointer',
|
|
@@ -151,27 +288,92 @@ const statusStyle$1 = {
|
|
|
151
288
|
|
|
152
289
|
/** Default pixel width for the live puzzle board (analysis uses {@link DEFAULT_ANALYSIS_LAYOUT}). */
|
|
153
290
|
const DEFAULT_PUZZLE_BOARD_WIDTH = 480;
|
|
154
|
-
/**
|
|
155
|
-
const
|
|
156
|
-
|
|
291
|
+
/** Width reserved for the vertical controls column beside the board. */
|
|
292
|
+
const PUZZLE_CONTROLS_COLUMN_WIDTH = 160;
|
|
293
|
+
/** Gap between the board column and controls column. */
|
|
294
|
+
const PUZZLE_BOARD_CONTROLS_GAP = 16;
|
|
295
|
+
/** Viewports at or below this width stack controls under the board (through tablet landscape). */
|
|
296
|
+
const PUZZLE_CONTROLS_STACK_BREAKPOINT_PX = 1365;
|
|
297
|
+
/** Horizontal space taken by the controls column when it sits beside the board. */
|
|
298
|
+
const PUZZLE_CONTROLS_BESIDE_RESERVE_PX = PUZZLE_CONTROLS_COLUMN_WIDTH + PUZZLE_BOARD_CONTROLS_GAP;
|
|
299
|
+
/** Minimum height reserved below the board so the caption slot does not shift between loads. */
|
|
300
|
+
const PUZZLE_BOARD_CAPTION_MIN_HEIGHT = 24;
|
|
301
|
+
const puzzlePlayRowStyle = (placement = 'beside') => ({
|
|
302
|
+
display: 'flex',
|
|
303
|
+
flexDirection: placement === 'below' ? 'column' : 'row',
|
|
304
|
+
alignItems: placement === 'below' ? 'stretch' : 'flex-start',
|
|
305
|
+
gap: PUZZLE_BOARD_CONTROLS_GAP,
|
|
306
|
+
width: placement === 'below' ? '100%' : 'fit-content',
|
|
307
|
+
maxWidth: '100%',
|
|
308
|
+
});
|
|
309
|
+
const puzzleBoardColumnStyle = (boardWidth, placement = 'beside') => ({
|
|
157
310
|
display: 'flex',
|
|
158
311
|
flexDirection: 'column',
|
|
159
312
|
width: boardWidth,
|
|
160
313
|
maxWidth: '100%',
|
|
314
|
+
flexShrink: 0,
|
|
315
|
+
alignSelf: placement === 'below' ? 'center' : undefined,
|
|
316
|
+
});
|
|
317
|
+
const puzzleBoardCaptionSlotStyle = () => ({
|
|
318
|
+
width: '100%',
|
|
319
|
+
minHeight: PUZZLE_BOARD_CAPTION_MIN_HEIGHT,
|
|
320
|
+
flexShrink: 0,
|
|
321
|
+
display: 'flex',
|
|
322
|
+
justifyContent: 'center',
|
|
323
|
+
alignItems: 'center',
|
|
324
|
+
marginTop: 4,
|
|
325
|
+
});
|
|
326
|
+
const puzzleBoardSlotWrapperStyle = () => ({
|
|
327
|
+
position: 'relative',
|
|
328
|
+
width: '100%',
|
|
329
|
+
flexShrink: 0,
|
|
161
330
|
});
|
|
162
331
|
const puzzleBoardSlotStyle = () => ({
|
|
163
332
|
width: '100%',
|
|
164
333
|
aspectRatio: '1 / 1',
|
|
165
334
|
flexShrink: 0,
|
|
166
335
|
});
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
336
|
+
const puzzleControlsFeedbackStyle = (placement = 'beside') => ({
|
|
337
|
+
display: 'flex',
|
|
338
|
+
flexDirection: 'column',
|
|
339
|
+
alignItems: placement === 'below' ? 'center' : 'flex-end',
|
|
340
|
+
gap: 4,
|
|
341
|
+
marginTop: placement === 'below' ? 0 : 'auto',
|
|
342
|
+
pointerEvents: 'none',
|
|
343
|
+
});
|
|
344
|
+
const puzzleControlsSlotStyle = (placement = 'beside') => ({
|
|
170
345
|
display: 'flex',
|
|
171
|
-
|
|
172
|
-
|
|
346
|
+
flexDirection: 'column',
|
|
347
|
+
alignItems: 'stretch',
|
|
348
|
+
gap: 8,
|
|
349
|
+
flexShrink: 0,
|
|
350
|
+
minWidth: placement === 'below' ? undefined : PUZZLE_CONTROLS_COLUMN_WIDTH,
|
|
351
|
+
width: placement === 'below' ? '100%' : undefined,
|
|
352
|
+
alignSelf: 'stretch',
|
|
173
353
|
});
|
|
174
354
|
|
|
355
|
+
const stackControlsQuery = `(max-width: ${PUZZLE_CONTROLS_STACK_BREAKPOINT_PX}px)`;
|
|
356
|
+
const readStackControlsBelow = () => {
|
|
357
|
+
if (typeof window === 'undefined') {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
return window.matchMedia(stackControlsQuery).matches;
|
|
361
|
+
};
|
|
362
|
+
/** True when hint / next controls should sit below the board instead of beside it. */
|
|
363
|
+
const useStackPuzzleControlsBelow = () => {
|
|
364
|
+
const [stackBelow, setStackBelow] = react.useState(readStackControlsBelow);
|
|
365
|
+
react.useEffect(() => {
|
|
366
|
+
const mediaQueryList = window.matchMedia(stackControlsQuery);
|
|
367
|
+
const onChange = (event) => {
|
|
368
|
+
setStackBelow(event.matches);
|
|
369
|
+
};
|
|
370
|
+
mediaQueryList.addEventListener('change', onChange);
|
|
371
|
+
setStackBelow(mediaQueryList.matches);
|
|
372
|
+
return () => mediaQueryList.removeEventListener('change', onChange);
|
|
373
|
+
}, []);
|
|
374
|
+
return stackBelow;
|
|
375
|
+
};
|
|
376
|
+
|
|
175
377
|
/** Apply a UCI move (e.g. `e7e8q`) without throwing. */
|
|
176
378
|
function applyUciMove(chess, uci) {
|
|
177
379
|
if (!uci || uci.length < 4) {
|
|
@@ -232,6 +434,9 @@ class Position {
|
|
|
232
434
|
fen() {
|
|
233
435
|
return this.chess.fen();
|
|
234
436
|
}
|
|
437
|
+
getSideToMove() {
|
|
438
|
+
return this.chess.turn() === 'w' ? 'white' : 'black';
|
|
439
|
+
}
|
|
235
440
|
getCheckSquare() {
|
|
236
441
|
return getCheckSquareFromChess(this.chess);
|
|
237
442
|
}
|
|
@@ -261,7 +466,7 @@ function playerColorForSolution(initialFen, moves) {
|
|
|
261
466
|
return chess.turn() === 'w' ? 'white' : 'black';
|
|
262
467
|
}
|
|
263
468
|
class PuzzlePosition extends Position {
|
|
264
|
-
constructor(initialFEN, moves) {
|
|
469
|
+
constructor(initialFEN, moves, resumeConfig) {
|
|
265
470
|
super();
|
|
266
471
|
this.isCorrect = false;
|
|
267
472
|
this.guessedMove = '';
|
|
@@ -269,7 +474,7 @@ class PuzzlePosition extends Position {
|
|
|
269
474
|
this.solutionRevealed = false;
|
|
270
475
|
this.moveHistory = [];
|
|
271
476
|
this.usedAlternativeCheckmate = false;
|
|
272
|
-
this.tryGuess = (sourceSquare, targetSquare, piece) => {
|
|
477
|
+
this.tryGuess = (sourceSquare, targetSquare, piece, options) => {
|
|
273
478
|
var _a;
|
|
274
479
|
const candidates = this.buildCandidateUcis(sourceSquare, targetSquare);
|
|
275
480
|
if (candidates.length === 0) {
|
|
@@ -295,12 +500,14 @@ class PuzzlePosition extends Position {
|
|
|
295
500
|
}
|
|
296
501
|
this.isCorrect = false;
|
|
297
502
|
this.guessedMove = `${sourceSquare}${targetSquare}`;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
503
|
+
if ((options === null || options === void 0 ? void 0 : options.recordIfIncorrect) !== false) {
|
|
504
|
+
this.moveHistory.push({
|
|
505
|
+
ply: this.moveHistory.length,
|
|
506
|
+
uci: (_a = candidates[candidates.length - 1]) !== null && _a !== void 0 ? _a : this.guessedMove,
|
|
507
|
+
actor: 'attempt',
|
|
508
|
+
isCorrect: false,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
304
511
|
return { accepted: false, finished: false };
|
|
305
512
|
};
|
|
306
513
|
this.judgeGuess = (sourceSquare, targetSquare, piece) => this.tryGuess(sourceSquare, targetSquare, piece).accepted;
|
|
@@ -308,6 +515,12 @@ class PuzzlePosition extends Position {
|
|
|
308
515
|
this.chess.load(initialFEN);
|
|
309
516
|
this.moves = moves;
|
|
310
517
|
this.playerColor = playerColorForSolution(initialFEN, moves);
|
|
518
|
+
this.resumeConfig = resumeConfig;
|
|
519
|
+
if (resumeConfig) {
|
|
520
|
+
for (let j = 0; j < resumeConfig.startIndex; j++) {
|
|
521
|
+
super.next();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
311
524
|
}
|
|
312
525
|
next() {
|
|
313
526
|
if (this.i >= this.moves.length) {
|
|
@@ -420,6 +633,12 @@ class PuzzlePosition extends Position {
|
|
|
420
633
|
this.usedAlternativeCheckmate = true;
|
|
421
634
|
return true;
|
|
422
635
|
}
|
|
636
|
+
/** True when dragging from/to matches the expected move at the current index. */
|
|
637
|
+
isExpectedGuess(sourceSquare, targetSquare) {
|
|
638
|
+
const candidates = this.buildCandidateUcis(sourceSquare, targetSquare);
|
|
639
|
+
const expectedUci = this.moves[this.i];
|
|
640
|
+
return candidates.some((uci) => uci === expectedUci);
|
|
641
|
+
}
|
|
423
642
|
resetInteractions() {
|
|
424
643
|
this.guessedMove = '';
|
|
425
644
|
this.isHintWanted = false;
|
|
@@ -457,9 +676,27 @@ class PuzzlePosition extends Position {
|
|
|
457
676
|
}
|
|
458
677
|
return this.guessedMove.slice(2, 4);
|
|
459
678
|
}
|
|
679
|
+
/** UCI of the move the player should play at the current index. */
|
|
680
|
+
getExpectedMoveUci() {
|
|
681
|
+
var _a;
|
|
682
|
+
return (_a = this.moves[this.i]) !== null && _a !== void 0 ? _a : '';
|
|
683
|
+
}
|
|
460
684
|
getPlayerColor() {
|
|
461
685
|
return this.playerColor;
|
|
462
686
|
}
|
|
687
|
+
hasResumeConfig() {
|
|
688
|
+
return this.resumeConfig !== undefined;
|
|
689
|
+
}
|
|
690
|
+
getResumeConfig() {
|
|
691
|
+
return this.resumeConfig;
|
|
692
|
+
}
|
|
693
|
+
/** True when the user must find the move at the current index. */
|
|
694
|
+
isQuizIndex() {
|
|
695
|
+
if (!this.resumeConfig) {
|
|
696
|
+
return !this.isFinished();
|
|
697
|
+
}
|
|
698
|
+
return this.resumeConfig.quizAtIndices.includes(this.i);
|
|
699
|
+
}
|
|
463
700
|
}
|
|
464
701
|
class GamePosition extends Position {
|
|
465
702
|
constructor(PGN) {
|
|
@@ -472,19 +709,36 @@ class GamePosition extends Position {
|
|
|
472
709
|
}
|
|
473
710
|
}
|
|
474
711
|
|
|
475
|
-
/**
|
|
476
|
-
const
|
|
712
|
+
/** Apply the opponent setup ply immediately so the board does not flash on load. */
|
|
713
|
+
const puzzlePositionFromFetch = (fen, moves, resume) => {
|
|
714
|
+
const newPosition = new PuzzlePosition(fen, moves, resume);
|
|
715
|
+
if (!resume && moves.length > 1) {
|
|
716
|
+
newPosition.next();
|
|
717
|
+
}
|
|
718
|
+
return newPosition;
|
|
719
|
+
};
|
|
477
720
|
/** Brief pause so the user sees a correct result before the next card loads. */
|
|
478
721
|
const AUTO_ADVANCE_ON_COMPLETE_DELAY_MS = 700;
|
|
479
722
|
const SOLUTION_STEP_MS = 500;
|
|
480
|
-
const
|
|
723
|
+
const RESUME_AUTO_STEP_MS = 500;
|
|
724
|
+
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, }) => {
|
|
725
|
+
var _a, _b, _c, _d;
|
|
726
|
+
const refutationOnIncorrect = showRefutationOnIncorrect !== null && showRefutationOnIncorrect !== void 0 ? showRefutationOnIncorrect : showAnswerArrowOnIncorrect;
|
|
727
|
+
const stackControlsBelow = useStackPuzzleControlsBelow();
|
|
728
|
+
const controlsPlacement = stackControlsBelow
|
|
729
|
+
? 'below'
|
|
730
|
+
: 'beside';
|
|
481
731
|
const { onFetch, onFetchError, onFeedback } = apiProxy;
|
|
482
732
|
const [position, setPosition] = react.useState(null);
|
|
733
|
+
const [loadingNextPuzzle, setLoadingNextPuzzle] = react.useState(true);
|
|
483
734
|
const [puzzleNum, setPuzzleNum] = react.useState(0);
|
|
484
735
|
const [hasIncorrectAttempt, setHasIncorrectAttempt] = react.useState(false);
|
|
485
736
|
const [puzzleComplete, setPuzzleComplete] = react.useState(false);
|
|
737
|
+
const [completedAfterMiss, setCompletedAfterMiss] = react.useState(false);
|
|
738
|
+
const [missFeedback, setMissFeedback] = react.useState(null);
|
|
486
739
|
const [, setInteractionNum] = react.useState(0);
|
|
487
740
|
const solutionAnimationRef = react.useRef({ cancelled: false, timeoutIds: [] });
|
|
741
|
+
const resumeAnimationRef = react.useRef({ cancelled: false, timeoutIds: [] });
|
|
488
742
|
const incInteractionNum = () => {
|
|
489
743
|
setInteractionNum((prev) => prev + 1);
|
|
490
744
|
};
|
|
@@ -494,57 +748,58 @@ const PuzzleBoardWithControls = ({ theme, apiProxy, renderControls = defaultRend
|
|
|
494
748
|
anim.timeoutIds.forEach(clearTimeout);
|
|
495
749
|
solutionAnimationRef.current = { cancelled: false, timeoutIds: [] };
|
|
496
750
|
};
|
|
751
|
+
const clearResumeAnimation = () => {
|
|
752
|
+
const anim = resumeAnimationRef.current;
|
|
753
|
+
anim.cancelled = true;
|
|
754
|
+
anim.timeoutIds.forEach(clearTimeout);
|
|
755
|
+
resumeAnimationRef.current = { cancelled: false, timeoutIds: [] };
|
|
756
|
+
};
|
|
497
757
|
react.useEffect(() => {
|
|
498
758
|
let cancelled = false;
|
|
499
|
-
|
|
500
|
-
setPosition(null);
|
|
759
|
+
setLoadingNextPuzzle(true);
|
|
501
760
|
setHasIncorrectAttempt(false);
|
|
502
761
|
setPuzzleComplete(false);
|
|
762
|
+
setCompletedAfterMiss(false);
|
|
763
|
+
setMissFeedback(null);
|
|
503
764
|
onFetch()
|
|
504
765
|
.then((data) => {
|
|
505
766
|
if (cancelled) {
|
|
506
767
|
return;
|
|
507
768
|
}
|
|
508
|
-
if (!data ||
|
|
769
|
+
if (!(data === null || data === void 0 ? void 0 : data.fen) || !Array.isArray(data.moves) || data.moves.length === 0) {
|
|
509
770
|
console.error('Invalid data fetched:', data);
|
|
771
|
+
setLoadingNextPuzzle(false);
|
|
510
772
|
return;
|
|
511
773
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
if (cancelled) {
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
if (newPosition.next()) {
|
|
522
|
-
incInteractionNum();
|
|
523
|
-
}
|
|
524
|
-
}, OPPONENT_OPENING_MOVE_DELAY_MS);
|
|
525
|
-
}
|
|
774
|
+
setPosition(puzzlePositionFromFetch(data.fen, data.moves, data.resume));
|
|
775
|
+
requestAnimationFrame(() => {
|
|
776
|
+
if (!cancelled) {
|
|
777
|
+
setLoadingNextPuzzle(false);
|
|
778
|
+
}
|
|
779
|
+
});
|
|
526
780
|
})
|
|
527
781
|
.catch((error) => {
|
|
528
782
|
if (!cancelled) {
|
|
783
|
+
setLoadingNextPuzzle(false);
|
|
529
784
|
onFetchError === null || onFetchError === void 0 ? void 0 : onFetchError(error);
|
|
530
785
|
}
|
|
531
786
|
});
|
|
532
787
|
return () => {
|
|
533
788
|
cancelled = true;
|
|
534
789
|
clearSolutionAnimation();
|
|
535
|
-
|
|
536
|
-
clearTimeout(openingMoveTimeoutId);
|
|
537
|
-
}
|
|
790
|
+
clearResumeAnimation();
|
|
538
791
|
};
|
|
539
792
|
}, [puzzleNum]);
|
|
540
793
|
const handleFeedback = (feedbackData) => {
|
|
541
|
-
|
|
794
|
+
const incorrectThisFeedback = feedbackData.hintRequested ||
|
|
542
795
|
feedbackData.solutionShown ||
|
|
543
|
-
feedbackData.isCorrect === false
|
|
796
|
+
feedbackData.isCorrect === false;
|
|
797
|
+
if (incorrectThisFeedback) {
|
|
544
798
|
setHasIncorrectAttempt(true);
|
|
545
799
|
}
|
|
546
800
|
if (feedbackData.isFinished) {
|
|
547
801
|
setPuzzleComplete(true);
|
|
802
|
+
setCompletedAfterMiss(hasIncorrectAttempt || incorrectThisFeedback);
|
|
548
803
|
}
|
|
549
804
|
onFeedback(feedbackData);
|
|
550
805
|
};
|
|
@@ -638,6 +893,53 @@ const PuzzleBoardWithControls = ({ theme, apiProxy, renderControls = defaultRend
|
|
|
638
893
|
};
|
|
639
894
|
advance();
|
|
640
895
|
};
|
|
896
|
+
const runResumeAutoAdvance = (pos) => {
|
|
897
|
+
clearResumeAnimation();
|
|
898
|
+
const anim = {
|
|
899
|
+
cancelled: false,
|
|
900
|
+
timeoutIds: [],
|
|
901
|
+
};
|
|
902
|
+
resumeAnimationRef.current = anim;
|
|
903
|
+
const schedule = (fn, ms) => {
|
|
904
|
+
const id = setTimeout(() => {
|
|
905
|
+
if (anim.cancelled) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
fn();
|
|
909
|
+
}, ms);
|
|
910
|
+
anim.timeoutIds.push(id);
|
|
911
|
+
};
|
|
912
|
+
const finish = () => {
|
|
913
|
+
setPuzzleComplete(true);
|
|
914
|
+
handleFeedback({
|
|
915
|
+
index: pos.getIndex(),
|
|
916
|
+
isCorrect: true,
|
|
917
|
+
isFinished: true,
|
|
918
|
+
});
|
|
919
|
+
incInteractionNum();
|
|
920
|
+
};
|
|
921
|
+
const step = () => {
|
|
922
|
+
if (anim.cancelled) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
if (pos.isFinished()) {
|
|
926
|
+
finish();
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
if (pos.isQuizIndex()) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
if (!pos.next()) {
|
|
933
|
+
if (pos.isFinished()) {
|
|
934
|
+
finish();
|
|
935
|
+
}
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
incInteractionNum();
|
|
939
|
+
schedule(step, RESUME_AUTO_STEP_MS);
|
|
940
|
+
};
|
|
941
|
+
schedule(step, RESUME_AUTO_STEP_MS);
|
|
942
|
+
};
|
|
641
943
|
const handleShowSolution = () => {
|
|
642
944
|
if (!position) {
|
|
643
945
|
return;
|
|
@@ -671,7 +973,10 @@ const PuzzleBoardWithControls = ({ theme, apiProxy, renderControls = defaultRend
|
|
|
671
973
|
if (!autoAdvanceOnComplete) {
|
|
672
974
|
return;
|
|
673
975
|
}
|
|
674
|
-
if (resultStatus !== 'complete'
|
|
976
|
+
if (resultStatus !== 'complete') {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
if (hasIncorrectAttempt && !autoAdvanceOnCompleteAfterIncorrect) {
|
|
675
980
|
return;
|
|
676
981
|
}
|
|
677
982
|
const timer = setTimeout(() => {
|
|
@@ -682,6 +987,7 @@ const PuzzleBoardWithControls = ({ theme, apiProxy, renderControls = defaultRend
|
|
|
682
987
|
};
|
|
683
988
|
}, [
|
|
684
989
|
autoAdvanceOnComplete,
|
|
990
|
+
autoAdvanceOnCompleteAfterIncorrect,
|
|
685
991
|
resultStatus,
|
|
686
992
|
hasIncorrectAttempt,
|
|
687
993
|
handleNextPuzzle,
|
|
@@ -689,8 +995,9 @@ const PuzzleBoardWithControls = ({ theme, apiProxy, renderControls = defaultRend
|
|
|
689
995
|
]);
|
|
690
996
|
const controlState = {
|
|
691
997
|
canShowHint: position !== null &&
|
|
692
|
-
|
|
693
|
-
!position.isSolutionRevealed()
|
|
998
|
+
!position.isFinished() &&
|
|
999
|
+
!position.isSolutionRevealed() &&
|
|
1000
|
+
!(hasIncorrectAttempt && showAnswerArrowOnIncorrect && !allowRetryOnIncorrect),
|
|
694
1001
|
canShowSolution: position !== null &&
|
|
695
1002
|
(position.isSolutionRevealed() || !position.isFinished()),
|
|
696
1003
|
};
|
|
@@ -699,10 +1006,27 @@ const PuzzleBoardWithControls = ({ theme, apiProxy, renderControls = defaultRend
|
|
|
699
1006
|
const useHostAnalysisUi = Boolean(renderAnalysisSidebar &&
|
|
700
1007
|
renderAnalysisContainer &&
|
|
701
1008
|
(renderEngineEvaluation || (engine === null || engine === void 0 ? void 0 : engine.enabled) === false));
|
|
702
|
-
return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, children: analysisSnapshot ? (jsxRuntime.jsx(reactChessCore.AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsxRuntime.jsx(reactChessCore.AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth: analysisLayout.boardWidth, 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:
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1009
|
+
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: analysisLayout.boardWidth, 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({
|
|
1010
|
+
sideToMove: (_a = position === null || position === void 0 ? void 0 : position.getSideToMove()) !== null && _a !== void 0 ? _a : null,
|
|
1011
|
+
playerColor: position
|
|
1012
|
+
? position.getPlayerColor()
|
|
1013
|
+
: null,
|
|
1014
|
+
incorrectAttempt: resultStatus === 'incorrect',
|
|
1015
|
+
complete: resultStatus === 'complete',
|
|
1016
|
+
cleanSolve: !hasIncorrectAttempt,
|
|
1017
|
+
refutationSan: (_b = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.refutationSan) !== null && _b !== void 0 ? _b : null,
|
|
1018
|
+
missPhase: (_c = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase) !== null && _c !== void 0 ? _c : null,
|
|
1019
|
+
answerArrowVisible: (_d = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.answerArrowVisible) !== null && _d !== void 0 ? _d : false,
|
|
1020
|
+
completedAfterMiss,
|
|
1021
|
+
}) }))] }), jsxRuntime.jsxs("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: [renderControls(handleHintRequest, handleShowSolution, handleNextPuzzle, resultStatus, {
|
|
1022
|
+
visible: analysis.canOpen,
|
|
1023
|
+
openAnalysis: analysis.openAnalysis,
|
|
1024
|
+
}, controlState), renderBoardFeedback && resultStatus === 'complete' && (jsxRuntime.jsx("div", { style: puzzleControlsFeedbackStyle(controlsPlacement), children: renderBoardFeedback({
|
|
1025
|
+
resultStatus,
|
|
1026
|
+
cleanSolve: !hasIncorrectAttempt,
|
|
1027
|
+
refutationSan: missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.refutationSan,
|
|
1028
|
+
missPhase: missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase,
|
|
1029
|
+
}) }))] })] })) }));
|
|
706
1030
|
};
|
|
707
1031
|
|
|
708
1032
|
/**
|
|
@@ -710,7 +1034,7 @@ const PuzzleBoardWithControls = ({ theme, apiProxy, renderControls = defaultRend
|
|
|
710
1034
|
* side's pieces be dragged. Move validation and sequencing live in
|
|
711
1035
|
* {@link LineBoardWithControls}.
|
|
712
1036
|
*/
|
|
713
|
-
const LineBoard = ({ fen, orientation, trainSide, draggable, onPieceDrop, boardWidth, }) => (jsxRuntime.jsx(
|
|
1037
|
+
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 } }) }));
|
|
714
1038
|
|
|
715
1039
|
/** Library default line-drill status controls (unstyled). */
|
|
716
1040
|
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
|
|
@@ -745,7 +1069,7 @@ const boardOrientationForLine = (side) => side === 'b' ? 'black' : 'white';
|
|
|
745
1069
|
* tactical puzzles. Mount one instance per drill (key it by line) so its
|
|
746
1070
|
* internal state resets between lines.
|
|
747
1071
|
*/
|
|
748
|
-
const LineBoardWithControls = ({ theme, line, onComplete, onMove, renderControls = defaultRenderLineControls, boardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, opponentMoveDelayMs = DEFAULT_OPPONENT_MOVE_DELAY_MS, }) => {
|
|
1072
|
+
const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, renderControls = defaultRenderLineControls, boardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, opponentMoveDelayMs = DEFAULT_OPPONENT_MOVE_DELAY_MS, }) => {
|
|
749
1073
|
const chessRef = react.useRef(new chess_js.Chess(line.startFen));
|
|
750
1074
|
const perMoveRef = react.useRef([]);
|
|
751
1075
|
const completedRef = react.useRef(false);
|
|
@@ -809,8 +1133,8 @@ const LineBoardWithControls = ({ theme, line, onComplete, onMove, renderControls
|
|
|
809
1133
|
completedRef.current = true;
|
|
810
1134
|
onCompleteRef.current(perMoveRef.current);
|
|
811
1135
|
}, [finished]);
|
|
812
|
-
const handleDrop = (source, target) => {
|
|
813
|
-
var _a, _b, _c
|
|
1136
|
+
const handleDrop = (source, target, piece) => {
|
|
1137
|
+
var _a, _b, _c;
|
|
814
1138
|
if (finished) {
|
|
815
1139
|
return false;
|
|
816
1140
|
}
|
|
@@ -818,31 +1142,27 @@ const LineBoardWithControls = ({ theme, line, onComplete, onMove, renderControls
|
|
|
818
1142
|
return false;
|
|
819
1143
|
}
|
|
820
1144
|
const index = currentIndex;
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const move = probe.move({ from: source, to: target, promotion: 'q' });
|
|
825
|
-
if (!move) {
|
|
826
|
-
return false;
|
|
827
|
-
}
|
|
828
|
-
userUci = `${move.from}${move.to}${(_a = move.promotion) !== null && _a !== void 0 ? _a : ''}`;
|
|
829
|
-
}
|
|
830
|
-
catch (_e) {
|
|
1145
|
+
const expected = line.movesUci[index];
|
|
1146
|
+
const dropResult = reactChessCore.evaluateExpectedMoveDrop(chessRef.current.fen(), source, target, piece, expected, true);
|
|
1147
|
+
if (dropResult.kind === 'illegal' || dropResult.kind === 'ignored') {
|
|
831
1148
|
return false;
|
|
832
1149
|
}
|
|
833
|
-
const
|
|
834
|
-
const
|
|
835
|
-
const expectedSan = (_c = (_b = line.movesSan) === null || _b === void 0 ? void 0 : _b[index]) !== null && _c !== void 0 ? _c : expected;
|
|
1150
|
+
const isCorrect = dropResult.kind === 'correct';
|
|
1151
|
+
const expectedSan = (_b = (_a = line.movesSan) === null || _a === void 0 ? void 0 : _a[index]) !== null && _b !== void 0 ? _b : expected;
|
|
836
1152
|
perMoveRef.current.push({ index, isCorrect });
|
|
837
1153
|
const moveFeedback = { index, isCorrect, expectedSan };
|
|
838
1154
|
setFeedback(moveFeedback);
|
|
839
|
-
(
|
|
1155
|
+
(_c = onMoveRef.current) === null || _c === void 0 ? void 0 : _c.call(onMoveRef, moveFeedback);
|
|
840
1156
|
applyMove(index);
|
|
841
1157
|
return isCorrect;
|
|
842
1158
|
};
|
|
843
1159
|
const moveNumber = Math.min(currentIndex + 1, total);
|
|
844
1160
|
const isUserTurn = !finished && turnFromFen(fen) === line.trainSide && currentIndex < total;
|
|
845
|
-
|
|
1161
|
+
const stackControlsBelow = useStackPuzzleControlsBelow();
|
|
1162
|
+
const controlsPlacement = stackControlsBelow
|
|
1163
|
+
? 'below'
|
|
1164
|
+
: 'beside';
|
|
1165
|
+
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: fen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, onPieceDrop: handleDrop, boardWidth: boardWidth }) }) }), jsxRuntime.jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
|
|
846
1166
|
trainSide: line.trainSide,
|
|
847
1167
|
moveNumber,
|
|
848
1168
|
total,
|
|
@@ -869,6 +1189,8 @@ exports.DefaultPuzzleControls = DefaultPuzzleControls;
|
|
|
869
1189
|
exports.GamePosition = GamePosition;
|
|
870
1190
|
exports.LineBoard = LineBoard;
|
|
871
1191
|
exports.LineBoardWithControls = LineBoardWithControls;
|
|
1192
|
+
exports.PUZZLE_CONTROLS_BESIDE_RESERVE_PX = PUZZLE_CONTROLS_BESIDE_RESERVE_PX;
|
|
1193
|
+
exports.PUZZLE_CONTROLS_STACK_BREAKPOINT_PX = PUZZLE_CONTROLS_STACK_BREAKPOINT_PX;
|
|
872
1194
|
exports.Position = Position;
|
|
873
1195
|
exports.PuzzleBoard = PuzzleBoard;
|
|
874
1196
|
exports.PuzzleBoardWithControls = PuzzleBoardWithControls;
|