@react-chess-tools/react-chess-puzzle 0.4.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @react-chess-tools/react-chess-puzzle
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - ca0a210: add missing properties to ChessPuzzleContextType
8
+ - 755a85d: fix: export missing TypeScript types and component props
9
+ - a9c69b5: test: add unit tests
10
+ - 5ed0baf: fix: fix SSR compatibility and prevent unnecessary sound re-renders
11
+ - c3ac738: fix: fix state management and lifecycle issues in chess components
12
+ - Updated dependencies [755a85d]
13
+ - Updated dependencies [a9c69b5]
14
+ - Updated dependencies [5ed0baf]
15
+ - Updated dependencies [c3ac738]
16
+ - @react-chess-tools/react-chess-game@0.4.1
17
+
18
+ ## 0.5.0
19
+
20
+ ### Minor Changes
21
+
22
+ - 3060132: Pass the whole puzzle context to onSolve and onFail callbacks
23
+
3
24
  ## 0.4.0
4
25
 
5
26
  ### Minor Changes
package/README.MD CHANGED
@@ -1,4 +1,5 @@
1
1
  <div align="center">
2
+ <h1>react-chess-puzzle</h1>
2
3
  A lightweight, customizable React component library for rendering and interacting with chess puzzles.
3
4
  </div>
4
5
 
@@ -26,50 +27,166 @@ To use the `react-chess-puzzle` package, you can import the `ChessPuzzle` compon
26
27
  import { ChessPuzzle } from "@react-chess-tools/react-chess-puzzle";
27
28
 
28
29
  const App = () => {
29
- <ChessPuzzle.Root puzzle={...}>
30
- <ChessPuzzle.Board />
31
- </ChessPuzzle.Root>;
30
+ const puzzle = {
31
+ fen: "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
32
+ moves: ["d2d4", "e5d4", "f3d4"],
33
+ makeFirstMove: false,
34
+ };
35
+
36
+ return (
37
+ <ChessPuzzle.Root puzzle={puzzle}>
38
+ <ChessPuzzle.Board />
39
+ <div className="controls">
40
+ <ChessPuzzle.Reset>Restart Puzzle</ChessPuzzle.Reset>
41
+ <ChessPuzzle.Hint showOn={["in-progress"]}>Get Hint</ChessPuzzle.Hint>
42
+ </div>
43
+ </ChessPuzzle.Root>
44
+ );
32
45
  };
