@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 +21 -0
- package/README.MD +248 -34
- package/dist/index.d.mts +18 -13
- package/dist/index.mjs +73 -38
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/ChessPuzzle/parts/Reset.tsx +3 -3
- package/src/components/ChessPuzzle/parts/Root.tsx +6 -3
- package/src/hooks/__tests__/reducer.test.ts +274 -0
- package/src/hooks/reducer.ts +24 -12
- package/src/hooks/useChessPuzzle.ts +75 -32
- package/src/index.ts +13 -0
- package/src/utils/__tests__/index.test.ts +192 -0
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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
|
|
48
|
-
| ----------- |
|
|
49
|
-
| `puzzle` | `Puzzle`
|
|
50
|
-
| `
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
### ChessPuzzle.Hint
|
|
67
114
|
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
203
|
+
### ChessGame.KeyboardControls
|
|
88
204
|
|
|
89
|
-
|
|
205
|
+
Add keyboard navigation for accessible play.
|
|
90
206
|
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
211
|
+
const App = () => (
|
|
212
|
+
<ChessPuzzle.Root puzzle={...}>
|
|
213
|
+
<ChessGame.KeyboardControls />
|
|
214
|
+
<ChessPuzzle.Board />
|
|
215
|
+
</ChessPuzzle.Root>
|
|
216
|
+
);
|
|
217
|
+
```
|
|
99
218
|
|
|
100
|
-
|
|
219
|
+
## Complete Example
|
|
101
220
|
|
|
102
|
-
|
|
221
|
+
Here's a complete example of a chess puzzle component with sounds, keyboard controls, and custom styling:
|
|
103
222
|
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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?: (
|
|
31
|
-
onFail?: (
|
|
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
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
status
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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;
|