@react-chess-tools/react-chess-puzzle 0.3.1 → 0.5.0
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 +20 -0
- package/README.MD +258 -25
- package/dist/index.d.mts +13 -11
- package/dist/index.mjs +21 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/src/components/ChessPuzzle/ChessPuzzle.stories.tsx +27 -0
- package/src/components/ChessPuzzle/parts/Root.tsx +6 -3
- package/src/hooks/reducer.ts +8 -6
- package/src/hooks/useChessPuzzle.ts +35 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @react-chess-tools/react-chess-puzzle
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3060132: Pass the whole puzzle context to onSolve and onFail callbacks
|
|
8
|
+
|
|
9
|
+
## 0.4.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- a161f18: Add keyboard controls
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 5fc2ee1: fix: makeFirstMove on Puzzle Restart
|
|
18
|
+
- 706b5ec: Upgrade dependencies (June 2024)
|
|
19
|
+
- Updated dependencies [a161f18]
|
|
20
|
+
- Updated dependencies [706b5ec]
|
|
21
|
+
- @react-chess-tools/react-chess-game@0.4.0
|
|
22
|
+
|
|
3
23
|
## 0.3.1
|
|
4
24
|
|
|
5
25
|
### Patch 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,64 +27,296 @@ 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:
|
|
65
94
|
|
|
66
|
-
|
|
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.
|
|
67
103
|
|
|
68
104
|
#### Props
|
|
69
105
|
|
|
70
|
-
| Name | Type
|
|
71
|
-
| --------- |
|
|
72
|
-
| `puzzle` | `Puzzle`
|
|
73
|
-
| `onReset` | `() => void`
|
|
74
|
-
| `showOn` | `"not-started"
|
|
75
|
-
| `asChild` | `boolean`
|
|
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. |
|
|
76
112
|
|
|
77
|
-
###
|
|
113
|
+
### ChessPuzzle.Hint
|
|
78
114
|
|
|
79
|
-
A button that
|
|
115
|
+
A button component that provides a hint by highlighting the next move in the solution.
|
|
80
116
|
|
|
81
117
|
#### Props
|
|
82
118
|
|
|
83
|
-
| Name | Type
|
|
84
|
-
| --------- |
|
|
85
|
-
| `showOn` | `
|
|
86
|
-
| `asChild` | `boolean`
|
|
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.
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import { ChessPuzzle } from "@react-chess-tools/react-chess-puzzle";
|
|
193
|
+
import { ChessGame } from "@react-chess-tools/react-chess-game";
|
|
194
|
+
|
|
195
|
+
const App = () => (
|
|
196
|
+
<ChessPuzzle.Root puzzle={...}>
|
|
197
|
+
<ChessGame.Sounds />
|
|
198
|
+
<ChessPuzzle.Board />
|
|
199
|
+
</ChessPuzzle.Root>
|
|
200
|
+
);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### ChessGame.KeyboardControls
|
|
204
|
+
|
|
205
|
+
Add keyboard navigation for accessible play.
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import { ChessPuzzle } from "@react-chess-tools/react-chess-puzzle";
|
|
209
|
+
import { ChessGame } from "@react-chess-tools/react-chess-game";
|
|
210
|
+
|
|
211
|
+
const App = () => (
|
|
212
|
+
<ChessPuzzle.Root puzzle={...}>
|
|
213
|
+
<ChessGame.KeyboardControls />
|
|
214
|
+
<ChessPuzzle.Board />
|
|
215
|
+
</ChessPuzzle.Root>
|
|
216
|
+
);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Complete Example
|
|
220
|
+
|
|
221
|
+
Here's a complete example of a chess puzzle component with sounds, keyboard controls, and custom styling:
|
|
222
|
+
|
|
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: "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
|
|
234
|
+
moves: ["d2d4", "e5d4", "f3d4"],
|
|
235
|
+
makeFirstMove: false,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
fen: "r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4",
|
|
239
|
+
moves: ["f3g5", "d7d5", "e4d5", "c6a5"],
|
|
240
|
+
makeFirstMove: false,
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
const [currentPuzzle, setCurrentPuzzle] = useState(0);
|
|
245
|
+
const [score, setScore] = useState(0);
|
|
246
|
+
|
|
247
|
+
// Function to load the next puzzle
|
|
248
|
+
const nextPuzzle = () => {
|
|
249
|
+
setCurrentPuzzle((prev) => (prev + 1) % puzzles.length);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const handleSolve = (puzzleContext) => {
|
|
253
|
+
setScore((prev) => prev + 10);
|
|
254
|
+
console.log(`Puzzle solved in ${puzzleContext.movesPlayed} moves`);
|
|
255
|
+
// Could automatically progress to next puzzle
|
|
256
|
+
// setTimeout(nextPuzzle, 1500);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const handleFail = (puzzleContext) => {
|
|
260
|
+
setScore((prev) => Math.max(0, prev - 5));
|
|
261
|
+
console.log(`Failed move: ${puzzleContext.lastMove}`);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<div className="puzzle-container">
|
|
266
|
+
<div className="score">Score: {score}</div>
|
|
267
|
+
<ChessPuzzle.Root
|
|
268
|
+
puzzle={puzzles[currentPuzzle]}
|
|
269
|
+
onSolve={handleSolve}
|
|
270
|
+
onFail={handleFail}
|
|
271
|
+
>
|
|
272
|
+
<ChessGame.Sounds />
|
|
273
|
+
<ChessGame.KeyboardControls />
|
|
274
|
+
|
|
275
|
+
<div className="board-container">
|
|
276
|
+
<ChessPuzzle.Board />
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<div className="controls">
|
|
280
|
+
<PuzzleStatus />
|
|
281
|
+
<div className="buttons">
|
|
282
|
+
<ChessPuzzle.Reset>Restart</ChessPuzzle.Reset>
|
|
283
|
+
<ChessPuzzle.Hint showOn={["in-progress"]}>Hint</ChessPuzzle.Hint>
|
|
284
|
+
<ChessPuzzle.Reset
|
|
285
|
+
puzzle={puzzles[(currentPuzzle + 1) % puzzles.length]}
|
|
286
|
+
onReset={nextPuzzle}
|
|
287
|
+
>
|
|
288
|
+
Next Puzzle
|
|
289
|
+
</ChessPuzzle.Reset>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
</ChessPuzzle.Root>
|
|
293
|
+
</div>
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Custom component using the context
|
|
298
|
+
const PuzzleStatus = () => {
|
|
299
|
+
const { puzzleState, movesPlayed, totalMoves } = useChessPuzzleContext();
|
|
300
|
+
|
|
301
|
+
let message = "";
|
|
302
|
+
switch (puzzleState) {
|
|
303
|
+
case "not-started":
|
|
304
|
+
message = "Make your move to start the puzzle";
|
|
305
|
+
break;
|
|
306
|
+
case "in-progress":
|
|
307
|
+
message = `Progress: ${movesPlayed}/${totalMoves} moves`;
|
|
308
|
+
break;
|
|
309
|
+
case "solved":
|
|
310
|
+
message = "Puzzle solved! Well done!";
|
|
311
|
+
break;
|
|
312
|
+
case "failed":
|
|
313
|
+
message = "Incorrect move. Try again!";
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return <div className={`status ${puzzleState}`}>{message}</div>;
|
|
318
|
+
};
|
|
319
|
+
```
|
|
87
320
|
|
|
88
321
|
## 📝 License
|
|
89
322
|
|
package/dist/index.d.mts
CHANGED
|
@@ -25,10 +25,20 @@ interface ResetProps {
|
|
|
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
|
+
};
|
|
37
|
+
|
|
28
38
|
interface RootProps {
|
|
29
39
|
puzzle: Puzzle;
|
|
30
|
-
onSolve?: (
|
|
31
|
-
onFail?: (
|
|
40
|
+
onSolve?: (puzzleContext: ChessPuzzleContextType) => void;
|
|
41
|
+
onFail?: (puzzleContext: ChessPuzzleContextType) => void;
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
declare const ChessPuzzle: {
|
|
@@ -38,14 +48,6 @@ declare const ChessPuzzle: {
|
|
|
38
48
|
Hint: React.FC<React.PropsWithChildren<HintProps>>;
|
|
39
49
|
};
|
|
40
50
|
|
|
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
|
-
};
|
|
51
|
+
declare const useChessPuzzleContext: () => ChessPuzzleContextType;
|
|
50
52
|
|
|
51
53
|
export { ChessPuzzle, useChessPuzzleContext };
|
package/dist/index.mjs
CHANGED
|
@@ -78,6 +78,7 @@ var initializePuzzle = ({ puzzle }) => {
|
|
|
78
78
|
status: "not-started",
|
|
79
79
|
nextMove: puzzle.moves[0],
|
|
80
80
|
hint: "none",
|
|
81
|
+
cpuMove: null,
|
|
81
82
|
needCpuMove: !!puzzle.makeFirstMove,
|
|
82
83
|
isPlayerTurn: !puzzle.makeFirstMove
|
|
83
84
|
};
|
|
@@ -118,14 +119,14 @@ var reducer = (state, action) => {
|
|
|
118
119
|
status: "in-progress"
|
|
119
120
|
};
|
|
120
121
|
case "PLAYER_MOVE": {
|
|
121
|
-
const { move, onSolve, onFail,
|
|
122
|
+
const { move, onSolve, onFail, puzzleContext } = action.payload;
|
|
122
123
|
const isMoveRight = [move == null ? void 0 : move.san, move == null ? void 0 : move.lan].includes(
|
|
123
124
|
(state == null ? void 0 : state.nextMove) || ""
|
|
124
125
|
);
|
|
125
126
|
const isPuzzleSolved = state.currentMoveIndex === state.puzzle.moves.length - 1;
|
|
126
127
|
if (!isMoveRight) {
|
|
127
128
|
if (onFail) {
|
|
128
|
-
onFail(
|
|
129
|
+
onFail(puzzleContext);
|
|
129
130
|
}
|
|
130
131
|
return {
|
|
131
132
|
...state,
|
|
@@ -137,7 +138,7 @@ var reducer = (state, action) => {
|
|
|
137
138
|
}
|
|
138
139
|
if (isPuzzleSolved) {
|
|
139
140
|
if (onSolve) {
|
|
140
|
-
onSolve(
|
|
141
|
+
onSolve(puzzleContext);
|
|
141
142
|
}
|
|
142
143
|
return {
|
|
143
144
|
...state,
|
|
@@ -196,6 +197,21 @@ var useChessPuzzle = (puzzle, onSolve, onFail) => {
|
|
|
196
197
|
makeMove(state.cpuMove);
|
|
197
198
|
}
|
|
198
199
|
}, [state.cpuMove]);
|
|
200
|
+
if (!gameContext) {
|
|
201
|
+
throw new Error("useChessPuzzle must be used within a ChessGameContext");
|
|
202
|
+
}
|
|
203
|
+
const onHint = () => {
|
|
204
|
+
dispatch({ type: "TOGGLE_HINT" });
|
|
205
|
+
};
|
|
206
|
+
const puzzleContext = {
|
|
207
|
+
status: state.status,
|
|
208
|
+
changePuzzle,
|
|
209
|
+
puzzle,
|
|
210
|
+
hint: state.hint,
|
|
211
|
+
onHint,
|
|
212
|
+
nextMove: state.nextMove,
|
|
213
|
+
isPlayerTurn: state.isPlayerTurn
|
|
214
|
+
};
|
|
199
215
|
useEffect(() => {
|
|
200
216
|
var _a2, _b, _c;
|
|
201
217
|
if (((_a2 = game == null ? void 0 : game.history()) == null ? void 0 : _a2.length) <= 0 + (puzzle.makeFirstMove ? 1 : 0)) {
|
|
@@ -208,7 +224,7 @@ var useChessPuzzle = (puzzle, onSolve, onFail) => {
|
|
|
208
224
|
move: ((_c = (_b = gameContext == null ? void 0 : gameContext.game) == null ? void 0 : _b.history({ verbose: true })) == null ? void 0 : _c.pop()) ?? null,
|
|
209
225
|
onSolve,
|
|
210
226
|
onFail,
|
|
211
|
-
|
|
227
|
+
puzzleContext,
|
|
212
228
|
game
|
|
213
229
|
}
|
|
214
230
|
});
|
|
@@ -217,21 +233,7 @@ var useChessPuzzle = (puzzle, onSolve, onFail) => {
|
|
|
217
233
|
});
|
|
218
234
|
}
|
|
219
235
|
}, [(_a = game == null ? void 0 : game.history()) == null ? void 0 : _a.length]);
|
|
220
|
-
|
|
221
|
-
throw new Error("useChessPuzzle must be used within a ChessGameContext");
|
|
222
|
-
}
|
|
223
|
-
const onHint = () => {
|
|
224
|
-
dispatch({ type: "TOGGLE_HINT" });
|
|
225
|
-
};
|
|
226
|
-
return {
|
|
227
|
-
status: state.status,
|
|
228
|
-
changePuzzle,
|
|
229
|
-
puzzle,
|
|
230
|
-
hint: state.hint,
|
|
231
|
-
onHint,
|
|
232
|
-
nextMove: state.nextMove,
|
|
233
|
-
isPlayerTurn: state.isPlayerTurn
|
|
234
|
-
};
|
|
236
|
+
return puzzleContext;
|
|
235
237
|
};
|
|
236
238
|
|
|
237
239
|
// src/components/ChessPuzzle/parts/Root.tsx
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/ChessPuzzle/parts/Root.tsx","../src/utils/index.ts","../src/hooks/useChessPuzzle.ts","../src/hooks/reducer.ts","../src/hooks/useChessPuzzleContext.ts","../src/components/ChessPuzzle/parts/PuzzleBoard.tsx","../src/components/ChessPuzzle/parts/Reset.tsx","../src/components/ChessPuzzle/parts/Hint.tsx","../src/components/ChessPuzzle/index.ts"],"sourcesContent":["import React from \"react\";\nimport { Puzzle, getOrientation } from \"../../../utils\";\nimport { useChessPuzzle } from \"../../../hooks/useChessPuzzle\";\nimport { ChessGame } from \"@react-chess-tools/react-chess-game\";\nimport { ChessPuzzleContext } from \"../../../hooks/useChessPuzzleContext\";\n\nexport interface RootProps {\n puzzle: Puzzle;\n onSolve?: (changePuzzle: (puzzle: Puzzle) => void) => void;\n onFail?: (changePuzzle: (puzzle: Puzzle) => void) => void;\n}\n\nconst PuzzleRoot: React.FC<React.PropsWithChildren<RootProps>> = ({\n puzzle,\n onSolve,\n onFail,\n children,\n}) => {\n const context = useChessPuzzle(puzzle, onSolve, onFail);\n\n return (\n <ChessPuzzleContext.Provider value={context}>\n {children}\n </ChessPuzzleContext.Provider>\n );\n};\n\nexport const Root: React.FC<React.PropsWithChildren<RootProps>> = ({\n puzzle,\n onSolve,\n onFail,\n children,\n}) => {\n return (\n <ChessGame.Root fen={puzzle.fen} orientation={getOrientation(puzzle)}>\n <PuzzleRoot puzzle={puzzle} onSolve={onSolve} onFail={onFail}>\n {children}\n </PuzzleRoot>\n </ChessGame.Root>\n );\n};\n","import { type Color, Chess, Move } from \"chess.js\";\nimport React, { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport _ from \"lodash\";\n\nexport type Status = \"not-started\" | \"in-progress\" | \"solved\" | \"failed\";\n\nexport type Hint = \"none\" | \"piece\" | \"move\";\n\nexport type Puzzle = {\n fen: string;\n moves: string[];\n // if the first move of the puzzle has to be made by the cpu, as in chess.com puzzles\n makeFirstMove?: boolean;\n};\n\nconst FAIL_COLOR = \"rgba(201, 52, 48, 0.5)\";\nconst SUCCESS_COLOR = \"rgba(172, 206, 89, 0.5)\";\nconst HINT_COLOR = \"rgba(27, 172, 166, 0.5)\";\n\nexport const getOrientation = (puzzle: Puzzle): Color => {\n const fen = puzzle.fen;\n const game = new Chess(fen);\n if (puzzle.makeFirstMove) {\n game.move(puzzle.moves[0]);\n }\n return game.turn();\n};\n\ninterface ClickableElement extends ReactElement {\n props: {\n onClick?: () => void;\n };\n}\n\nexport const isClickableElement = (\n element: ReactNode,\n): element is ClickableElement => React.isValidElement(element);\n\nexport const getCustomSquareStyles = (\n status: Status,\n hint: Hint,\n isPlayerTurn: boolean,\n game: Chess,\n nextMove?: Move | null,\n) => {\n const customSquareStyles: Record<string, CSSProperties> = {};\n\n const lastMove = _.last(game.history({ verbose: true }));\n\n if (status === \"failed\" && lastMove) {\n customSquareStyles[lastMove.from] = {\n backgroundColor: FAIL_COLOR,\n };\n customSquareStyles[lastMove.to] = {\n backgroundColor: FAIL_COLOR,\n };\n }\n\n if (\n lastMove &&\n (status === \"solved\" || (status !== \"failed\" && !isPlayerTurn))\n ) {\n customSquareStyles[lastMove.from] = {\n backgroundColor: SUCCESS_COLOR,\n };\n customSquareStyles[lastMove.to] = {\n backgroundColor: SUCCESS_COLOR,\n };\n }\n\n if (hint === \"piece\") {\n if (nextMove) {\n customSquareStyles[nextMove.from] = {\n backgroundColor: HINT_COLOR,\n };\n }\n }\n\n if (hint === \"move\") {\n if (nextMove) {\n customSquareStyles[nextMove.from] = {\n backgroundColor: HINT_COLOR,\n };\n customSquareStyles[nextMove.to] = {\n backgroundColor: HINT_COLOR,\n };\n }\n }\n\n return customSquareStyles;\n};\n\nexport const stringToMove = (game: Chess, move: string | null | undefined) => {\n const copy = new Chess(game.fen());\n if (move === null || move === undefined) {\n return null;\n }\n try {\n return copy.move(move);\n } catch (e) {\n return null;\n }\n};\n","import { useEffect, useReducer } from \"react\";\nimport { initializePuzzle, reducer } from \"./reducer\";\nimport { getOrientation, type Puzzle } from \"../utils\";\nimport { useChessGameContext } from \"@react-chess-tools/react-chess-game\";\n\nexport const useChessPuzzle = (\n puzzle: Puzzle,\n onSolve?: (changePuzzle: (puzzle: Puzzle) => void) => void,\n onFail?: (changePuzzle: (puzzle: Puzzle) => void) => void,\n) => {\n const gameContext = useChessGameContext();\n\n const [state, dispatch] = useReducer(reducer, { puzzle }, initializePuzzle);\n\n const {\n game,\n methods: { makeMove, setPosition },\n } = gameContext;\n\n useEffect(() => {\n if (gameContext && game.fen() !== puzzle.fen) {\n setPosition(puzzle.fen, getOrientation(puzzle));\n }\n }, []);\n\n const changePuzzle = (puzzle: Puzzle) => {\n dispatch({ type: \"INITIALIZE\", payload: { puzzle } });\n setPosition(puzzle.fen, getOrientation(puzzle));\n };\n\n useEffect(() => {\n if (gameContext && game.fen() === puzzle.fen && state.needCpuMove) {\n setTimeout(\n () =>\n dispatch({\n type: \"CPU_MOVE\",\n }),\n 0,\n );\n }\n }, [gameContext, state.needCpuMove]);\n\n useEffect(() => {\n if (state.cpuMove) {\n makeMove(state.cpuMove);\n }\n }, [state.cpuMove]);\n\n useEffect(() => {\n if (game?.history()?.length <= 0 + (puzzle.makeFirstMove ? 1 : 0)) {\n return;\n }\n if (game.history().length % 2 === (puzzle.makeFirstMove ? 0 : 1)) {\n dispatch({\n type: \"PLAYER_MOVE\",\n payload: {\n move: gameContext?.game?.history({ verbose: true })?.pop() ?? null,\n onSolve,\n onFail,\n changePuzzle,\n game: game,\n },\n });\n\n dispatch({\n type: \"CPU_MOVE\",\n });\n }\n }, [game?.history()?.length]);\n\n if (!gameContext) {\n throw new Error(\"useChessPuzzle must be used within a ChessGameContext\");\n }\n\n const onHint = () => {\n dispatch({ type: \"TOGGLE_HINT\" });\n };\n\n return {\n status: state.status,\n changePuzzle,\n puzzle,\n hint: state.hint,\n onHint,\n nextMove: state.nextMove,\n isPlayerTurn: state.isPlayerTurn,\n };\n};\n","import { Chess, Move } from \"chess.js\";\nimport { type Puzzle, type Hint, type Status } from \"../utils\";\n\nexport type State = {\n puzzle: Puzzle;\n currentMoveIndex: number;\n status: Status;\n cpuMove?: string | null;\n nextMove?: string | null;\n hint: Hint;\n needCpuMove: boolean;\n isPlayerTurn: boolean;\n};\n\nexport type Action =\n | {\n type: \"INITIALIZE\";\n payload: {\n puzzle: Puzzle;\n };\n }\n | {\n type: \"RESET\";\n }\n | { type: \"TOGGLE_HINT\" }\n | {\n type: \"CPU_MOVE\";\n }\n | {\n type: \"PLAYER_MOVE\";\n payload: {\n move?: Move | null;\n onSolve?: (changePuzzle: (puzzle: Puzzle) => void) => void;\n onFail?: (changePuzzle: (puzzle: Puzzle) => void) => void;\n changePuzzle: (puzzle: Puzzle) => void;\n game: Chess;\n };\n };\n\nexport const initializePuzzle = ({ puzzle }: { puzzle: Puzzle }): State => {\n return {\n puzzle,\n currentMoveIndex: 0,\n status: \"not-started\",\n nextMove: puzzle.moves[0],\n hint: \"none\",\n needCpuMove: !!puzzle.makeFirstMove,\n isPlayerTurn: !puzzle.makeFirstMove,\n };\n};\n\nexport const reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"INITIALIZE\":\n return {\n ...state,\n ...initializePuzzle(action.payload),\n };\n case \"RESET\":\n return {\n ...state,\n ...initializePuzzle({\n puzzle: state.puzzle,\n }),\n };\n case \"TOGGLE_HINT\":\n if (state.hint === \"none\") {\n return { ...state, hint: \"piece\" };\n }\n return { ...state, hint: \"move\" };\n case \"CPU_MOVE\":\n if (state.isPlayerTurn) {\n return state;\n }\n if ([\"solved\", \"failed\"].includes(state.status)) {\n return state;\n }\n\n return {\n ...state,\n currentMoveIndex: state.currentMoveIndex + 1,\n cpuMove: state.puzzle.moves[state.currentMoveIndex],\n nextMove:\n state.currentMoveIndex < state.puzzle.moves.length - 1\n ? state.puzzle.moves[state.currentMoveIndex + 1]\n : null,\n needCpuMove: false,\n isPlayerTurn: true,\n status: \"in-progress\",\n };\n\n case \"PLAYER_MOVE\": {\n const { move, onSolve, onFail, changePuzzle } = action.payload;\n\n const isMoveRight = [move?.san, move?.lan].includes(\n state?.nextMove || \"\",\n );\n const isPuzzleSolved =\n state.currentMoveIndex === state.puzzle.moves.length - 1;\n\n if (!isMoveRight) {\n if (onFail) {\n onFail(changePuzzle);\n }\n return {\n ...state,\n status: \"failed\",\n nextMove: null,\n hint: \"none\",\n isPlayerTurn: false,\n };\n }\n\n if (isPuzzleSolved) {\n if (onSolve) {\n onSolve(changePuzzle);\n }\n\n return {\n ...state,\n status: \"solved\",\n nextMove: null,\n hint: \"none\",\n isPlayerTurn: false,\n };\n }\n\n return {\n ...state,\n hint: \"none\",\n currentMoveIndex: state.currentMoveIndex + 1,\n nextMove: state.puzzle.moves[state.currentMoveIndex + 1],\n status: \"in-progress\",\n needCpuMove: true,\n isPlayerTurn: false,\n };\n }\n\n default:\n return state;\n }\n};\n","import React from \"react\";\nimport { useChessPuzzle } from \"./useChessPuzzle\";\n\nexport const ChessPuzzleContext = React.createContext<ReturnType<\n typeof useChessPuzzle\n> | null>(null);\n\nexport const useChessPuzzleContext = () => {\n const context = React.useContext(ChessPuzzleContext);\n if (!context) {\n throw new Error(\n \"useChessGameContext must be used within a ChessGameProvider\",\n );\n }\n return context;\n};\n","import React from \"react\";\nimport {\n ChessGame,\n useChessGameContext,\n} from \"@react-chess-tools/react-chess-game\";\nimport { getCustomSquareStyles, stringToMove } from \"../../../utils\";\nimport { useChessPuzzleContext } from \"../../..\";\n\nexport interface PuzzleBoardProps\n extends React.ComponentProps<typeof ChessGame.Board> {}\nexport const PuzzleBoard: React.FC<PuzzleBoardProps> = ({ ...rest }) => {\n const puzzleContext = useChessPuzzleContext();\n const gameContext = useChessGameContext();\n\n if (!puzzleContext) {\n throw new Error(\"PuzzleContext not found\");\n }\n if (!gameContext) {\n throw new Error(\"ChessGameContext not found\");\n }\n\n const { game } = gameContext;\n const { status, hint, isPlayerTurn, nextMove } = puzzleContext;\n\n return (\n <ChessGame.Board\n customSquareStyles={getCustomSquareStyles(\n status,\n hint,\n isPlayerTurn,\n game,\n stringToMove(game, nextMove),\n )}\n {...rest}\n />\n );\n};\n","import React from \"react\";\nimport { isClickableElement, type Puzzle, type Status } from \"../../../utils\";\nimport { useChessPuzzleContext } from \"../../..\";\n\nexport interface ResetProps {\n asChild?: boolean;\n puzzle?: Puzzle;\n onReset?: () => void;\n showOn?: Status[];\n}\n\nconst defaultShowOn: Status[] = [\"failed\", \"solved\"];\n\nexport const Reset: React.FC<React.PropsWithChildren<ResetProps>> = ({\n children,\n asChild,\n puzzle,\n onReset,\n showOn = defaultShowOn,\n}) => {\n const puzzleContext = useChessPuzzleContext();\n if (!puzzleContext) {\n throw new Error(\"PuzzleContext not found\");\n }\n const { changePuzzle, status } = puzzleContext;\n const handleClick = () => {\n changePuzzle(puzzle || puzzleContext.puzzle);\n onReset?.();\n };\n\n if (!showOn.includes(status)) {\n return null;\n }\n\n if (asChild) {\n const child = React.Children.only(children);\n if (isClickableElement(child)) {\n return React.cloneElement(child, {\n onClick: handleClick,\n });\n } else {\n throw new Error(\"Change child must be a clickable element\");\n }\n }\n\n return (\n <button type=\"button\" onClick={handleClick}>\n {children}\n </button>\n );\n};\n","import React from \"react\";\nimport { Status, isClickableElement } from \"../../../utils\";\nimport { useChessPuzzleContext } from \"../../..\";\n\nexport interface HintProps {\n asChild?: boolean;\n showOn?: Status[];\n}\n\nconst defaultShowOn: Status[] = [\"not-started\", \"in-progress\"];\n\nexport const Hint: React.FC<React.PropsWithChildren<HintProps>> = ({\n children,\n asChild,\n showOn = defaultShowOn,\n}) => {\n const puzzleContext = useChessPuzzleContext();\n if (!puzzleContext) {\n throw new Error(\"PuzzleContext not found\");\n }\n const { onHint, status } = puzzleContext;\n const handleClick = () => {\n onHint();\n };\n\n if (!showOn.includes(status)) {\n return null;\n }\n\n if (asChild) {\n const child = React.Children.only(children);\n if (isClickableElement(child)) {\n return React.cloneElement(child, {\n onClick: handleClick,\n });\n } else {\n throw new Error(\"Change child must be a clickable element\");\n }\n }\n\n return (\n <button type=\"button\" onClick={handleClick}>\n {children}\n </button>\n );\n};\n","import { Root } from \"./parts/Root\";\nimport { PuzzleBoard } from \"./parts/PuzzleBoard\";\nimport { Reset } from \"./parts/Reset\";\nimport { Hint } from \"./parts/Hint\";\n\nexport const ChessPuzzle = {\n Root,\n Board: PuzzleBoard,\n Reset,\n Hint,\n};\n"],"mappings":"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","names":["React","puzzle","_a","React","React","React","ChessGame","useChessGameContext","useChessGameContext","React","ChessGame","React","React","React","defaultShowOn","React"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/ChessPuzzle/parts/Root.tsx","../src/utils/index.ts","../src/hooks/useChessPuzzle.ts","../src/hooks/reducer.ts","../src/hooks/useChessPuzzleContext.ts","../src/components/ChessPuzzle/parts/PuzzleBoard.tsx","../src/components/ChessPuzzle/parts/Reset.tsx","../src/components/ChessPuzzle/parts/Hint.tsx","../src/components/ChessPuzzle/index.ts"],"sourcesContent":["import React from \"react\";\nimport { Puzzle, getOrientation } from \"../../../utils\";\nimport {\n ChessPuzzleContextType,\n useChessPuzzle,\n} from \"../../../hooks/useChessPuzzle\";\nimport { ChessGame } from \"@react-chess-tools/react-chess-game\";\nimport { ChessPuzzleContext } from \"../../../hooks/useChessPuzzleContext\";\n\nexport interface RootProps {\n puzzle: Puzzle;\n onSolve?: (puzzleContext: ChessPuzzleContextType) => void;\n onFail?: (puzzleContext: ChessPuzzleContextType) => void;\n}\n\nconst PuzzleRoot: React.FC<React.PropsWithChildren<RootProps>> = ({\n puzzle,\n onSolve,\n onFail,\n children,\n}) => {\n const context = useChessPuzzle(puzzle, onSolve, onFail);\n\n return (\n <ChessPuzzleContext.Provider value={context}>\n {children}\n </ChessPuzzleContext.Provider>\n );\n};\n\nexport const Root: React.FC<React.PropsWithChildren<RootProps>> = ({\n puzzle,\n onSolve,\n onFail,\n children,\n}) => {\n return (\n <ChessGame.Root fen={puzzle.fen} orientation={getOrientation(puzzle)}>\n <PuzzleRoot puzzle={puzzle} onSolve={onSolve} onFail={onFail}>\n {children}\n </PuzzleRoot>\n </ChessGame.Root>\n );\n};\n","import { type Color, Chess, Move } from \"chess.js\";\nimport React, { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport _ from \"lodash\";\n\nexport type Status = \"not-started\" | \"in-progress\" | \"solved\" | \"failed\";\n\nexport type Hint = \"none\" | \"piece\" | \"move\";\n\nexport type Puzzle = {\n fen: string;\n moves: string[];\n // if the first move of the puzzle has to be made by the cpu, as in chess.com puzzles\n makeFirstMove?: boolean;\n};\n\nconst FAIL_COLOR = \"rgba(201, 52, 48, 0.5)\";\nconst SUCCESS_COLOR = \"rgba(172, 206, 89, 0.5)\";\nconst HINT_COLOR = \"rgba(27, 172, 166, 0.5)\";\n\nexport const getOrientation = (puzzle: Puzzle): Color => {\n const fen = puzzle.fen;\n const game = new Chess(fen);\n if (puzzle.makeFirstMove) {\n game.move(puzzle.moves[0]);\n }\n return game.turn();\n};\n\ninterface ClickableElement extends ReactElement {\n props: {\n onClick?: () => void;\n };\n}\n\nexport const isClickableElement = (\n element: ReactNode,\n): element is ClickableElement => React.isValidElement(element);\n\nexport const getCustomSquareStyles = (\n status: Status,\n hint: Hint,\n isPlayerTurn: boolean,\n game: Chess,\n nextMove?: Move | null,\n) => {\n const customSquareStyles: Record<string, CSSProperties> = {};\n\n const lastMove = _.last(game.history({ verbose: true }));\n\n if (status === \"failed\" && lastMove) {\n customSquareStyles[lastMove.from] = {\n backgroundColor: FAIL_COLOR,\n };\n customSquareStyles[lastMove.to] = {\n backgroundColor: FAIL_COLOR,\n };\n }\n\n if (\n lastMove &&\n (status === \"solved\" || (status !== \"failed\" && !isPlayerTurn))\n ) {\n customSquareStyles[lastMove.from] = {\n backgroundColor: SUCCESS_COLOR,\n };\n customSquareStyles[lastMove.to] = {\n backgroundColor: SUCCESS_COLOR,\n };\n }\n\n if (hint === \"piece\") {\n if (nextMove) {\n customSquareStyles[nextMove.from] = {\n backgroundColor: HINT_COLOR,\n };\n }\n }\n\n if (hint === \"move\") {\n if (nextMove) {\n customSquareStyles[nextMove.from] = {\n backgroundColor: HINT_COLOR,\n };\n customSquareStyles[nextMove.to] = {\n backgroundColor: HINT_COLOR,\n };\n }\n }\n\n return customSquareStyles;\n};\n\nexport const stringToMove = (game: Chess, move: string | null | undefined) => {\n const copy = new Chess(game.fen());\n if (move === null || move === undefined) {\n return null;\n }\n try {\n return copy.move(move);\n } catch (e) {\n return null;\n }\n};\n","import { useEffect, useReducer } from \"react\";\nimport { initializePuzzle, reducer } from \"./reducer\";\nimport { getOrientation, type Puzzle, type Hint, type Status } from \"../utils\";\nimport { useChessGameContext } from \"@react-chess-tools/react-chess-game\";\nimport { useChessPuzzleContext } from \"./useChessPuzzleContext\";\n\nexport type ChessPuzzleContextType = {\n status: Status;\n changePuzzle: (puzzle: Puzzle) => void;\n puzzle: Puzzle;\n hint: Hint;\n nextMove?: string | null;\n isPlayerTurn: boolean;\n onHint: () => void;\n};\n\nexport const useChessPuzzle = (\n puzzle: Puzzle,\n onSolve?: (puzzleContext: ChessPuzzleContextType) => void,\n onFail?: (puzzleContext: ChessPuzzleContextType) => void,\n): ChessPuzzleContextType => {\n const gameContext = useChessGameContext();\n\n const [state, dispatch] = useReducer(reducer, { puzzle }, initializePuzzle);\n\n const {\n game,\n methods: { makeMove, setPosition },\n } = gameContext;\n\n useEffect(() => {\n if (gameContext && game.fen() !== puzzle.fen) {\n setPosition(puzzle.fen, getOrientation(puzzle));\n }\n }, []);\n\n const changePuzzle = (puzzle: Puzzle) => {\n dispatch({ type: \"INITIALIZE\", payload: { puzzle } });\n setPosition(puzzle.fen, getOrientation(puzzle));\n };\n\n useEffect(() => {\n if (gameContext && game.fen() === puzzle.fen && state.needCpuMove) {\n setTimeout(\n () =>\n dispatch({\n type: \"CPU_MOVE\",\n }),\n 0,\n );\n }\n }, [gameContext, state.needCpuMove]);\n\n useEffect(() => {\n if (state.cpuMove) {\n makeMove(state.cpuMove);\n }\n }, [state.cpuMove]);\n\n if (!gameContext) {\n throw new Error(\"useChessPuzzle must be used within a ChessGameContext\");\n }\n\n const onHint = () => {\n dispatch({ type: \"TOGGLE_HINT\" });\n };\n\n const puzzleContext: ChessPuzzleContextType = {\n status: state.status,\n changePuzzle,\n puzzle,\n hint: state.hint,\n onHint,\n nextMove: state.nextMove,\n isPlayerTurn: state.isPlayerTurn,\n };\n\n useEffect(() => {\n if (game?.history()?.length <= 0 + (puzzle.makeFirstMove ? 1 : 0)) {\n return;\n }\n if (game.history().length % 2 === (puzzle.makeFirstMove ? 0 : 1)) {\n dispatch({\n type: \"PLAYER_MOVE\",\n payload: {\n move: gameContext?.game?.history({ verbose: true })?.pop() ?? null,\n onSolve,\n onFail,\n puzzleContext,\n game: game,\n },\n });\n\n dispatch({\n type: \"CPU_MOVE\",\n });\n }\n }, [game?.history()?.length]);\n\n return puzzleContext;\n};\n","import { Chess, Move } from \"chess.js\";\nimport { type Puzzle, type Hint, type Status } from \"../utils\";\nimport { ChessPuzzleContextType } from \"./useChessPuzzle\";\n\nexport type State = {\n puzzle: Puzzle;\n currentMoveIndex: number;\n status: Status;\n cpuMove?: string | null;\n nextMove?: string | null;\n hint: Hint;\n needCpuMove: boolean;\n isPlayerTurn: boolean;\n};\n\nexport type Action =\n | {\n type: \"INITIALIZE\";\n payload: {\n puzzle: Puzzle;\n };\n }\n | {\n type: \"RESET\";\n }\n | { type: \"TOGGLE_HINT\" }\n | {\n type: \"CPU_MOVE\";\n }\n | {\n type: \"PLAYER_MOVE\";\n payload: {\n move?: Move | null;\n onSolve?: (puzzleContext: ChessPuzzleContextType) => void;\n onFail?: (puzzleContext: ChessPuzzleContextType) => void;\n puzzleContext: ChessPuzzleContextType;\n game: Chess;\n };\n };\n\nexport const initializePuzzle = ({ puzzle }: { puzzle: Puzzle }): State => {\n return {\n puzzle,\n currentMoveIndex: 0,\n status: \"not-started\",\n nextMove: puzzle.moves[0],\n hint: \"none\",\n cpuMove: null,\n needCpuMove: !!puzzle.makeFirstMove,\n isPlayerTurn: !puzzle.makeFirstMove,\n };\n};\n\nexport const reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"INITIALIZE\":\n return {\n ...state,\n ...initializePuzzle(action.payload),\n };\n case \"RESET\":\n return {\n ...state,\n ...initializePuzzle({\n puzzle: state.puzzle,\n }),\n };\n case \"TOGGLE_HINT\":\n if (state.hint === \"none\") {\n return { ...state, hint: \"piece\" };\n }\n return { ...state, hint: \"move\" };\n case \"CPU_MOVE\":\n if (state.isPlayerTurn) {\n return state;\n }\n if ([\"solved\", \"failed\"].includes(state.status)) {\n return state;\n }\n\n return {\n ...state,\n currentMoveIndex: state.currentMoveIndex + 1,\n cpuMove: state.puzzle.moves[state.currentMoveIndex],\n nextMove:\n state.currentMoveIndex < state.puzzle.moves.length - 1\n ? state.puzzle.moves[state.currentMoveIndex + 1]\n : null,\n needCpuMove: false,\n isPlayerTurn: true,\n status: \"in-progress\",\n };\n\n case \"PLAYER_MOVE\": {\n const { move, onSolve, onFail, puzzleContext } = action.payload;\n\n const isMoveRight = [move?.san, move?.lan].includes(\n state?.nextMove || \"\",\n );\n const isPuzzleSolved =\n state.currentMoveIndex === state.puzzle.moves.length - 1;\n\n if (!isMoveRight) {\n if (onFail) {\n onFail(puzzleContext);\n }\n return {\n ...state,\n status: \"failed\",\n nextMove: null,\n hint: \"none\",\n isPlayerTurn: false,\n };\n }\n\n if (isPuzzleSolved) {\n if (onSolve) {\n onSolve(puzzleContext);\n }\n\n return {\n ...state,\n status: \"solved\",\n nextMove: null,\n hint: \"none\",\n isPlayerTurn: false,\n };\n }\n\n return {\n ...state,\n hint: \"none\",\n currentMoveIndex: state.currentMoveIndex + 1,\n nextMove: state.puzzle.moves[state.currentMoveIndex + 1],\n status: \"in-progress\",\n needCpuMove: true,\n isPlayerTurn: false,\n };\n }\n\n default:\n return state;\n }\n};\n","import React from \"react\";\nimport { useChessPuzzle } from \"./useChessPuzzle\";\n\nexport const ChessPuzzleContext = React.createContext<ReturnType<\n typeof useChessPuzzle\n> | null>(null);\n\nexport const useChessPuzzleContext = () => {\n const context = React.useContext(ChessPuzzleContext);\n if (!context) {\n throw new Error(\n \"useChessGameContext must be used within a ChessGameProvider\",\n );\n }\n return context;\n};\n","import React from \"react\";\nimport {\n ChessGame,\n useChessGameContext,\n} from \"@react-chess-tools/react-chess-game\";\nimport { getCustomSquareStyles, stringToMove } from \"../../../utils\";\nimport { useChessPuzzleContext } from \"../../..\";\n\nexport interface PuzzleBoardProps\n extends React.ComponentProps<typeof ChessGame.Board> {}\nexport const PuzzleBoard: React.FC<PuzzleBoardProps> = ({ ...rest }) => {\n const puzzleContext = useChessPuzzleContext();\n const gameContext = useChessGameContext();\n\n if (!puzzleContext) {\n throw new Error(\"PuzzleContext not found\");\n }\n if (!gameContext) {\n throw new Error(\"ChessGameContext not found\");\n }\n\n const { game } = gameContext;\n const { status, hint, isPlayerTurn, nextMove } = puzzleContext;\n\n return (\n <ChessGame.Board\n customSquareStyles={getCustomSquareStyles(\n status,\n hint,\n isPlayerTurn,\n game,\n stringToMove(game, nextMove),\n )}\n {...rest}\n />\n );\n};\n","import React from \"react\";\nimport { isClickableElement, type Puzzle, type Status } from \"../../../utils\";\nimport { useChessPuzzleContext } from \"../../..\";\n\nexport interface ResetProps {\n asChild?: boolean;\n puzzle?: Puzzle;\n onReset?: () => void;\n showOn?: Status[];\n}\n\nconst defaultShowOn: Status[] = [\"failed\", \"solved\"];\n\nexport const Reset: React.FC<React.PropsWithChildren<ResetProps>> = ({\n children,\n asChild,\n puzzle,\n onReset,\n showOn = defaultShowOn,\n}) => {\n const puzzleContext = useChessPuzzleContext();\n if (!puzzleContext) {\n throw new Error(\"PuzzleContext not found\");\n }\n const { changePuzzle, status } = puzzleContext;\n const handleClick = () => {\n changePuzzle(puzzle || puzzleContext.puzzle);\n onReset?.();\n };\n\n if (!showOn.includes(status)) {\n return null;\n }\n\n if (asChild) {\n const child = React.Children.only(children);\n if (isClickableElement(child)) {\n return React.cloneElement(child, {\n onClick: handleClick,\n });\n } else {\n throw new Error(\"Change child must be a clickable element\");\n }\n }\n\n return (\n <button type=\"button\" onClick={handleClick}>\n {children}\n </button>\n );\n};\n","import React from \"react\";\nimport { Status, isClickableElement } from \"../../../utils\";\nimport { useChessPuzzleContext } from \"../../..\";\n\nexport interface HintProps {\n asChild?: boolean;\n showOn?: Status[];\n}\n\nconst defaultShowOn: Status[] = [\"not-started\", \"in-progress\"];\n\nexport const Hint: React.FC<React.PropsWithChildren<HintProps>> = ({\n children,\n asChild,\n showOn = defaultShowOn,\n}) => {\n const puzzleContext = useChessPuzzleContext();\n if (!puzzleContext) {\n throw new Error(\"PuzzleContext not found\");\n }\n const { onHint, status } = puzzleContext;\n const handleClick = () => {\n onHint();\n };\n\n if (!showOn.includes(status)) {\n return null;\n }\n\n if (asChild) {\n const child = React.Children.only(children);\n if (isClickableElement(child)) {\n return React.cloneElement(child, {\n onClick: handleClick,\n });\n } else {\n throw new Error(\"Change child must be a clickable element\");\n }\n }\n\n return (\n <button type=\"button\" onClick={handleClick}>\n {children}\n </button>\n );\n};\n","import { Root } from \"./parts/Root\";\nimport { PuzzleBoard } from \"./parts/PuzzleBoard\";\nimport { Reset } from \"./parts/Reset\";\nimport { Hint } from \"./parts/Hint\";\n\nexport const ChessPuzzle = {\n Root,\n Board: PuzzleBoard,\n Reset,\n Hint,\n};\n"],"mappings":"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","names":["React","puzzle","_a","React","React","React","ChessGame","useChessGameContext","useChessGameContext","React","ChessGame","React","React","React","defaultShowOn","React"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-chess-tools/react-chess-puzzle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A lightweight, customizable React component library for rendering and interacting with chess puzzles.",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -32,11 +32,12 @@
|
|
|
32
32
|
"author": "Daniele Cammareri <daniele.cammareri@gmail.com>",
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@react-chess-tools/react-chess-game": "0.
|
|
35
|
+
"@react-chess-tools/react-chess-game": "0.4.0",
|
|
36
36
|
"chess.js": "^1.0.0-beta.8",
|
|
37
37
|
"lodash": "^4.17.21"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
+
"@types/lodash": "^4.17.15",
|
|
40
41
|
"react": "^18.3.1",
|
|
41
42
|
"react-dom": "^18.3.1"
|
|
42
43
|
},
|
|
@@ -3,6 +3,7 @@ import type { Meta } from "@storybook/react";
|
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { RootProps } from "./parts/Root";
|
|
5
5
|
import { ChessPuzzle } from ".";
|
|
6
|
+
import { ChessGame } from "@react-chess-tools/react-chess-game";
|
|
6
7
|
|
|
7
8
|
const puzzles = [
|
|
8
9
|
{
|
|
@@ -83,3 +84,29 @@ export const Underpromotion = (args: RootProps) => {
|
|
|
83
84
|
</div>
|
|
84
85
|
);
|
|
85
86
|
};
|
|
87
|
+
|
|
88
|
+
export const WithSounds = (args: RootProps) => {
|
|
89
|
+
return (
|
|
90
|
+
<ChessPuzzle.Root {...args} puzzle={puzzles[0]}>
|
|
91
|
+
<ChessGame.Sounds />
|
|
92
|
+
<ChessPuzzle.Board />
|
|
93
|
+
</ChessPuzzle.Root>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const WithKeyboardControls = (args: RootProps) => {
|
|
98
|
+
return (
|
|
99
|
+
<ChessPuzzle.Root {...args} puzzle={puzzles[0]}>
|
|
100
|
+
<ChessGame.KeyboardControls
|
|
101
|
+
controls={{
|
|
102
|
+
f: (context) => context.methods.flipBoard(),
|
|
103
|
+
w: (context) => context.methods.goToStart(),
|
|
104
|
+
s: (context) => context.methods.goToEnd(),
|
|
105
|
+
a: (context) => context.methods.goToPreviousMove(),
|
|
106
|
+
d: (context) => context.methods.goToNextMove(),
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
<ChessPuzzle.Board />
|
|
110
|
+
</ChessPuzzle.Root>
|
|
111
|
+
);
|
|
112
|
+
};
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Puzzle, getOrientation } from "../../../utils";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ChessPuzzleContextType,
|
|
5
|
+
useChessPuzzle,
|
|
6
|
+
} from "../../../hooks/useChessPuzzle";
|
|
4
7
|
import { ChessGame } from "@react-chess-tools/react-chess-game";
|
|
5
8
|
import { ChessPuzzleContext } from "../../../hooks/useChessPuzzleContext";
|
|
6
9
|
|
|
7
10
|
export interface RootProps {
|
|
8
11
|
puzzle: Puzzle;
|
|
9
|
-
onSolve?: (
|
|
10
|
-
onFail?: (
|
|
12
|
+
onSolve?: (puzzleContext: ChessPuzzleContextType) => void;
|
|
13
|
+
onFail?: (puzzleContext: ChessPuzzleContextType) => void;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
const PuzzleRoot: React.FC<React.PropsWithChildren<RootProps>> = ({
|
package/src/hooks/reducer.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Chess, Move } from "chess.js";
|
|
2
2
|
import { type Puzzle, type Hint, type Status } from "../utils";
|
|
3
|
+
import { ChessPuzzleContextType } from "./useChessPuzzle";
|
|
3
4
|
|
|
4
5
|
export type State = {
|
|
5
6
|
puzzle: Puzzle;
|
|
@@ -30,9 +31,9 @@ export type Action =
|
|
|
30
31
|
type: "PLAYER_MOVE";
|
|
31
32
|
payload: {
|
|
32
33
|
move?: Move | null;
|
|
33
|
-
onSolve?: (
|
|
34
|
-
onFail?: (
|
|
35
|
-
|
|
34
|
+
onSolve?: (puzzleContext: ChessPuzzleContextType) => void;
|
|
35
|
+
onFail?: (puzzleContext: ChessPuzzleContextType) => void;
|
|
36
|
+
puzzleContext: ChessPuzzleContextType;
|
|
36
37
|
game: Chess;
|
|
37
38
|
};
|
|
38
39
|
};
|
|
@@ -44,6 +45,7 @@ export const initializePuzzle = ({ puzzle }: { puzzle: Puzzle }): State => {
|
|
|
44
45
|
status: "not-started",
|
|
45
46
|
nextMove: puzzle.moves[0],
|
|
46
47
|
hint: "none",
|
|
48
|
+
cpuMove: null,
|
|
47
49
|
needCpuMove: !!puzzle.makeFirstMove,
|
|
48
50
|
isPlayerTurn: !puzzle.makeFirstMove,
|
|
49
51
|
};
|
|
@@ -90,7 +92,7 @@ export const reducer = (state: State, action: Action): State => {
|
|
|
90
92
|
};
|
|
91
93
|
|
|
92
94
|
case "PLAYER_MOVE": {
|
|
93
|
-
const { move, onSolve, onFail,
|
|
95
|
+
const { move, onSolve, onFail, puzzleContext } = action.payload;
|
|
94
96
|
|
|
95
97
|
const isMoveRight = [move?.san, move?.lan].includes(
|
|
96
98
|
state?.nextMove || "",
|
|
@@ -100,7 +102,7 @@ export const reducer = (state: State, action: Action): State => {
|
|
|
100
102
|
|
|
101
103
|
if (!isMoveRight) {
|
|
102
104
|
if (onFail) {
|
|
103
|
-
onFail(
|
|
105
|
+
onFail(puzzleContext);
|
|
104
106
|
}
|
|
105
107
|
return {
|
|
106
108
|
...state,
|
|
@@ -113,7 +115,7 @@ export const reducer = (state: State, action: Action): State => {
|
|
|
113
115
|
|
|
114
116
|
if (isPuzzleSolved) {
|
|
115
117
|
if (onSolve) {
|
|
116
|
-
onSolve(
|
|
118
|
+
onSolve(puzzleContext);
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
return {
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { useEffect, useReducer } from "react";
|
|
2
2
|
import { initializePuzzle, reducer } from "./reducer";
|
|
3
|
-
import { getOrientation, type Puzzle } from "../utils";
|
|
3
|
+
import { getOrientation, type Puzzle, type Hint, type Status } from "../utils";
|
|
4
4
|
import { useChessGameContext } from "@react-chess-tools/react-chess-game";
|
|
5
|
+
import { useChessPuzzleContext } from "./useChessPuzzleContext";
|
|
6
|
+
|
|
7
|
+
export type ChessPuzzleContextType = {
|
|
8
|
+
status: Status;
|
|
9
|
+
changePuzzle: (puzzle: Puzzle) => void;
|
|
10
|
+
puzzle: Puzzle;
|
|
11
|
+
hint: Hint;
|
|
12
|
+
nextMove?: string | null;
|
|
13
|
+
isPlayerTurn: boolean;
|
|
14
|
+
onHint: () => void;
|
|
15
|
+
};
|
|
5
16
|
|
|
6
17
|
export const useChessPuzzle = (
|
|
7
18
|
puzzle: Puzzle,
|
|
8
|
-
onSolve?: (
|
|
9
|
-
onFail?: (
|
|
10
|
-
) => {
|
|
19
|
+
onSolve?: (puzzleContext: ChessPuzzleContextType) => void,
|
|
20
|
+
onFail?: (puzzleContext: ChessPuzzleContextType) => void,
|
|
21
|
+
): ChessPuzzleContextType => {
|
|
11
22
|
const gameContext = useChessGameContext();
|
|
12
23
|
|
|
13
24
|
const [state, dispatch] = useReducer(reducer, { puzzle }, initializePuzzle);
|
|
@@ -46,6 +57,24 @@ export const useChessPuzzle = (
|
|
|
46
57
|
}
|
|
47
58
|
}, [state.cpuMove]);
|
|
48
59
|
|
|
60
|
+
if (!gameContext) {
|
|
61
|
+
throw new Error("useChessPuzzle must be used within a ChessGameContext");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const onHint = () => {
|
|
65
|
+
dispatch({ type: "TOGGLE_HINT" });
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const puzzleContext: ChessPuzzleContextType = {
|
|
69
|
+
status: state.status,
|
|
70
|
+
changePuzzle,
|
|
71
|
+
puzzle,
|
|
72
|
+
hint: state.hint,
|
|
73
|
+
onHint,
|
|
74
|
+
nextMove: state.nextMove,
|
|
75
|
+
isPlayerTurn: state.isPlayerTurn,
|
|
76
|
+
};
|
|
77
|
+
|
|
49
78
|
useEffect(() => {
|
|
50
79
|
if (game?.history()?.length <= 0 + (puzzle.makeFirstMove ? 1 : 0)) {
|
|
51
80
|
return;
|
|
@@ -57,7 +86,7 @@ export const useChessPuzzle = (
|
|
|
57
86
|
move: gameContext?.game?.history({ verbose: true })?.pop() ?? null,
|
|
58
87
|
onSolve,
|
|
59
88
|
onFail,
|
|
60
|
-
|
|
89
|
+
puzzleContext,
|
|
61
90
|
game: game,
|
|
62
91
|
},
|
|
63
92
|
});
|
|
@@ -68,21 +97,5 @@ export const useChessPuzzle = (
|
|
|
68
97
|
}
|
|
69
98
|
}, [game?.history()?.length]);
|
|
70
99
|
|
|
71
|
-
|
|
72
|
-
throw new Error("useChessPuzzle must be used within a ChessGameContext");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const onHint = () => {
|
|
76
|
-
dispatch({ type: "TOGGLE_HINT" });
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
status: state.status,
|
|
81
|
-
changePuzzle,
|
|
82
|
-
puzzle,
|
|
83
|
-
hint: state.hint,
|
|
84
|
-
onHint,
|
|
85
|
-
nextMove: state.nextMove,
|
|
86
|
-
isPlayerTurn: state.isPlayerTurn,
|
|
87
|
-
};
|
|
100
|
+
return puzzleContext;
|
|
88
101
|
};
|