@react-chess-tools/react-chess-puzzle 0.4.0 → 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 +6 -0
- package/README.MD +244 -34
- package/dist/index.d.mts +13 -11
- package/dist/index.mjs +20 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ChessPuzzle/parts/Root.tsx +6 -3
- package/src/hooks/reducer.ts +7 -6
- package/src/hooks/useChessPuzzle.ts +35 -22
package/CHANGELOG.md
CHANGED
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,128 @@ 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: "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
|
+
};
|
|
105
296
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
+
```
|
|
110
320
|
|
|
111
321
|
## 📝 License
|
|
112
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
|
@@ -119,14 +119,14 @@ var reducer = (state, action) => {
|
|
|
119
119
|
status: "in-progress"
|
|
120
120
|
};
|
|
121
121
|
case "PLAYER_MOVE": {
|
|
122
|
-
const { move, onSolve, onFail,
|
|
122
|
+
const { move, onSolve, onFail, puzzleContext } = action.payload;
|
|
123
123
|
const isMoveRight = [move == null ? void 0 : move.san, move == null ? void 0 : move.lan].includes(
|
|
124
124
|
(state == null ? void 0 : state.nextMove) || ""
|
|
125
125
|
);
|
|
126
126
|
const isPuzzleSolved = state.currentMoveIndex === state.puzzle.moves.length - 1;
|
|
127
127
|
if (!isMoveRight) {
|
|
128
128
|
if (onFail) {
|
|
129
|
-
onFail(
|
|
129
|
+
onFail(puzzleContext);
|
|
130
130
|
}
|
|
131
131
|
return {
|
|
132
132
|
...state,
|
|
@@ -138,7 +138,7 @@ var reducer = (state, action) => {
|
|
|
138
138
|
}
|
|
139
139
|
if (isPuzzleSolved) {
|
|
140
140
|
if (onSolve) {
|
|
141
|
-
onSolve(
|
|
141
|
+
onSolve(puzzleContext);
|
|
142
142
|
}
|
|
143
143
|
return {
|
|
144
144
|
...state,
|
|
@@ -197,6 +197,21 @@ var useChessPuzzle = (puzzle, onSolve, onFail) => {
|
|
|
197
197
|
makeMove(state.cpuMove);
|
|
198
198
|
}
|
|
199
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
|
+
};
|
|
200
215
|
useEffect(() => {
|
|
201
216
|
var _a2, _b, _c;
|
|
202
217
|
if (((_a2 = game == null ? void 0 : game.history()) == null ? void 0 : _a2.length) <= 0 + (puzzle.makeFirstMove ? 1 : 0)) {
|
|
@@ -209,7 +224,7 @@ var useChessPuzzle = (puzzle, onSolve, onFail) => {
|
|
|
209
224
|
move: ((_c = (_b = gameContext == null ? void 0 : gameContext.game) == null ? void 0 : _b.history({ verbose: true })) == null ? void 0 : _c.pop()) ?? null,
|
|
210
225
|
onSolve,
|
|
211
226
|
onFail,
|
|
212
|
-
|
|
227
|
+
puzzleContext,
|
|
213
228
|
game
|
|
214
229
|
}
|
|
215
230
|
});
|
|
@@ -218,21 +233,7 @@ var useChessPuzzle = (puzzle, onSolve, onFail) => {
|
|
|
218
233
|
});
|
|
219
234
|
}
|
|
220
235
|
}, [(_a = game == null ? void 0 : game.history()) == null ? void 0 : _a.length]);
|
|
221
|
-
|
|
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
|
-
};
|
|
236
|
+
return puzzleContext;
|
|
236
237
|
};
|
|
237
238
|
|
|
238
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 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, 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":";AAAA,OAAOA,YAAW;;;ACAlB,SAAqB,aAAmB;AACxC,OAAO,WAAuD;AAC9D,OAAO,OAAO;AAad,IAAM,aAAa;AACnB,IAAM,gBAAgB;AACtB,IAAM,aAAa;AAEZ,IAAM,iBAAiB,CAAC,WAA0B;AACvD,QAAM,MAAM,OAAO;AACnB,QAAM,OAAO,IAAI,MAAM,GAAG;AAC1B,MAAI,OAAO,eAAe;AACxB,SAAK,KAAK,OAAO,MAAM,CAAC,CAAC;AAAA,EAC3B;AACA,SAAO,KAAK,KAAK;AACnB;AAQO,IAAM,qBAAqB,CAChC,YACgC,MAAM,eAAe,OAAO;AAEvD,IAAM,wBAAwB,CACnC,QACA,MACA,cACA,MACA,aACG;AACH,QAAM,qBAAoD,CAAC;AAE3D,QAAM,WAAW,EAAE,KAAK,KAAK,QAAQ,EAAE,SAAS,KAAK,CAAC,CAAC;AAEvD,MAAI,WAAW,YAAY,UAAU;AACnC,uBAAmB,SAAS,IAAI,IAAI;AAAA,MAClC,iBAAiB;AAAA,IACnB;AACA,uBAAmB,SAAS,EAAE,IAAI;AAAA,MAChC,iBAAiB;AAAA,IACnB;AAAA,EACF;AAEA,MACE,aACC,WAAW,YAAa,WAAW,YAAY,CAAC,eACjD;AACA,uBAAmB,SAAS,IAAI,IAAI;AAAA,MAClC,iBAAiB;AAAA,IACnB;AACA,uBAAmB,SAAS,EAAE,IAAI;AAAA,MAChC,iBAAiB;AAAA,IACnB;AAAA,EACF;AAEA,MAAI,SAAS,SAAS;AACpB,QAAI,UAAU;AACZ,yBAAmB,SAAS,IAAI,IAAI;AAAA,QAClC,iBAAiB;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,QAAQ;AACnB,QAAI,UAAU;AACZ,yBAAmB,SAAS,IAAI,IAAI;AAAA,QAClC,iBAAiB;AAAA,MACnB;AACA,yBAAmB,SAAS,EAAE,IAAI;AAAA,QAChC,iBAAiB;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,IAAM,eAAe,CAAC,MAAa,SAAoC;AAC5E,QAAM,OAAO,IAAI,MAAM,KAAK,IAAI,CAAC;AACjC,MAAI,SAAS,QAAQ,SAAS,QAAW;AACvC,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,KAAK,KAAK,IAAI;AAAA,EACvB,SAAS,GAAG;AACV,WAAO;AAAA,EACT;AACF;;;ACtGA,SAAS,WAAW,kBAAkB;;;ACuC/B,IAAM,mBAAmB,CAAC,EAAE,OAAO,MAAiC;AACzE,SAAO;AAAA,IACL;AAAA,IACA,kBAAkB;AAAA,IAClB,QAAQ;AAAA,IACR,UAAU,OAAO,MAAM,CAAC;AAAA,IACxB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa,CAAC,CAAC,OAAO;AAAA,IACtB,cAAc,CAAC,OAAO;AAAA,EACxB;AACF;AAEO,IAAM,UAAU,CAAC,OAAc,WAA0B;AAC9D,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,GAAG,iBAAiB,OAAO,OAAO;AAAA,MACpC;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,GAAG,iBAAiB;AAAA,UAClB,QAAQ,MAAM;AAAA,QAChB,CAAC;AAAA,MACH;AAAA,IACF,KAAK;AACH,UAAI,MAAM,SAAS,QAAQ;AACzB,eAAO,EAAE,GAAG,OAAO,MAAM,QAAQ;AAAA,MACnC;AACA,aAAO,EAAE,GAAG,OAAO,MAAM,OAAO;AAAA,IAClC,KAAK;AACH,UAAI,MAAM,cAAc;AACtB,eAAO;AAAA,MACT;AACA,UAAI,CAAC,UAAU,QAAQ,EAAE,SAAS,MAAM,MAAM,GAAG;AAC/C,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,kBAAkB,MAAM,mBAAmB;AAAA,QAC3C,SAAS,MAAM,OAAO,MAAM,MAAM,gBAAgB;AAAA,QAClD,UACE,MAAM,mBAAmB,MAAM,OAAO,MAAM,SAAS,IACjD,MAAM,OAAO,MAAM,MAAM,mBAAmB,CAAC,IAC7C;AAAA,QACN,aAAa;AAAA,QACb,cAAc;AAAA,QACd,QAAQ;AAAA,MACV;AAAA,IAEF,KAAK,eAAe;AAClB,YAAM,EAAE,MAAM,SAAS,QAAQ,aAAa,IAAI,OAAO;AAEvD,YAAM,cAAc,CAAC,6BAAM,KAAK,6BAAM,GAAG,EAAE;AAAA,SACzC,+BAAO,aAAY;AAAA,MACrB;AACA,YAAM,iBACJ,MAAM,qBAAqB,MAAM,OAAO,MAAM,SAAS;AAEzD,UAAI,CAAC,aAAa;AAChB,YAAI,QAAQ;AACV,iBAAO,YAAY;AAAA,QACrB;AACA,eAAO;AAAA,UACL,GAAG;AAAA,UACH,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,MAAM;AAAA,UACN,cAAc;AAAA,QAChB;AAAA,MACF;AAEA,UAAI,gBAAgB;AAClB,YAAI,SAAS;AACX,kBAAQ,YAAY;AAAA,QACtB;AAEA,eAAO;AAAA,UACL,GAAG;AAAA,UACH,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,MAAM;AAAA,UACN,cAAc;AAAA,QAChB;AAAA,MACF;AAEA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,MAAM;AAAA,QACN,kBAAkB,MAAM,mBAAmB;AAAA,QAC3C,UAAU,MAAM,OAAO,MAAM,MAAM,mBAAmB,CAAC;AAAA,QACvD,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,cAAc;AAAA,MAChB;AAAA,IACF;AAAA,IAEA;AACE,aAAO;AAAA,EACX;AACF;;;AD3IA,SAAS,2BAA2B;AAE7B,IAAM,iBAAiB,CAC5B,QACA,SACA,WACG;AATL;AAUE,QAAM,cAAc,oBAAoB;AAExC,QAAM,CAAC,OAAO,QAAQ,IAAI,WAAW,SAAS,EAAE,OAAO,GAAG,gBAAgB;AAE1E,QAAM;AAAA,IACJ;AAAA,IACA,SAAS,EAAE,UAAU,YAAY;AAAA,EACnC,IAAI;AAEJ,YAAU,MAAM;AACd,QAAI,eAAe,KAAK,IAAI,MAAM,OAAO,KAAK;AAC5C,kBAAY,OAAO,KAAK,eAAe,MAAM,CAAC;AAAA,IAChD;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,CAACC,YAAmB;AACvC,aAAS,EAAE,MAAM,cAAc,SAAS,EAAE,QAAAA,QAAO,EAAE,CAAC;AACpD,gBAAYA,QAAO,KAAK,eAAeA,OAAM,CAAC;AAAA,EAChD;AAEA,YAAU,MAAM;AACd,QAAI,eAAe,KAAK,IAAI,MAAM,OAAO,OAAO,MAAM,aAAa;AACjE;AAAA,QACE,MACE,SAAS;AAAA,UACP,MAAM;AAAA,QACR,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,aAAa,MAAM,WAAW,CAAC;AAEnC,YAAU,MAAM;AACd,QAAI,MAAM,SAAS;AACjB,eAAS,MAAM,OAAO;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,MAAM,OAAO,CAAC;AAElB,YAAU,MAAM;AAhDlB,QAAAC,KAAA;AAiDI,UAAIA,MAAA,6BAAM,cAAN,gBAAAA,IAAiB,WAAU,KAAK,OAAO,gBAAgB,IAAI,IAAI;AACjE;AAAA,IACF;AACA,QAAI,KAAK,QAAQ,EAAE,SAAS,OAAO,OAAO,gBAAgB,IAAI,IAAI;AAChE,eAAS;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,UACP,QAAM,sDAAa,SAAb,mBAAmB,QAAQ,EAAE,SAAS,KAAK,OAA3C,mBAA+C,UAAS;AAAA,UAC9D;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF,CAAC;AAED,eAAS;AAAA,QACP,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,EACF,GAAG,EAAC,kCAAM,cAAN,mBAAiB,MAAM,CAAC;AAE5B,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AAEA,QAAM,SAAS,MAAM;AACnB,aAAS,EAAE,MAAM,cAAc,CAAC;AAAA,EAClC;AAEA,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd;AAAA,IACA;AAAA,IACA,MAAM,MAAM;AAAA,IACZ;AAAA,IACA,UAAU,MAAM;AAAA,IAChB,cAAc,MAAM;AAAA,EACtB;AACF;;;AFpFA,SAAS,iBAAiB;;;AIH1B,OAAOC,YAAW;AAGX,IAAM,qBAAqBA,OAAM,cAE9B,IAAI;AAEP,IAAM,wBAAwB,MAAM;AACzC,QAAM,UAAUA,OAAM,WAAW,kBAAkB;AACnD,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;AJHA,IAAM,aAA2D,CAAC;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,UAAU,eAAe,QAAQ,SAAS,MAAM;AAEtD,SACE,gBAAAC,OAAA,cAAC,mBAAmB,UAAnB,EAA4B,OAAO,WACjC,QACH;AAEJ;AAEO,IAAM,OAAqD,CAAC;AAAA,EACjE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,SACE,gBAAAA,OAAA,cAAC,UAAU,MAAV,EAAe,KAAK,OAAO,KAAK,aAAa,eAAe,MAAM,KACjE,gBAAAA,OAAA,cAAC,cAAW,QAAgB,SAAkB,UAC3C,QACH,CACF;AAEJ;;;AKxCA,OAAOC,YAAW;AAClB;AAAA,EACE,aAAAC;AAAA,EACA,uBAAAC;AAAA,OACK;AAMA,IAAM,cAA0C,CAAC,EAAE,GAAG,KAAK,MAAM;AACtE,QAAM,gBAAgB,sBAAsB;AAC5C,QAAM,cAAcC,qBAAoB;AAExC,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AACA,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAEA,QAAM,EAAE,KAAK,IAAI;AACjB,QAAM,EAAE,QAAQ,MAAM,cAAc,SAAS,IAAI;AAEjD,SACE,gBAAAC,OAAA;AAAA,IAACC,WAAU;AAAA,IAAV;AAAA,MACC,oBAAoB;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAa,MAAM,QAAQ;AAAA,MAC7B;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;;;ACpCA,OAAOC,YAAW;AAWlB,IAAM,gBAA0B,CAAC,UAAU,QAAQ;AAE5C,IAAM,QAAuD,CAAC;AAAA,EACnE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AACX,MAAM;AACJ,QAAM,gBAAgB,sBAAsB;AAC5C,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AACA,QAAM,EAAE,cAAc,OAAO,IAAI;AACjC,QAAM,cAAc,MAAM;AACxB,iBAAa,UAAU,cAAc,MAAM;AAC3C;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,SAAS,MAAM,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,MAAI,SAAS;AACX,UAAM,QAAQC,OAAM,SAAS,KAAK,QAAQ;AAC1C,QAAI,mBAAmB,KAAK,GAAG;AAC7B,aAAOA,OAAM,aAAa,OAAO;AAAA,QAC/B,SAAS;AAAA,MACX,CAAC;AAAA,IACH,OAAO;AACL,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AAAA,EACF;AAEA,SACE,gBAAAA,OAAA,cAAC,YAAO,MAAK,UAAS,SAAS,eAC5B,QACH;AAEJ;;;AClDA,OAAOC,YAAW;AASlB,IAAMC,iBAA0B,CAAC,eAAe,aAAa;AAEtD,IAAM,OAAqD,CAAC;AAAA,EACjE;AAAA,EACA;AAAA,EACA,SAASA;AACX,MAAM;AACJ,QAAM,gBAAgB,sBAAsB;AAC5C,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AACA,QAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,QAAM,cAAc,MAAM;AACxB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,OAAO,SAAS,MAAM,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,MAAI,SAAS;AACX,UAAM,QAAQC,OAAM,SAAS,KAAK,QAAQ;AAC1C,QAAI,mBAAmB,KAAK,GAAG;AAC7B,aAAOA,OAAM,aAAa,OAAO;AAAA,QAC/B,SAAS;AAAA,MACX,CAAC;AAAA,IACH,OAAO;AACL,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AAAA,EACF;AAEA,SACE,gBAAAA,OAAA,cAAC,YAAO,MAAK,UAAS,SAAS,eAC5B,QACH;AAEJ;;;ACxCO,IAAM,cAAc;AAAA,EACzB;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AACF;","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":";AAAA,OAAOA,YAAW;;;ACAlB,SAAqB,aAAmB;AACxC,OAAO,WAAuD;AAC9D,OAAO,OAAO;AAad,IAAM,aAAa;AACnB,IAAM,gBAAgB;AACtB,IAAM,aAAa;AAEZ,IAAM,iBAAiB,CAAC,WAA0B;AACvD,QAAM,MAAM,OAAO;AACnB,QAAM,OAAO,IAAI,MAAM,GAAG;AAC1B,MAAI,OAAO,eAAe;AACxB,SAAK,KAAK,OAAO,MAAM,CAAC,CAAC;AAAA,EAC3B;AACA,SAAO,KAAK,KAAK;AACnB;AAQO,IAAM,qBAAqB,CAChC,YACgC,MAAM,eAAe,OAAO;AAEvD,IAAM,wBAAwB,CACnC,QACA,MACA,cACA,MACA,aACG;AACH,QAAM,qBAAoD,CAAC;AAE3D,QAAM,WAAW,EAAE,KAAK,KAAK,QAAQ,EAAE,SAAS,KAAK,CAAC,CAAC;AAEvD,MAAI,WAAW,YAAY,UAAU;AACnC,uBAAmB,SAAS,IAAI,IAAI;AAAA,MAClC,iBAAiB;AAAA,IACnB;AACA,uBAAmB,SAAS,EAAE,IAAI;AAAA,MAChC,iBAAiB;AAAA,IACnB;AAAA,EACF;AAEA,MACE,aACC,WAAW,YAAa,WAAW,YAAY,CAAC,eACjD;AACA,uBAAmB,SAAS,IAAI,IAAI;AAAA,MAClC,iBAAiB;AAAA,IACnB;AACA,uBAAmB,SAAS,EAAE,IAAI;AAAA,MAChC,iBAAiB;AAAA,IACnB;AAAA,EACF;AAEA,MAAI,SAAS,SAAS;AACpB,QAAI,UAAU;AACZ,yBAAmB,SAAS,IAAI,IAAI;AAAA,QAClC,iBAAiB;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,QAAQ;AACnB,QAAI,UAAU;AACZ,yBAAmB,SAAS,IAAI,IAAI;AAAA,QAClC,iBAAiB;AAAA,MACnB;AACA,yBAAmB,SAAS,EAAE,IAAI;AAAA,QAChC,iBAAiB;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,IAAM,eAAe,CAAC,MAAa,SAAoC;AAC5E,QAAM,OAAO,IAAI,MAAM,KAAK,IAAI,CAAC;AACjC,MAAI,SAAS,QAAQ,SAAS,QAAW;AACvC,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,KAAK,KAAK,IAAI;AAAA,EACvB,SAAS,GAAG;AACV,WAAO;AAAA,EACT;AACF;;;ACtGA,SAAS,WAAW,kBAAkB;;;ACwC/B,IAAM,mBAAmB,CAAC,EAAE,OAAO,MAAiC;AACzE,SAAO;AAAA,IACL;AAAA,IACA,kBAAkB;AAAA,IAClB,QAAQ;AAAA,IACR,UAAU,OAAO,MAAM,CAAC;AAAA,IACxB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa,CAAC,CAAC,OAAO;AAAA,IACtB,cAAc,CAAC,OAAO;AAAA,EACxB;AACF;AAEO,IAAM,UAAU,CAAC,OAAc,WAA0B;AAC9D,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,GAAG,iBAAiB,OAAO,OAAO;AAAA,MACpC;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,GAAG,iBAAiB;AAAA,UAClB,QAAQ,MAAM;AAAA,QAChB,CAAC;AAAA,MACH;AAAA,IACF,KAAK;AACH,UAAI,MAAM,SAAS,QAAQ;AACzB,eAAO,EAAE,GAAG,OAAO,MAAM,QAAQ;AAAA,MACnC;AACA,aAAO,EAAE,GAAG,OAAO,MAAM,OAAO;AAAA,IAClC,KAAK;AACH,UAAI,MAAM,cAAc;AACtB,eAAO;AAAA,MACT;AACA,UAAI,CAAC,UAAU,QAAQ,EAAE,SAAS,MAAM,MAAM,GAAG;AAC/C,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,kBAAkB,MAAM,mBAAmB;AAAA,QAC3C,SAAS,MAAM,OAAO,MAAM,MAAM,gBAAgB;AAAA,QAClD,UACE,MAAM,mBAAmB,MAAM,OAAO,MAAM,SAAS,IACjD,MAAM,OAAO,MAAM,MAAM,mBAAmB,CAAC,IAC7C;AAAA,QACN,aAAa;AAAA,QACb,cAAc;AAAA,QACd,QAAQ;AAAA,MACV;AAAA,IAEF,KAAK,eAAe;AAClB,YAAM,EAAE,MAAM,SAAS,QAAQ,cAAc,IAAI,OAAO;AAExD,YAAM,cAAc,CAAC,6BAAM,KAAK,6BAAM,GAAG,EAAE;AAAA,SACzC,+BAAO,aAAY;AAAA,MACrB;AACA,YAAM,iBACJ,MAAM,qBAAqB,MAAM,OAAO,MAAM,SAAS;AAEzD,UAAI,CAAC,aAAa;AAChB,YAAI,QAAQ;AACV,iBAAO,aAAa;AAAA,QACtB;AACA,eAAO;AAAA,UACL,GAAG;AAAA,UACH,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,MAAM;AAAA,UACN,cAAc;AAAA,QAChB;AAAA,MACF;AAEA,UAAI,gBAAgB;AAClB,YAAI,SAAS;AACX,kBAAQ,aAAa;AAAA,QACvB;AAEA,eAAO;AAAA,UACL,GAAG;AAAA,UACH,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,MAAM;AAAA,UACN,cAAc;AAAA,QAChB;AAAA,MACF;AAEA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,MAAM;AAAA,QACN,kBAAkB,MAAM,mBAAmB;AAAA,QAC3C,UAAU,MAAM,OAAO,MAAM,MAAM,mBAAmB,CAAC;AAAA,QACvD,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,cAAc;AAAA,MAChB;AAAA,IACF;AAAA,IAEA;AACE,aAAO;AAAA,EACX;AACF;;;AD5IA,SAAS,2BAA2B;AAa7B,IAAM,iBAAiB,CAC5B,QACA,SACA,WAC2B;AApB7B;AAqBE,QAAM,cAAc,oBAAoB;AAExC,QAAM,CAAC,OAAO,QAAQ,IAAI,WAAW,SAAS,EAAE,OAAO,GAAG,gBAAgB;AAE1E,QAAM;AAAA,IACJ;AAAA,IACA,SAAS,EAAE,UAAU,YAAY;AAAA,EACnC,IAAI;AAEJ,YAAU,MAAM;AACd,QAAI,eAAe,KAAK,IAAI,MAAM,OAAO,KAAK;AAC5C,kBAAY,OAAO,KAAK,eAAe,MAAM,CAAC;AAAA,IAChD;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,CAACC,YAAmB;AACvC,aAAS,EAAE,MAAM,cAAc,SAAS,EAAE,QAAAA,QAAO,EAAE,CAAC;AACpD,gBAAYA,QAAO,KAAK,eAAeA,OAAM,CAAC;AAAA,EAChD;AAEA,YAAU,MAAM;AACd,QAAI,eAAe,KAAK,IAAI,MAAM,OAAO,OAAO,MAAM,aAAa;AACjE;AAAA,QACE,MACE,SAAS;AAAA,UACP,MAAM;AAAA,QACR,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,aAAa,MAAM,WAAW,CAAC;AAEnC,YAAU,MAAM;AACd,QAAI,MAAM,SAAS;AACjB,eAAS,MAAM,OAAO;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,MAAM,OAAO,CAAC;AAElB,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AAEA,QAAM,SAAS,MAAM;AACnB,aAAS,EAAE,MAAM,cAAc,CAAC;AAAA,EAClC;AAEA,QAAM,gBAAwC;AAAA,IAC5C,QAAQ,MAAM;AAAA,IACd;AAAA,IACA;AAAA,IACA,MAAM,MAAM;AAAA,IACZ;AAAA,IACA,UAAU,MAAM;AAAA,IAChB,cAAc,MAAM;AAAA,EACtB;AAEA,YAAU,MAAM;AA7ElB,QAAAC,KAAA;AA8EI,UAAIA,MAAA,6BAAM,cAAN,gBAAAA,IAAiB,WAAU,KAAK,OAAO,gBAAgB,IAAI,IAAI;AACjE;AAAA,IACF;AACA,QAAI,KAAK,QAAQ,EAAE,SAAS,OAAO,OAAO,gBAAgB,IAAI,IAAI;AAChE,eAAS;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,UACP,QAAM,sDAAa,SAAb,mBAAmB,QAAQ,EAAE,SAAS,KAAK,OAA3C,mBAA+C,UAAS;AAAA,UAC9D;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF,CAAC;AAED,eAAS;AAAA,QACP,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,EACF,GAAG,EAAC,kCAAM,cAAN,mBAAiB,MAAM,CAAC;AAE5B,SAAO;AACT;;;AF9FA,SAAS,iBAAiB;;;AIN1B,OAAOC,YAAW;AAGX,IAAM,qBAAqBA,OAAM,cAE9B,IAAI;AAEP,IAAM,wBAAwB,MAAM;AACzC,QAAM,UAAUA,OAAM,WAAW,kBAAkB;AACnD,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;AJAA,IAAM,aAA2D,CAAC;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,UAAU,eAAe,QAAQ,SAAS,MAAM;AAEtD,SACE,gBAAAC,OAAA,cAAC,mBAAmB,UAAnB,EAA4B,OAAO,WACjC,QACH;AAEJ;AAEO,IAAM,OAAqD,CAAC;AAAA,EACjE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,SACE,gBAAAA,OAAA,cAAC,UAAU,MAAV,EAAe,KAAK,OAAO,KAAK,aAAa,eAAe,MAAM,KACjE,gBAAAA,OAAA,cAAC,cAAW,QAAgB,SAAkB,UAC3C,QACH,CACF;AAEJ;;;AK3CA,OAAOC,YAAW;AAClB;AAAA,EACE,aAAAC;AAAA,EACA,uBAAAC;AAAA,OACK;AAMA,IAAM,cAA0C,CAAC,EAAE,GAAG,KAAK,MAAM;AACtE,QAAM,gBAAgB,sBAAsB;AAC5C,QAAM,cAAcC,qBAAoB;AAExC,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AACA,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAEA,QAAM,EAAE,KAAK,IAAI;AACjB,QAAM,EAAE,QAAQ,MAAM,cAAc,SAAS,IAAI;AAEjD,SACE,gBAAAC,OAAA;AAAA,IAACC,WAAU;AAAA,IAAV;AAAA,MACC,oBAAoB;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAa,MAAM,QAAQ;AAAA,MAC7B;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;;;ACpCA,OAAOC,YAAW;AAWlB,IAAM,gBAA0B,CAAC,UAAU,QAAQ;AAE5C,IAAM,QAAuD,CAAC;AAAA,EACnE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AACX,MAAM;AACJ,QAAM,gBAAgB,sBAAsB;AAC5C,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AACA,QAAM,EAAE,cAAc,OAAO,IAAI;AACjC,QAAM,cAAc,MAAM;AACxB,iBAAa,UAAU,cAAc,MAAM;AAC3C;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,SAAS,MAAM,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,MAAI,SAAS;AACX,UAAM,QAAQC,OAAM,SAAS,KAAK,QAAQ;AAC1C,QAAI,mBAAmB,KAAK,GAAG;AAC7B,aAAOA,OAAM,aAAa,OAAO;AAAA,QAC/B,SAAS;AAAA,MACX,CAAC;AAAA,IACH,OAAO;AACL,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AAAA,EACF;AAEA,SACE,gBAAAA,OAAA,cAAC,YAAO,MAAK,UAAS,SAAS,eAC5B,QACH;AAEJ;;;AClDA,OAAOC,YAAW;AASlB,IAAMC,iBAA0B,CAAC,eAAe,aAAa;AAEtD,IAAM,OAAqD,CAAC;AAAA,EACjE;AAAA,EACA;AAAA,EACA,SAASA;AACX,MAAM;AACJ,QAAM,gBAAgB,sBAAsB;AAC5C,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AACA,QAAM,EAAE,QAAQ,OAAO,IAAI;AAC3B,QAAM,cAAc,MAAM;AACxB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,OAAO,SAAS,MAAM,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,MAAI,SAAS;AACX,UAAM,QAAQC,OAAM,SAAS,KAAK,QAAQ;AAC1C,QAAI,mBAAmB,KAAK,GAAG;AAC7B,aAAOA,OAAM,aAAa,OAAO;AAAA,QAC/B,SAAS;AAAA,MACX,CAAC;AAAA,IACH,OAAO;AACL,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AAAA,EACF;AAEA,SACE,gBAAAA,OAAA,cAAC,YAAO,MAAK,UAAS,SAAS,eAC5B,QACH;AAEJ;;;ACxCO,IAAM,cAAc;AAAA,EACzB;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AACF;","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",
|
|
@@ -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
|
};
|
|
@@ -91,7 +92,7 @@ export const reducer = (state: State, action: Action): State => {
|
|
|
91
92
|
};
|
|
92
93
|
|
|
93
94
|
case "PLAYER_MOVE": {
|
|
94
|
-
const { move, onSolve, onFail,
|
|
95
|
+
const { move, onSolve, onFail, puzzleContext } = action.payload;
|
|
95
96
|
|
|
96
97
|
const isMoveRight = [move?.san, move?.lan].includes(
|
|
97
98
|
state?.nextMove || "",
|
|
@@ -101,7 +102,7 @@ export const reducer = (state: State, action: Action): State => {
|
|
|
101
102
|
|
|
102
103
|
if (!isMoveRight) {
|
|
103
104
|
if (onFail) {
|
|
104
|
-
onFail(
|
|
105
|
+
onFail(puzzleContext);
|
|
105
106
|
}
|
|
106
107
|
return {
|
|
107
108
|
...state,
|
|
@@ -114,7 +115,7 @@ export const reducer = (state: State, action: Action): State => {
|
|
|
114
115
|
|
|
115
116
|
if (isPuzzleSolved) {
|
|
116
117
|
if (onSolve) {
|
|
117
|
-
onSolve(
|
|
118
|
+
onSolve(puzzleContext);
|
|
118
119
|
}
|
|
119
120
|
|
|
120
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
|
};
|