33
46
  ```
34
47
 
48
+ ## Puzzle Solving Flow
49
+
50
+ The puzzle-solving flow follows these steps:
51
+
52
+ 1. **Initial Setup**: The board is set up using the provided FEN string
53
+ 2. **First Move**: If `makeFirstMove` is `true`, the component automatically makes the first move in the solution sequence
54
+ 3. **User Interaction**: The user attempts to solve the puzzle by making the correct moves
55
+ 4. **Feedback**: The component validates each move and provides feedback:
56
+ - If the move is correct, the puzzle continues
57
+ - If the move is incorrect, the puzzle is marked as failed
58
+ 5. **Completion**: When all correct moves have been made, the puzzle is marked as solved
59
+
35
60
  ## Documentation
36
61
 
37
62
  The `react-chess-puzzle` package provides a set of components that you can use to build your chess app. The following sections describe the components and their usage.
38
63
 
39
64
  ### ChessPuzzle.Root
40
65
 
41
- The `ChessPuzzle.Root` component is the root component of the `react-chess-puzzle` package. It is used to provide the `ChessPuzzleContext` to the rest of the components. It accept a `puzzle` prop that is used to instantiate the puzzle.
66
+ The `ChessPuzzle.Root` component is the root component of the `react-chess-puzzle` package. It is used to provide the `ChessPuzzleContext` to the rest of the components. It accepts a `puzzle` prop that is used to instantiate the puzzle.
42
67
 
43
68
  #### Props
44
69
 
45
70
  The `ChessPuzzle.Root` component accepts the following props:
46
71
 
47
- | Name | Type | Default | Description |
48
- | ----------- | ----------- | ------- | --------------------------- |
49
- | `puzzle` | `Puzzle` | | The puzzle to be solved |
50
- | `children?` | `ReactNode` | | The children to be rendered |
72
+ | Name | Type | Default | Description |
73
+ | ----------- | ------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------- |
74
+ | `puzzle` | `Puzzle` | | The puzzle to be solved |
75
+ | `onSolve` | `(puzzleContext: ChessPuzzleContextType) => void` | `undefined` | Callback function that is triggered when the puzzle is successfully solved, receives the puzzle context as parameter |
76
+ | `onFail` | `(puzzleContext: ChessPuzzleContextType) => void` | `undefined` | Callback function that is triggered when an incorrect move is played, receives the puzzle context as parameter |
77
+ | `children?` | `ReactNode` | | The children to be rendered |
51
78
 
52
- the `puzzle` prop contains the following properties:
79
+ The `puzzle` prop contains the following properties:
53
80
 
54
81
  | Name | Type | Default | Description |
55
82
  | --------------- | ---------- | ------- | -------------------------------------------------------------------------- |
56
- | `fen` | `string` | | The FEN string of the puzzle |
57
- | `moves` | `string[]` | | The moves of the puzzle |
83
+ | `fen` | `string` | | The FEN string representing the initial position of the puzzle |
84
+ | `moves` | `string[]` | | The sequence of moves (in algebraic notation) that solve the puzzle |
58
85
  | `makeFirstMove` | `boolean` | `false` | Whether the first move is part of the problem or must be played by the CPU |
59
86
 
60
87
  ### ChessPuzzle.Board
61
88
 
62
89
  The `ChessPuzzle.Board` component is used to render the chess board. It is a wrapper around the `ChessGame.Board` component and accepts the same props.
63
90
 
64
- ### Using react-chess-game Components
91
+ #### Props
92
+
93
+ Inherits all props from `ChessGame.Board` with these additional options:
94
+
95
+ | Name | Type | Default | Description |
96
+ | ---------------- | -------------------- | --------- | -------------------------------------------- |
97
+ | `showHighlights` | `boolean` | `true` | Show highlights for legal moves on the board |
98
+ | `orientation` | `"white" \| "black"` | `"white"` | Board orientation |
99
+
100
+ ### ChessPuzzle.Reset
101
+
102
+ A button component that resets the current puzzle or loads a new one.
103
+
104
+ #### Props
105
+
106
+ | Name | Type | Default | Description |
107
+ | --------- | --------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
108
+ | `puzzle` | `Puzzle \| undefined` | `undefined` | The puzzle object for a new puzzle. If not provided, the current puzzle is reset. |
109
+ | `onReset` | `() => void` | `undefined` | A callback function that is called when the puzzle is reset. |
110
+ | `showOn` | `PuzzleState[]` | `["not-started", "in-progress", "solved", "failed"]` | The state(s) in which the button is shown. Valid states are: "not-started", "in-progress", "solved", "failed" |
111
+ | `asChild` | `boolean` | `false` | Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node. |
65
112
 
66
- Since `react-chess-puzzle` is built on top of `react-chess-game`, you can use any of its components within your puzzle interface. This includes:
113
+ ### ChessPuzzle.Hint
67
114
 
68
- - `ChessGame.Sounds` - Add sound effects for moves/captures
69
- - `ChessGame.KeyboardControls` - Add keyboard navigation
70
- - `useChessGameContext` - Access game state and methods
115
+ A button component that provides a hint by highlighting the next move in the solution.
71
116
 
72
- Example usage with sounds and keyboard controls:
117
+ #### Props
118
+
119
+ | Name | Type | Default | Description |
120
+ | --------- | --------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
121
+ | `showOn` | `PuzzleState[]` | `["in-progress"]` | The state(s) in which the button is shown. Valid states are: "not-started", "in-progress", "solved", "failed" |
122
+ | `asChild` | `boolean` | `false` | Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node. |
123
+
124
+ ## Hooks and Context
125
+
126
+ ### useChessPuzzleContext
127
+
128
+ A hook that provides access to the puzzle state and methods.
129
+
130
+ ```tsx
131
+ import { useChessPuzzleContext } from "@react-chess-tools/react-chess-puzzle";
132
+
133
+ const MyComponent = () => {
134
+ const {
135
+ puzzleState, // "not-started" | "in-progress" | "solved" | "failed"
136
+ resetPuzzle, // Function to reset the current puzzle
137
+ showHint, // Function to show a hint
138
+ movesPlayed, // Number of moves played so far
139
+ totalMoves, // Total number of moves in the solution
140
+ } = useChessPuzzleContext();
141
+
142
+ return (
143
+ <div>
144
+ <p>Puzzle state: {puzzleState}</p>
145
+ <p>
146
+ Progress: {movesPlayed}/{totalMoves} moves
147
+ </p>
148
+ <button onClick={resetPuzzle}>Reset</button>
149
+ </div>
150
+ );
151
+ };
152
+ ```
153
+
154
+ ### useChessGameContext
155
+
156
+ Since `react-chess-puzzle` is built on top of `react-chess-game`, you can also use the `useChessGameContext` hook to access the underlying game state and methods.
157
+
158
+ ```tsx
159
+ import { useChessGameContext } from "@react-chess-tools/react-chess-game";
160
+
161
+ const MyComponent = () => {
162
+ const {
163
+ fen, // Current FEN string
164
+ gameState, // Game state (playing, checkmate, etc.)
165
+ turn, // Current turn ("w" | "b")
166
+ moves, // List of legal moves
167
+ makeMove, // Function to make a move
168
+ history, // Game move history
169
+ selectedSquare, // Currently selected square
170
+ setSelectedSquare, // Function to select a square
171
+ checkSquare, // Square with the king in check (if any)
172
+ } = useChessGameContext();
173
+
174
+ return (
175
+ <div>
176
+ <p>Current FEN: {fen}</p>
177
+ <p>Current turn: {turn === "w" ? "White" : "Black"}</p>
178
+ </div>
179
+ );
180
+ };
181
+ ```
182
+
183
+ ## Using react-chess-game Components
184
+
185
+ Since `react-chess-puzzle` is built on top of `react-chess-game`, you can use any of its components within your puzzle interface:
186
+
187
+ ### ChessGame.Sounds
188
+
189
+ Add sound effects for moves, captures, and other chess events.
73
190
 
74
191
  ```tsx
