@react-chess-tools/react-chess-puzzle 0.1.3 → 0.2.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 ADDED
@@ -0,0 +1,13 @@
1
+ # @react-chess-tools/react-chess-puzzle
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ea0eafb: Add changesets versioning
8
+ setup `changesets` and created the first changeset for the available packages. Removed release-it
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies [ea0eafb]
13
+ - @react-chess-tools/react-chess-game@0.2.0
package/dist/index.mjs CHANGED
@@ -4,7 +4,7 @@ import React3 from "react";
4
4
  // src/utils/index.ts
5
5
  import { Chess } from "chess.js";
6
6
  import React from "react";
7
- import { last } from "lodash";
7
+ import _ from "lodash";
8
8
  var FAIL_COLOR = "rgba(201, 52, 48, 0.5)";
9
9
  var SUCCESS_COLOR = "rgba(172, 206, 89, 0.5)";
10
10
  var HINT_COLOR = "rgba(27, 172, 166, 0.5)";
@@ -19,7 +19,7 @@ var getOrientation = (puzzle) => {
19
19
  var isClickableElement = (element) => React.isValidElement(element);
20
20
  var getCustomSquareStyles = (status, hint, isPlayerTurn, game, nextMove) => {
21
21
  const customSquareStyles = {};
22
- const lastMove = last(game.history({ verbose: true }));
22
+ const lastMove = _.last(game.history({ verbose: true }));
23
23
  if (status === "failed" && lastMove) {
24
24
  customSquareStyles[lastMove.from] = {
25
25
  backgroundColor: FAIL_COLOR
@@ -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 { last } 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 { 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(\n reducer,\n { puzzle, setPosition: gameContext?.methods.setPosition ?? (() => {}) },\n initializePuzzle,\n );\n\n const {\n game,\n methods: { makeMove, setPosition },\n } = gameContext;\n\n useEffect(() => {\n if (gameContext && game.fen() === puzzle.fen && state.needCpuMove) {\n setTimeout(\n () =>\n dispatch({\n type: \"CPU_MOVE\",\n payload: {\n makeMove,\n },\n }),\n 0,\n );\n }\n }, [gameContext, state.needCpuMove]);\n\n if (!gameContext) {\n throw new Error(\"useChessPuzzle must be used within a ChessGameContext\");\n }\n\n const changePuzzle = (puzzle: Puzzle) => {\n dispatch({ type: \"INITIALIZE\", payload: { puzzle, setPosition } });\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 changePuzzle,\n game: game,\n },\n });\n\n dispatch({\n type: \"CPU_MOVE\",\n payload: {\n makeMove,\n },\n });\n }\n }, [game?.history()?.length]);\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 { useChessGame } from \"@react-chess-tools/react-chess-game\";\nimport { getOrientation, type Puzzle, type Hint, type Status } from \"../utils\";\n\nexport type State = {\n puzzle: Puzzle;\n currentMoveIndex: number;\n status: Status;\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 setPosition: ReturnType<typeof useChessGame>[\"methods\"][\"setPosition\"];\n };\n }\n | {\n type: \"RESET\";\n payload: {\n setPosition: ReturnType<typeof useChessGame>[\"methods\"][\"setPosition\"];\n };\n }\n | { type: \"TOGGLE_HINT\" }\n | {\n type: \"CPU_MOVE\";\n payload: {\n makeMove?: ReturnType<typeof useChessGame>[\"methods\"][\"makeMove\"];\n };\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 = ({\n puzzle,\n setPosition,\n}: {\n puzzle: Puzzle;\n setPosition: ReturnType<typeof useChessGame>[\"methods\"][\"setPosition\"];\n}): State => {\n setPosition(puzzle.fen, getOrientation(puzzle));\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 setPosition: action.payload.setPosition,\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 if (state.nextMove) {\n action.payload.makeMove?.(state.nextMove);\n }\n\n return {\n ...state,\n currentMoveIndex: state.currentMoveIndex + 1,\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,SAAS,YAAY;AAarB,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,KAAK,KAAK,QAAQ,EAAE,SAAS,KAAK,CAAC,CAAC;AAErD,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;;;AC8C/B,IAAM,mBAAmB,CAAC;AAAA,EAC/B;AAAA,EACA;AACF,MAGa;AACX,cAAY,OAAO,KAAK,eAAe,MAAM,CAAC;AAC9C,SAAO;AAAA,IACL;AAAA,IACA,kBAAkB;AAAA,IAClB,QAAQ;AAAA,IACR,UAAU,OAAO,MAAM,CAAC;AAAA,IACxB,MAAM;AAAA,IACN,aAAa,CAAC,CAAC,OAAO;AAAA,IACtB,cAAc,CAAC,OAAO;AAAA,EACxB;AACF;AAEO,IAAM,UAAU,CAAC,OAAc,WAA0B;AAjEhE;AAkEE,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,UACd,aAAa,OAAO,QAAQ;AAAA,QAC9B,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,UAAI,MAAM,UAAU;AAClB,2BAAO,SAAQ,aAAf,4BAA0B,MAAM;AAAA,MAClC;AAEA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,kBAAkB,MAAM,mBAAmB;AAAA,QAC3C,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;;;AD5JA,SAAS,2BAA2B;AAE7B,IAAM,iBAAiB,CAC5B,QACA,SACA,WACG;AATL;AAUE,QAAM,cAAc,oBAAoB;AAExC,QAAM,CAAC,OAAO,QAAQ,IAAI;AAAA,IACxB;AAAA,IACA,EAAE,QAAQ,cAAa,2CAAa,QAAQ,iBAAgB,MAAM;AAAA,IAAC,GAAG;AAAA,IACtE;AAAA,EACF;AAEA,QAAM;AAAA,IACJ;AAAA,IACA,SAAS,EAAE,UAAU,YAAY;AAAA,EACnC,IAAI;AAEJ,YAAU,MAAM;AACd,QAAI,eAAe,KAAK,IAAI,MAAM,OAAO,OAAO,MAAM,aAAa;AACjE;AAAA,QACE,MACE,SAAS;AAAA,UACP,MAAM;AAAA,UACN,SAAS;AAAA,YACP;AAAA,UACF;AAAA,QACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,aAAa,MAAM,WAAW,CAAC;AAEnC,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AAEA,QAAM,eAAe,CAACC,YAAmB;AACvC,aAAS,EAAE,MAAM,cAAc,SAAS,EAAE,QAAAA,SAAQ,YAAY,EAAE,CAAC;AAAA,EACnE;AAEA,YAAU,MAAM;AA9ClB,QAAAC,KAAA;AA+CI,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,QACN,SAAS;AAAA,UACP;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,GAAG,EAAC,kCAAM,cAAN,mBAAiB,MAAM,CAAC;AAE5B,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;;;AFjFA,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 { 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 { 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(\n reducer,\n { puzzle, setPosition: gameContext?.methods.setPosition ?? (() => {}) },\n initializePuzzle,\n );\n\n const {\n game,\n methods: { makeMove, setPosition },\n } = gameContext;\n\n useEffect(() => {\n if (gameContext && game.fen() === puzzle.fen && state.needCpuMove) {\n setTimeout(\n () =>\n dispatch({\n type: \"CPU_MOVE\",\n payload: {\n makeMove,\n },\n }),\n 0,\n );\n }\n }, [gameContext, state.needCpuMove]);\n\n if (!gameContext) {\n throw new Error(\"useChessPuzzle must be used within a ChessGameContext\");\n }\n\n const changePuzzle = (puzzle: Puzzle) => {\n dispatch({ type: \"INITIALIZE\", payload: { puzzle, setPosition } });\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 changePuzzle,\n game: game,\n },\n });\n\n dispatch({\n type: \"CPU_MOVE\",\n payload: {\n makeMove,\n },\n });\n }\n }, [game?.history()?.length]);\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 { useChessGame } from \"@react-chess-tools/react-chess-game\";\nimport { getOrientation, type Puzzle, type Hint, type Status } from \"../utils\";\n\nexport type State = {\n puzzle: Puzzle;\n currentMoveIndex: number;\n status: Status;\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 setPosition: ReturnType<typeof useChessGame>[\"methods\"][\"setPosition\"];\n };\n }\n | {\n type: \"RESET\";\n payload: {\n setPosition: ReturnType<typeof useChessGame>[\"methods\"][\"setPosition\"];\n };\n }\n | { type: \"TOGGLE_HINT\" }\n | {\n type: \"CPU_MOVE\";\n payload: {\n makeMove?: ReturnType<typeof useChessGame>[\"methods\"][\"makeMove\"];\n };\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 = ({\n puzzle,\n setPosition,\n}: {\n puzzle: Puzzle;\n setPosition: ReturnType<typeof useChessGame>[\"methods\"][\"setPosition\"];\n}): State => {\n setPosition(puzzle.fen, getOrientation(puzzle));\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 setPosition: action.payload.setPosition,\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 if (state.nextMove) {\n action.payload.makeMove?.(state.nextMove);\n }\n\n return {\n ...state,\n currentMoveIndex: state.currentMoveIndex + 1,\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"]}
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@react-chess-tools/react-chess-puzzle",
3
- "version": "0.1.3",
3
+ "version": "0.2.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",
7
7
  "types": "dist/index.d.mts",
8
8
  "scripts": {
9
9
  "build": "tsup src/index.ts",
10
- "release": "npm run build && release-it --npm.publish=true",
11
10
  "test": "echo \"Error: no test specified\" && exit 1"
12
11
  },
13
12
  "keywords": [
@@ -33,7 +32,7 @@
33
32
  "author": "Daniele Cammareri <daniele.cammareri@gmail.com>",
34
33
  "license": "MIT",
35
34
  "dependencies": {
36
- "@react-chess-tools/react-chess-game": "0.1.3",
35
+ "@react-chess-tools/react-chess-game": "0.2.0",
37
36
  "chess.js": "^1.0.0-beta.6",
38
37
  "lodash": "^4.17.21"
39
38
  },
@@ -0,0 +1,85 @@
1
+ import type { Meta } from "@storybook/react";
2
+
3
+ import React from "react";
4
+ import { RootProps } from "./parts/Root";
5
+ import { ChessPuzzle } from ".";
6
+
7
+ const puzzles = [
8
+ {
9
+ fen: "4kb1r/p2r1ppp/4qn2/1B2p1B1/4P3/1Q6/PPP2PPP/2KR4 w k - 0 1",
10
+ moves: ["Bxd7+", "Nxd7", "Qb8+", "Nxb8", "Rd8#"],
11
+ makeFirstMove: false,
12
+ },
13
+ {
14
+ fen: "6k1/5p1p/p1q1p1p1/1pB1P3/1Pr3Pn/P4P1P/4Q3/3R2K1 b - - 0 31",
15
+ moves: ["h4f3", "e2f3", "c4c5", "d1d8", "g8g7", "f3f6"],
16
+ makeFirstMove: true,
17
+ },
18
+ ];
19
+
20
+ // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
21
+ const meta = {
22
+ title: "react-chess-puzzle/Components/Puzzle",
23
+ component: ChessPuzzle.Root,
24
+ tags: ["components", "puzzle"],
25
+ argTypes: {
26
+ onSolve: { action: "onSolve" },
27
+ onFail: { action: "onFail" },
28
+ },
29
+ parameters: {
30
+ actions: { argTypesRegex: "^_on.*" },
31
+ },
32
+ decorators: [
33
+ (Story) => (
34
+ <div style={{ width: "400px" }}>
35
+ {/* 👇 Decorators in Storybook also accept a function. Replace <Story/> with Story() to enable it */}
36
+ <Story />
37
+ </div>
38
+ ),
39
+ ],
40
+ } satisfies Meta<typeof ChessPuzzle.Root>;
41
+
42
+ export default meta;
43
+
44
+ // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
45
+
46
+ export const Example = (args: RootProps) => {
47
+ const [puzzleIndex, setPuzzleIndex] = React.useState(0);
48
+ const puzzle = puzzles[puzzleIndex];
49
+ return (
50
+ <div>
51
+ <ChessPuzzle.Root {...args} puzzle={puzzle}>
52
+ <ChessPuzzle.Board />
53
+ <ChessPuzzle.Reset asChild>
54
+ <button>restart</button>
55
+ </ChessPuzzle.Reset>
56
+ <ChessPuzzle.Reset
57
+ asChild
58
+ puzzle={puzzles[(puzzleIndex + 1) % puzzles.length]}
59
+ onReset={() => setPuzzleIndex((puzzleIndex + 1) % puzzles.length)}
60
+ >
61
+ <button>next</button>
62
+ </ChessPuzzle.Reset>
63
+ <ChessPuzzle.Hint>hint</ChessPuzzle.Hint>
64
+ </ChessPuzzle.Root>
65
+ </div>
66
+ );
67
+ };
68
+
69
+ export const Underpromotion = (args: RootProps) => {
70
+ const puzzle = {
71
+ fen: "8/8/5R1p/8/3pb1P1/kpKp4/8/8 w - - 0 54",
72
+ moves: ["c3d4", "d3d2", "d4c3", "d2d1n"],
73
+ makeFirstMove: true,
74
+ };
75
+ return (
76
+ <div>
77
+ <ChessPuzzle.Root {...args} puzzle={puzzle}>
78
+ <ChessPuzzle.Board />
79
+ <ChessPuzzle.Reset asChild>
80
+ <button>done! Restart</button>
81
+ </ChessPuzzle.Reset>
82
+ </ChessPuzzle.Root>
83
+ </div>
84
+ );
85
+ };
@@ -0,0 +1,11 @@
1
+ import { Root } from "./parts/Root";
2
+ import { PuzzleBoard } from "./parts/PuzzleBoard";
3
+ import { Reset } from "./parts/Reset";
4
+ import { Hint } from "./parts/Hint";
5
+
6
+ export const ChessPuzzle = {
7
+ Root,
8
+ Board: PuzzleBoard,
9
+ Reset,
10
+ Hint,
11
+ };
@@ -0,0 +1,46 @@
1
+ import React from "react";
2
+ import { Status, isClickableElement } from "../../../utils";
3
+ import { useChessPuzzleContext } from "../../..";
4
+
5
+ export interface HintProps {
6
+ asChild?: boolean;
7
+ showOn?: Status[];
8
+ }
9
+
10
+ const defaultShowOn: Status[] = ["not-started", "in-progress"];
11
+
12
+ export const Hint: React.FC<React.PropsWithChildren<HintProps>> = ({
13
+ children,
14
+ asChild,
15
+ showOn = defaultShowOn,
16
+ }) => {
17
+ const puzzleContext = useChessPuzzleContext();
18
+ if (!puzzleContext) {
19
+ throw new Error("PuzzleContext not found");
20
+ }
21
+ const { onHint, status } = puzzleContext;
22
+ const handleClick = () => {
23
+ onHint();
24
+ };
25
+
26
+ if (!showOn.includes(status)) {
27
+ return null;
28
+ }
29
+
30
+ if (asChild) {
31
+ const child = React.Children.only(children);
32
+ if (isClickableElement(child)) {
33
+ return React.cloneElement(child, {
34
+ onClick: handleClick,
35
+ });
36
+ } else {
37
+ throw new Error("Change child must be a clickable element");
38
+ }
39
+ }
40
+
41
+ return (
42
+ <button type="button" onClick={handleClick}>
43
+ {children}
44
+ </button>
45
+ );
46
+ };
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import {
3
+ ChessGame,
4
+ useChessGameContext,
5
+ } from "@react-chess-tools/react-chess-game";
6
+ import { getCustomSquareStyles, stringToMove } from "../../../utils";
7
+ import { useChessPuzzleContext } from "../../..";
8
+
9
+ export interface PuzzleBoardProps
10
+ extends React.ComponentProps<typeof ChessGame.Board> {}
11
+ export const PuzzleBoard: React.FC<PuzzleBoardProps> = ({ ...rest }) => {
12
+ const puzzleContext = useChessPuzzleContext();
13
+ const gameContext = useChessGameContext();
14
+
15
+ if (!puzzleContext) {
16
+ throw new Error("PuzzleContext not found");
17
+ }
18
+ if (!gameContext) {
19
+ throw new Error("ChessGameContext not found");
20
+ }
21
+
22
+ const { game } = gameContext;
23
+ const { status, hint, isPlayerTurn, nextMove } = puzzleContext;
24
+
25
+ return (
26
+ <ChessGame.Board
27
+ customSquareStyles={getCustomSquareStyles(
28
+ status,
29
+ hint,
30
+ isPlayerTurn,
31
+ game,
32
+ stringToMove(game, nextMove),
33
+ )}
34
+ {...rest}
35
+ />
36
+ );
37
+ };
@@ -0,0 +1,51 @@
1
+ import React from "react";
2
+ import { isClickableElement, type Puzzle, type Status } from "../../../utils";
3
+ import { useChessPuzzleContext } from "../../..";
4
+
5
+ export interface ResetProps {
6
+ asChild?: boolean;
7
+ puzzle?: Puzzle;
8
+ onReset?: () => void;
9
+ showOn?: Status[];
10
+ }
11
+
12
+ const defaultShowOn: Status[] = ["failed", "solved"];
13
+
14
+ export const Reset: React.FC<React.PropsWithChildren<ResetProps>> = ({
15
+ children,
16
+ asChild,
17
+ puzzle,
18
+ onReset,
19
+ showOn = defaultShowOn,
20
+ }) => {
21
+ const puzzleContext = useChessPuzzleContext();
22
+ if (!puzzleContext) {
23
+ throw new Error("PuzzleContext not found");
24
+ }
25
+ const { changePuzzle, status } = puzzleContext;
26
+ const handleClick = () => {
27
+ changePuzzle(puzzle || puzzleContext.puzzle);
28
+ onReset?.();
29
+ };
30
+
31
+ if (!showOn.includes(status)) {
32
+ return null;
33
+ }
34
+
35
+ if (asChild) {
36
+ const child = React.Children.only(children);
37
+ if (isClickableElement(child)) {
38
+ return React.cloneElement(child, {
39
+ onClick: handleClick,
40
+ });
41
+ } else {
42
+ throw new Error("Change child must be a clickable element");
43
+ }
44
+ }
45
+
46
+ return (
47
+ <button type="button" onClick={handleClick}>
48
+ {children}
49
+ </button>
50
+ );
51
+ };
@@ -0,0 +1,41 @@
1
+ import React from "react";
2
+ import { Puzzle, getOrientation } from "../../../utils";
3
+ import { useChessPuzzle } from "../../../hooks/useChessPuzzle";
4
+ import { ChessGame } from "@react-chess-tools/react-chess-game";
5
+ import { ChessPuzzleContext } from "../../../hooks/useChessPuzzleContext";
6
+
7
+ export interface RootProps {
8
+ puzzle: Puzzle;
9
+ onSolve?: (changePuzzle: (puzzle: Puzzle) => void) => void;
10
+ onFail?: (changePuzzle: (puzzle: Puzzle) => void) => void;
11
+ }
12
+
13
+ const PuzzleRoot: React.FC<React.PropsWithChildren<RootProps>> = ({
14
+ puzzle,
15
+ onSolve,
16
+ onFail,
17
+ children,
18
+ }) => {
19
+ const context = useChessPuzzle(puzzle, onSolve, onFail);
20
+
21
+ return (
22
+ <ChessPuzzleContext.Provider value={context}>
23
+ {children}
24
+ </ChessPuzzleContext.Provider>
25
+ );
26
+ };
27
+
28
+ export const Root: React.FC<React.PropsWithChildren<RootProps>> = ({
29
+ puzzle,
30
+ onSolve,
31
+ onFail,
32
+ children,
33
+ }) => {
34
+ return (
35
+ <ChessGame.Root fen={puzzle.fen} orientation={getOrientation(puzzle)}>
36
+ <PuzzleRoot puzzle={puzzle} onSolve={onSolve} onFail={onFail}>
37
+ {children}
38
+ </PuzzleRoot>
39
+ </ChessGame.Root>
40
+ );
41
+ };
@@ -0,0 +1,160 @@
1
+ import { Chess, Move } from "chess.js";
2
+ import { useChessGame } from "@react-chess-tools/react-chess-game";
3
+ import { getOrientation, type Puzzle, type Hint, type Status } from "../utils";
4
+
5
+ export type State = {
6
+ puzzle: Puzzle;
7
+ currentMoveIndex: number;
8
+ status: Status;
9
+ nextMove?: string | null;
10
+ hint: Hint;
11
+ needCpuMove: boolean;
12
+ isPlayerTurn: boolean;
13
+ };
14
+
15
+ export type Action =
16
+ | {
17
+ type: "INITIALIZE";
18
+ payload: {
19
+ puzzle: Puzzle;
20
+ setPosition: ReturnType<typeof useChessGame>["methods"]["setPosition"];
21
+ };
22
+ }
23
+ | {
24
+ type: "RESET";
25
+ payload: {
26
+ setPosition: ReturnType<typeof useChessGame>["methods"]["setPosition"];
27
+ };
28
+ }
29
+ | { type: "TOGGLE_HINT" }
30
+ | {
31
+ type: "CPU_MOVE";
32
+ payload: {
33
+ makeMove?: ReturnType<typeof useChessGame>["methods"]["makeMove"];
34
+ };
35
+ }
36
+ | {
37
+ type: "PLAYER_MOVE";
38
+ payload: {
39
+ move?: Move | null;
40
+ onSolve?: (changePuzzle: (puzzle: Puzzle) => void) => void;
41
+ onFail?: (changePuzzle: (puzzle: Puzzle) => void) => void;
42
+ changePuzzle: (puzzle: Puzzle) => void;
43
+ game: Chess;
44
+ };
45
+ };
46
+
47
+ export const initializePuzzle = ({
48
+ puzzle,
49
+ setPosition,
50
+ }: {
51
+ puzzle: Puzzle;
52
+ setPosition: ReturnType<typeof useChessGame>["methods"]["setPosition"];
53
+ }): State => {
54
+ setPosition(puzzle.fen, getOrientation(puzzle));
55
+ return {
56
+ puzzle,
57
+ currentMoveIndex: 0,
58
+ status: "not-started",
59
+ nextMove: puzzle.moves[0],
60
+ hint: "none",
61
+ needCpuMove: !!puzzle.makeFirstMove,
62
+ isPlayerTurn: !puzzle.makeFirstMove,
63
+ };
64
+ };
65
+
66
+ export const reducer = (state: State, action: Action): State => {
67
+ switch (action.type) {
68
+ case "INITIALIZE":
69
+ return {
70
+ ...state,
71
+ ...initializePuzzle(action.payload),
72
+ };
73
+ case "RESET":
74
+ return {
75
+ ...state,
76
+ ...initializePuzzle({
77
+ puzzle: state.puzzle,
78
+ setPosition: action.payload.setPosition,
79
+ }),
80
+ };
81
+ case "TOGGLE_HINT":
82
+ if (state.hint === "none") {
83
+ return { ...state, hint: "piece" };
84
+ }
85
+ return { ...state, hint: "move" };
86
+ case "CPU_MOVE":
87
+ if (state.isPlayerTurn) {
88
+ return state;
89
+ }
90
+ if (["solved", "failed"].includes(state.status)) {
91
+ return state;
92
+ }
93
+
94
+ if (state.nextMove) {
95
+ action.payload.makeMove?.(state.nextMove);
96
+ }
97
+
98
+ return {
99
+ ...state,
100
+ currentMoveIndex: state.currentMoveIndex + 1,
101
+ nextMove:
102
+ state.currentMoveIndex < state.puzzle.moves.length - 1
103
+ ? state.puzzle.moves[state.currentMoveIndex + 1]
104
+ : null,
105
+ needCpuMove: false,
106
+ isPlayerTurn: true,
107
+ status: "in-progress",
108
+ };
109
+
110
+ case "PLAYER_MOVE": {
111
+ const { move, onSolve, onFail, changePuzzle } = action.payload;
112
+
113
+ const isMoveRight = [move?.san, move?.lan].includes(
114
+ state?.nextMove || "",
115
+ );
116
+ const isPuzzleSolved =
117
+ state.currentMoveIndex === state.puzzle.moves.length - 1;
118
+
119
+ if (!isMoveRight) {
120
+ if (onFail) {
121
+ onFail(changePuzzle);
122
+ }
123
+ return {
124
+ ...state,
125
+ status: "failed",
126
+ nextMove: null,
127
+ hint: "none",
128
+ isPlayerTurn: false,
129
+ };
130
+ }
131
+
132
+ if (isPuzzleSolved) {
133
+ if (onSolve) {
134
+ onSolve(changePuzzle);
135
+ }
136
+
137
+ return {
138
+ ...state,
139
+ status: "solved",
140
+ nextMove: null,
141
+ hint: "none",
142
+ isPlayerTurn: false,
143
+ };
144
+ }
145
+
146
+ return {
147
+ ...state,
148
+ hint: "none",
149
+ currentMoveIndex: state.currentMoveIndex + 1,
150
+ nextMove: state.puzzle.moves[state.currentMoveIndex + 1],
151
+ status: "in-progress",
152
+ needCpuMove: true,
153
+ isPlayerTurn: false,
154
+ };
155
+ }
156
+
157
+ default:
158
+ return state;
159
+ }
160
+ };
@@ -0,0 +1,85 @@
1
+ import { useEffect, useReducer } from "react";
2
+ import { initializePuzzle, reducer } from "./reducer";
3
+ import { type Puzzle } from "../utils";
4
+ import { useChessGameContext } from "@react-chess-tools/react-chess-game";
5
+
6
+ export const useChessPuzzle = (
7
+ puzzle: Puzzle,
8
+ onSolve?: (changePuzzle: (puzzle: Puzzle) => void) => void,
9
+ onFail?: (changePuzzle: (puzzle: Puzzle) => void) => void,
10
+ ) => {
11
+ const gameContext = useChessGameContext();
12
+
13
+ const [state, dispatch] = useReducer(
14
+ reducer,
15
+ { puzzle, setPosition: gameContext?.methods.setPosition ?? (() => {}) },
16
+ initializePuzzle,
17
+ );
18
+
19
+ const {
20
+ game,
21
+ methods: { makeMove, setPosition },
22
+ } = gameContext;
23
+
24
+ useEffect(() => {
25
+ if (gameContext && game.fen() === puzzle.fen && state.needCpuMove) {
26
+ setTimeout(
27
+ () =>
28
+ dispatch({
29
+ type: "CPU_MOVE",
30
+ payload: {
31
+ makeMove,
32
+ },
33
+ }),
34
+ 0,
35
+ );
36
+ }
37
+ }, [gameContext, state.needCpuMove]);
38
+
39
+ if (!gameContext) {
40
+ throw new Error("useChessPuzzle must be used within a ChessGameContext");
41
+ }
42
+
43
+ const changePuzzle = (puzzle: Puzzle) => {
44
+ dispatch({ type: "INITIALIZE", payload: { puzzle, setPosition } });
45
+ };
46
+
47
+ useEffect(() => {
48
+ if (game?.history()?.length <= 0 + (puzzle.makeFirstMove ? 1 : 0)) {
49
+ return;
50
+ }
51
+ if (game.history().length % 2 === (puzzle.makeFirstMove ? 0 : 1)) {
52
+ dispatch({
53
+ type: "PLAYER_MOVE",
54
+ payload: {
55
+ move: gameContext?.game?.history({ verbose: true })?.pop() ?? null,
56
+ onSolve,
57
+ onFail,
58
+ changePuzzle,
59
+ game: game,
60
+ },
61
+ });
62
+
63
+ dispatch({
64
+ type: "CPU_MOVE",
65
+ payload: {
66
+ makeMove,
67
+ },
68
+ });
69
+ }
70
+ }, [game?.history()?.length]);
71
+
72
+ const onHint = () => {
73
+ dispatch({ type: "TOGGLE_HINT" });
74
+ };
75
+
76
+ return {
77
+ status: state.status,
78
+ changePuzzle,
79
+ puzzle,
80
+ hint: state.hint,
81
+ onHint,
82
+ nextMove: state.nextMove,
83
+ isPlayerTurn: state.isPlayerTurn,
84
+ };
85
+ };
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import { useChessPuzzle } from "./useChessPuzzle";
3
+
4
+ export const ChessPuzzleContext = React.createContext<ReturnType<
5
+ typeof useChessPuzzle
6
+ > | null>(null);
7
+
8
+ export const useChessPuzzleContext = () => {
9
+ const context = React.useContext(ChessPuzzleContext);
10
+ if (!context) {
11
+ throw new Error(
12
+ "useChessGameContext must be used within a ChessGameProvider",
13
+ );
14
+ }
15
+ return context;
16
+ };
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { ChessPuzzle } from "./components/ChessPuzzle";
2
+ export { useChessPuzzleContext } from "./hooks/useChessPuzzleContext";
@@ -0,0 +1,103 @@
1
+ import { type Color, Chess, Move } from "chess.js";
2
+ import React, { CSSProperties, ReactElement, ReactNode } from "react";
3
+ import _ from "lodash";
4
+
5
+ export type Status = "not-started" | "in-progress" | "solved" | "failed";
6
+
7
+ export type Hint = "none" | "piece" | "move";
8
+
9
+ export type Puzzle = {
10
+ fen: string;
11
+ moves: string[];
12
+ // if the first move of the puzzle has to be made by the cpu, as in chess.com puzzles
13
+ makeFirstMove?: boolean;
14
+ };
15
+
16
+ const FAIL_COLOR = "rgba(201, 52, 48, 0.5)";
17
+ const SUCCESS_COLOR = "rgba(172, 206, 89, 0.5)";
18
+ const HINT_COLOR = "rgba(27, 172, 166, 0.5)";
19
+
20
+ export const getOrientation = (puzzle: Puzzle): Color => {
21
+ const fen = puzzle.fen;
22
+ const game = new Chess(fen);
23
+ if (puzzle.makeFirstMove) {
24
+ game.move(puzzle.moves[0]);
25
+ }
26
+ return game.turn();
27
+ };
28
+
29
+ interface ClickableElement extends ReactElement {
30
+ props: {
31
+ onClick?: () => void;
32
+ };
33
+ }
34
+
35
+ export const isClickableElement = (
36
+ element: ReactNode,
37
+ ): element is ClickableElement => React.isValidElement(element);
38
+
39
+ export const getCustomSquareStyles = (
40
+ status: Status,
41
+ hint: Hint,
42
+ isPlayerTurn: boolean,
43
+ game: Chess,
44
+ nextMove?: Move | null,
45
+ ) => {
46
+ const customSquareStyles: Record<string, CSSProperties> = {};
47
+
48
+ const lastMove = _.last(game.history({ verbose: true }));
49
+
50
+ if (status === "failed" && lastMove) {
51
+ customSquareStyles[lastMove.from] = {
52
+ backgroundColor: FAIL_COLOR,
53
+ };
54
+ customSquareStyles[lastMove.to] = {
55
+ backgroundColor: FAIL_COLOR,
56
+ };
57
+ }
58
+
59
+ if (
60
+ lastMove &&
61
+ (status === "solved" || (status !== "failed" && !isPlayerTurn))
62
+ ) {
63
+ customSquareStyles[lastMove.from] = {
64
+ backgroundColor: SUCCESS_COLOR,
65
+ };
66
+ customSquareStyles[lastMove.to] = {
67
+ backgroundColor: SUCCESS_COLOR,
68
+ };
69
+ }
70
+
71
+ if (hint === "piece") {
72
+ if (nextMove) {
73
+ customSquareStyles[nextMove.from] = {
74
+ backgroundColor: HINT_COLOR,
75
+ };
76
+ }
77
+ }
78
+
79
+ if (hint === "move") {
80
+ if (nextMove) {
81
+ customSquareStyles[nextMove.from] = {
82
+ backgroundColor: HINT_COLOR,
83
+ };
84
+ customSquareStyles[nextMove.to] = {
85
+ backgroundColor: HINT_COLOR,
86
+ };
87
+ }
88
+ }
89
+
90
+ return customSquareStyles;
91
+ };
92
+
93
+ export const stringToMove = (game: Chess, move: string | null | undefined) => {
94
+ const copy = new Chess(game.fen());
95
+ if (move === null || move === undefined) {
96
+ return null;
97
+ }
98
+ try {
99
+ return copy.move(move);
100
+ } catch (e) {
101
+ return null;
102
+ }
103
+ };