75
192
  import { ChessPuzzle } from "@react-chess-tools/react-chess-puzzle";
@@ -78,35 +195,132 @@ import { ChessGame } from "@react-chess-tools/react-chess-game";
78
195
  const App = () => (
79
196
  <ChessPuzzle.Root puzzle={...}>
80
197
  <ChessGame.Sounds />
81
- <ChessGame.KeyboardControls />
82
198
  <ChessPuzzle.Board />
83
199
  </ChessPuzzle.Root>
84
200
  );
85
201
  ```
86
202
 
87
- ### `Puzzle.Reset`
203
+ ### ChessGame.KeyboardControls
88
204
 
89
- A button that changes the puzzle. It can be used, for example, to restart the puzzle or move to the next puzzle.
205
+ Add keyboard navigation for accessible play.
90
206
 
91
- #### Props
207
+ ```tsx
208
+ import { ChessPuzzle } from "@react-chess-tools/react-chess-puzzle";
209
+ import { ChessGame } from "@react-chess-tools/react-chess-game";
92
210
 
93
- | Name | Type | Default | Description |
94
- | --------- | ---------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
95
- | `puzzle` | `Puzzle` | | The puzzle object containing the FEN string and moves sequence. If not provided, the current puzzle is reset. |
96
- | `onReset` | `() => void` | | A callback function that is called when the puzzle is reset. |
97
- | `showOn` | `"not-started" \| "in-progress" \| "solved" \| "failed"[]` | | The state(s) in which the button is shown. |
98
- | `asChild` | `boolean` | `false` | Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node. |
211
+ const App = () => (
212
+ <ChessPuzzle.Root puzzle={...}>
213
+ <ChessGame.KeyboardControls />
214
+ <ChessPuzzle.Board />
215
+ </ChessPuzzle.Root>
216
+ );
217
+ ```
99
218
 
100
- ### `Puzzle.Hint`
219
+ ## Complete Example
101
220
 
102
- A button that shows the next move of the puzzle.
221
+ Here's a complete example of a chess puzzle component with sounds, keyboard controls, and custom styling:
103
222
 
104
- #### Props
223
+ ```tsx
224
+ import { ChessPuzzle } from "@react-chess-tools/react-chess-puzzle";
225
+ import { ChessGame } from "@react-chess-tools/react-chess-game";
226
+ import { useState } from "react";
227
+ import "./ChessPuzzleStyles.css"; // Your custom CSS
228
+
229
+ export const PuzzleSolver = () => {
230
+ // Example puzzles
231
+ const puzzles = [
232
+ {
233
+ fen: "4kb1r/p2r1ppp/4qn2/1B2p1B1/4P3/1Q6/PPP2PPP/2KR4 w k - 0 1",
234
+ moves: ["Bxd7+", "Nxd7", "Qb8+", "Nxb8", "Rd8#"],
235
+ makeFirstMove: false,
236
+ },
237
+ {
238
+ fen: "6k1/5p1p/p1q1p1p1/1pB1P3/1Pr3Pn/P4P1P/4Q3/3R2K1 b - - 0 31",
239
+ moves: ["h4f3", "e2f3", "c4c5", "d1d8", "g8g7", "f3f6"],
240
+ makeFirstMove: true,
241
+ },
242
+ ];
243
+
244
+ const [currentPuzzle, setCurrentPuzzle] = useState(0);
245
+ const [score, setScore] = useState(0);
246
+
247
+ const nextPuzzle = () => {
248
+ const nextPuzzle = (currentPuzzle + 1) % puzzles.length;
249
+ setCurrentPuzzle(nextPuzzle);
250
+ };
251
+
252
+ const handleSolve = (puzzleContext: ChessPuzzleContextType) => {
253
+ setScore((prev) => prev + 10);
254
+ console.log(`Puzzle solved in ${puzzleContext.movesPlayed} moves`);
255
+ nextPuzzle();
256
+ };
257
+
258
+ const handleFail = (puzzleContext: ChessPuzzleContextType) => {
259
+ setScore((prev) => Math.max(0, prev - 5));
260
+ console.log(`Puzzle failed in ${puzzleContext.movesPlayed} moves`);
261
+
262
+ nextPuzzle();
263
+ };
264
+
265
+ const handleReset = (puzzleContext: ChessPuzzleContextType) => {
266
+ puzzleContext.changePuzzle(puzzles[currentPuzzle]!);
267
+ };
268
+
269
+ return (
270
+ <div className="puzzle-container">
271
+ <div className="score">Score: {score}</div>
272
+ <ChessPuzzle.Root
273
+ puzzle={puzzles[currentPuzzle]!}
274
+ onSolve={handleSolve}
275
+ onFail={handleFail}
276
+ >
277
+ <ChessGame.Sounds />
278
+ <ChessGame.KeyboardControls />
279
+
280
+ <div className="board-container">
281
+ <ChessPuzzle.Board />
282
+ </div>
283
+
284
+ <div className="controls">
285
+ <div className="buttons">
286
+ <ChessPuzzle.Reset>Restart</ChessPuzzle.Reset>
287
+ <ChessPuzzle.Hint showOn={["in-progress"]}>Hint</ChessPuzzle.Hint>
288
+ <ChessPuzzle.Reset
289
+ puzzle={puzzles[(currentPuzzle + 1) % puzzles.length]}
290
+ onReset={handleReset}
291
+ >
292
+ Next Puzzle
293
+ </ChessPuzzle.Reset>
294
+ </div>
295
+ </div>
296
+ </ChessPuzzle.Root>
297
+ </div>
298
+ );
299
+ };
105
300
 
106
- | Name | Type | Default | Description |
107
- | --------- | ---------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
108
- | `showOn` | `"not-started" \| "in-progress" \| "solved" \| "failed"[]` | | The state(s) in which the button is shown. |
109
- | `asChild` | `boolean` | `false` | Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node. |
301
+ // Custom component using the context
302
+ const PuzzleStatus = () => {
303
+ const { puzzleState, movesPlayed, totalMoves } = useChessPuzzleContext();
304
+
305
+ let message = "";
306
+ switch (puzzleState) {
307
+ case "not-started":
308
+ message = "Make your move to start the puzzle";
309
+ break;
310
+ case "in-progress":
311
+ message = `Progress: ${movesPlayed}/${totalMoves} moves`;
312
+ break;
313
+ case "solved":
314
+ message = "Puzzle solved! Well done!";
315
+ break;
316
+ case "failed":
317
+ message = "Incorrect move. Try again!";
318
+ break;
319
+ }
320
+
321
+ return <div className={`status ${puzzleState}`}>{message}</div>;
322
+ };
323
+ ```
110
324
 
111
325
  ## 📝 License
112
326
 
package/dist/index.d.mts CHANGED
@@ -18,17 +18,30 @@ interface HintProps {
18
18
  interface ResetProps {
19
19
  asChild?: boolean;
20
20
  puzzle?: Puzzle;
21
- onReset?: () => void;
21
+ onReset?: (puzzleContext: ChessPuzzleContextType) => void;
22
22
  showOn?: Status[];
23
23
  }
24
24
 
25
25
  interface PuzzleBoardProps extends React__default.ComponentProps<typeof ChessGame.Board> {
26
26
  }
27
27
 
28
+ type ChessPuzzleContextType = {
29
+ status: Status;
30
+ changePuzzle: (puzzle: Puzzle) => void;
31
+ puzzle: Puzzle;
32
+ hint: Hint;
33
+ nextMove?: string | null;
34
+ isPlayerTurn: boolean;
35
+ onHint: () => void;
36
+ puzzleState: Status;
37
+ movesPlayed: number;
38
+ totalMoves: number;
39
+ };
40
+
28
41
  interface RootProps {
29
42
  puzzle: Puzzle;
30
- onSolve?: (changePuzzle: (puzzle: Puzzle) => void) => void;
31
- onFail?: (changePuzzle: (puzzle: Puzzle) => void) => void;
43
+ onSolve?: (puzzleContext: ChessPuzzleContextType) => void;
44
+ onFail?: (puzzleContext: ChessPuzzleContextType) => void;
32
45
  }
33
46
 
34
47
  declare const ChessPuzzle: {
@@ -38,14 +51,6 @@ declare const ChessPuzzle: {
38
51
  Hint: React.FC<React.PropsWithChildren<HintProps>>;
39
52
  };
40
53
 
41
- declare const useChessPuzzleContext: () => {
42
- status: Status;
43
- changePuzzle: (puzzle: Puzzle) => void;
44
- puzzle: Puzzle;
45
- hint: Hint;
46
- onHint: () => void;
47
- nextMove: string | null | undefined;
48
- isPlayerTurn: boolean;
49
- };
54
+ declare const useChessPuzzleContext: () => ChessPuzzleContextType;
50
55
 
51
- export { ChessPuzzle, useChessPuzzleContext };
56
+ export { ChessPuzzle, type ChessPuzzleContextType, type Hint, type HintProps, type Puzzle, type PuzzleBoardProps, type ResetProps, type RootProps, type Status, useChessPuzzleContext };
package/dist/index.mjs CHANGED
@@ -68,7 +68,7 @@ var stringToMove = (game, move) => {
68
68
  };
69
69
 
70
70
  // src/hooks/useChessPuzzle.ts
71
- import { useEffect, useReducer } from "react";
71
+ import { useEffect, useReducer, useCallback, useMemo } from "react";
72
72
 
73
73
  // src/hooks/reducer.ts
74
74
  var initializePuzzle = ({ puzzle }) => {
@@ -80,7 +80,9 @@ var initializePuzzle = ({ puzzle }) => {
80
80
  hint: "none",
81
81
  cpuMove: null,
82
82
  needCpuMove: !!puzzle.makeFirstMove,
83
- isPlayerTurn: !puzzle.makeFirstMove
83
+ isPlayerTurn: !puzzle.makeFirstMove,
84
+ onSolveInvoked: false,
85
+ onFailInvoked: false
84
86
  };
85
87
  };
86
88
  var reducer = (state, action) => {
@@ -119,33 +121,29 @@ var reducer = (state, action) => {
119
121
  status: "in-progress"
120
122
  };
121
123
  case "PLAYER_MOVE": {
122
- const { move, onSolve, onFail, changePuzzle } = action.payload;
124
+ const { move } = action.payload;
123
125
  const isMoveRight = [move == null ? void 0 : move.san, move == null ? void 0 : move.lan].includes(
124
126
  (state == null ? void 0 : state.nextMove) || ""
125
127
  );
126
128
  const isPuzzleSolved = state.currentMoveIndex === state.puzzle.moves.length - 1;
127
129
  if (!isMoveRight) {
128
- if (onFail) {
129
- onFail(changePuzzle);
130
- }
131
130
  return {
132
131
  ...state,
133
132
  status: "failed",
134
133
  nextMove: null,
135
134
  hint: "none",
136
- isPlayerTurn: false
135
+ isPlayerTurn: false,
136
+ onFailInvoked: false
137
137
  };
138
138
  }
139
139
  if (isPuzzleSolved) {
140
- if (onSolve) {
141
- onSolve(changePuzzle);
142
- }
143
140
  return {
144
141
  ...state,
145
142
  status: "solved",
146
143
  nextMove: null,
147
144
  hint: "none",
148
- isPlayerTurn: false
145
+ isPlayerTurn: false,
146
+ onSolveInvoked: false
149
147
  };
150
148
  }
151
149
  return {
@@ -158,6 +156,16 @@ var reducer = (state, action) => {
158
156
  isPlayerTurn: false
159
157
  };
160
158
  }
159
+ case "MARK_SOLVE_INVOKED":
160
+ return {
161
+ ...state,
162
+ onSolveInvoked: true
163
+ };
164
+ case "MARK_FAIL_INVOKED":
165
+ return {
166
+ ...state,
167
+ onFailInvoked: true
168
+ };
161
169
  default:
162
170
  return state;
163
171
  }
@@ -173,15 +181,16 @@ var useChessPuzzle = (puzzle, onSolve, onFail) => {
173
181
  game,
174
182
  methods: { makeMove, setPosition }
175
183
  } = gameContext;
184
+ const changePuzzle = useCallback(
185
+ (puzzle2) => {
186
+ setPosition(puzzle2.fen, getOrientation(puzzle2));
187
+ dispatch({ type: "INITIALIZE", payload: { puzzle: puzzle2 } });
188
+ },
189
+ [setPosition]
190
+ );
176
191
  useEffect(() => {
177
- if (gameContext && game.fen() !== puzzle.fen) {
178
- setPosition(puzzle.fen, getOrientation(puzzle));
179
- }
180
- }, []);
181
- const changePuzzle = (puzzle2) => {
182
- dispatch({ type: "INITIALIZE", payload: { puzzle: puzzle2 } });
183
- setPosition(puzzle2.fen, getOrientation(puzzle2));
184
- };
192
+ changePuzzle(puzzle);
193
+ }, [JSON.stringify(puzzle), changePuzzle]);
185
194
  useEffect(() => {
186
195
  if (gameContext && game.fen() === puzzle.fen && state.needCpuMove) {
187
196
  setTimeout(
@@ -197,6 +206,36 @@ var useChessPuzzle = (puzzle, onSolve, onFail) => {
197
206
  makeMove(state.cpuMove);
198
207
  }
199
208
  }, [state.cpuMove]);
209
+ if (!gameContext) {
210
+ throw new Error("useChessPuzzle must be used within a ChessGameContext");
211
+ }
212
+ const onHint = useCallback(() => {
213
+ dispatch({ type: "TOGGLE_HINT" });
214
+ }, []);
215
+ const puzzleContext = useMemo(
216
+ () => ({
217
+ status: state.status,
218
+ changePuzzle,
219
+ puzzle,
220
+ hint: state.hint,
221
+ onHint,
222
+ nextMove: state.nextMove,
223
+ isPlayerTurn: state.isPlayerTurn,
224
+ puzzleState: state.status,
225
+ movesPlayed: state.currentMoveIndex,
226
+ totalMoves: puzzle.moves.length
227
+ }),
228
+ [
229
+ state.status,
230
+ changePuzzle,
231
+ puzzle,
232
+ state.hint,
233
+ onHint,
234
+ state.nextMove,
235
+ state.isPlayerTurn,
236
+ state.currentMoveIndex
237
+ ]
238
+ );
200
239
  useEffect(() => {
201
240
  var _a2, _b, _c;
202
241
  if (((_a2 = game == null ? void 0 : game.history()) == null ? void 0 : _a2.length) <= 0 + (puzzle.makeFirstMove ? 1 : 0)) {
@@ -207,9 +246,7 @@ var useChessPuzzle = (puzzle, onSolve, onFail) => {
207
246
  type: "PLAYER_MOVE",
208
247
  payload: {
209
248
  move: ((_c = (_b = gameContext == null ? void 0 : gameContext.game) == null ? void 0 : _b.history({ verbose: true })) == null ? void 0 : _c.pop()) ?? null,
210
- onSolve,
211
- onFail,
212
- changePuzzle,
249
+ puzzleContext,
213
250
  game
214
251
  }
215
252
  });
@@ -218,21 +255,19 @@ var useChessPuzzle = (puzzle, onSolve, onFail) => {
218
255
  });
219
256
  }
220
257
  }, [(_a = game == null ? void 0 : game.history()) == null ? void 0 : _a.length]);
221
- if (!gameContext) {
222
- throw new Error("useChessPuzzle must be used within a ChessGameContext");
223
- }
224
- const onHint = () => {
225
- dispatch({ type: "TOGGLE_HINT" });
226
- };
227
- return {
228
- status: state.status,
229
- changePuzzle,
230
- puzzle,
231
- hint: state.hint,
232
- onHint,
233
- nextMove: state.nextMove,
234
- isPlayerTurn: state.isPlayerTurn
235
- };
258
+ useEffect(() => {
259
+ if (state.status === "solved" && !state.onSolveInvoked && onSolve) {
260
+ onSolve(puzzleContext);
261
+ dispatch({ type: "MARK_SOLVE_INVOKED" });
262
+ }
263
+ }, [state.status, state.onSolveInvoked]);
264
+ useEffect(() => {
265
+ if (state.status === "failed" && !state.onFailInvoked && onFail) {
266
+ onFail(puzzleContext);
267
+ dispatch({ type: "MARK_FAIL_INVOKED" });
268
+ }
269
+ }, [state.status, state.onFailInvoked]);
270
+ return puzzleContext;
236
271
  };
237
272
 
238
273
  // src/components/ChessPuzzle/parts/Root.tsx
@@ -319,7 +354,7 @@ var Reset = ({
319
354
  const { changePuzzle, status } = puzzleContext;
320
355
  const handleClick = () => {
321
356
  changePuzzle(puzzle || puzzleContext.puzzle);
322
- onReset == null ? void 0 : onReset();
357
+ onReset == null ? void 0 : onReset(puzzleContext);
323
358
  };
324
359
  if (!showOn.includes(status)) {
325
360
  return null;