UNPKG

@react-chess-tools/react-chess-puzzle

Version:

A lightweight, customizable React component library for rendering and interacting with chess puzzles.

510 lines (493 loc) 15.7 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { ChessPuzzle: () => ChessPuzzle, ChessPuzzleThemeContext: () => ChessPuzzleThemeContext, defaultPuzzleTheme: () => defaultPuzzleTheme, mergePuzzleTheme: () => mergePuzzleTheme, useChessPuzzleContext: () => useChessPuzzleContext, useChessPuzzleTheme: () => useChessPuzzleTheme }); module.exports = __toCommonJS(index_exports); // src/components/ChessPuzzle/parts/Root.tsx var import_react5 = __toESM(require("react"), 1); // src/utils/index.ts var import_chess = require("chess.js"); var import_react = __toESM(require("react"), 1); var import_lodash = __toESM(require("lodash"), 1); // src/theme/defaults.ts var import_react_chess_game = require("@react-chess-tools/react-chess-game"); var defaultPuzzleTheme = { ...import_react_chess_game.defaultGameTheme, puzzle: { success: "rgba(172, 206, 89, 0.5)", failure: "rgba(201, 52, 48, 0.5)", hint: "rgba(27, 172, 166, 0.5)" } }; // src/utils/index.ts var getOrientation = (puzzle) => { const fen = puzzle.fen; const game = new import_chess.Chess(fen); if (puzzle.makeFirstMove) { game.move(puzzle.moves[0]); } return game.turn(); }; var isClickableElement = (element) => import_react.default.isValidElement(element); var getCustomSquareStyles = (status, hint, isPlayerTurn, game, nextMove, theme = defaultPuzzleTheme) => { const customSquareStyles = {}; const lastMove = import_lodash.default.last(game.history({ verbose: true })); if (status === "failed" && lastMove) { customSquareStyles[lastMove.from] = { backgroundColor: theme.puzzle.failure }; customSquareStyles[lastMove.to] = { backgroundColor: theme.puzzle.failure }; } if (lastMove && (status === "solved" || status !== "failed" && !isPlayerTurn)) { customSquareStyles[lastMove.from] = { backgroundColor: theme.puzzle.success }; customSquareStyles[lastMove.to] = { backgroundColor: theme.puzzle.success }; } if (hint === "piece") { if (nextMove) { customSquareStyles[nextMove.from] = { backgroundColor: theme.puzzle.hint }; } } if (hint === "move") { if (nextMove) { customSquareStyles[nextMove.from] = { backgroundColor: theme.puzzle.hint }; customSquareStyles[nextMove.to] = { backgroundColor: theme.puzzle.hint }; } } return customSquareStyles; }; var stringToMove = (game, move) => { const copy = new import_chess.Chess(game.fen()); if (move === null || move === void 0) { return null; } try { return copy.move(move); } catch (e) { return null; } }; // src/hooks/useChessPuzzle.ts var import_react2 = require("react"); // src/hooks/reducer.ts var initializePuzzle = ({ puzzle }) => { return { puzzle, currentMoveIndex: 0, status: "not-started", nextMove: puzzle.moves[0], hint: "none", cpuMove: null, needCpuMove: !!puzzle.makeFirstMove, isPlayerTurn: !puzzle.makeFirstMove, onSolveInvoked: false, onFailInvoked: false }; }; var reducer = (state, action) => { switch (action.type) { case "INITIALIZE": return { ...state, ...initializePuzzle(action.payload) }; case "RESET": return { ...state, ...initializePuzzle({ puzzle: state.puzzle }) }; case "TOGGLE_HINT": if (state.hint === "none") { return { ...state, hint: "piece" }; } return { ...state, hint: "move" }; case "CPU_MOVE": if (state.isPlayerTurn) { return state; } if (["solved", "failed"].includes(state.status)) { return state; } return { ...state, currentMoveIndex: state.currentMoveIndex + 1, cpuMove: state.puzzle.moves[state.currentMoveIndex], nextMove: state.currentMoveIndex < state.puzzle.moves.length - 1 ? state.puzzle.moves[state.currentMoveIndex + 1] : null, needCpuMove: false, isPlayerTurn: true, status: "in-progress" }; case "PLAYER_MOVE": { const { move } = action.payload; const isMoveRight = [move == null ? void 0 : move.san, move == null ? void 0 : move.lan].includes( (state == null ? void 0 : state.nextMove) || "" ); const isPuzzleSolved = state.currentMoveIndex === state.puzzle.moves.length - 1; if (!isMoveRight) { return { ...state, status: "failed", nextMove: null, hint: "none", isPlayerTurn: false, onFailInvoked: false }; } if (isPuzzleSolved) { return { ...state, status: "solved", nextMove: null, hint: "none", isPlayerTurn: false, onSolveInvoked: false }; } return { ...state, hint: "none", currentMoveIndex: state.currentMoveIndex + 1, nextMove: state.puzzle.moves[state.currentMoveIndex + 1], status: "in-progress", needCpuMove: true, isPlayerTurn: false }; } case "MARK_SOLVE_INVOKED": return { ...state, onSolveInvoked: true }; case "MARK_FAIL_INVOKED": return { ...state, onFailInvoked: true }; default: return state; } }; // src/hooks/useChessPuzzle.ts var import_react_chess_game2 = require("@react-chess-tools/react-chess-game"); var useChessPuzzle = (puzzle, onSolve, onFail) => { var _a; const gameContext = (0, import_react_chess_game2.useChessGameContext)(); const [state, dispatch] = (0, import_react2.useReducer)(reducer, { puzzle }, initializePuzzle); const { game, methods: { makeMove, setPosition } } = gameContext; const changePuzzle = (0, import_react2.useCallback)( (puzzle2) => { setPosition(puzzle2.fen, getOrientation(puzzle2)); dispatch({ type: "INITIALIZE", payload: { puzzle: puzzle2 } }); }, [setPosition] ); (0, import_react2.useEffect)(() => { changePuzzle(puzzle); }, [JSON.stringify(puzzle), changePuzzle]); (0, import_react2.useEffect)(() => { if (gameContext && game.fen() === puzzle.fen && state.needCpuMove) { setTimeout( () => dispatch({ type: "CPU_MOVE" }), 0 ); } }, [gameContext, state.needCpuMove]); (0, import_react2.useEffect)(() => { if (state.cpuMove) { makeMove(state.cpuMove); } }, [state.cpuMove]); if (!gameContext) { throw new Error("useChessPuzzle must be used within a ChessGameContext"); } const onHint = (0, import_react2.useCallback)(() => { dispatch({ type: "TOGGLE_HINT" }); }, []); const resetPuzzle = (0, import_react2.useCallback)(() => { changePuzzle(puzzle); }, [changePuzzle, puzzle]); const puzzleContext = (0, import_react2.useMemo)( () => ({ status: state.status, changePuzzle, resetPuzzle, puzzle, hint: state.hint, onHint, nextMove: state.nextMove, isPlayerTurn: state.isPlayerTurn, puzzleState: state.status, movesPlayed: state.currentMoveIndex, totalMoves: puzzle.moves.length }), [ state.status, changePuzzle, resetPuzzle, puzzle, state.hint, onHint, state.nextMove, state.isPlayerTurn, state.currentMoveIndex ] ); (0, import_react2.useEffect)(() => { var _a2, _b, _c; if (((_a2 = game == null ? void 0 : game.history()) == null ? void 0 : _a2.length) <= 0 + (puzzle.makeFirstMove ? 1 : 0)) { return; } if (game.history().length % 2 === (puzzle.makeFirstMove ? 0 : 1)) { dispatch({ type: "PLAYER_MOVE", payload: { move: ((_c = (_b = gameContext == null ? void 0 : gameContext.game) == null ? void 0 : _b.history({ verbose: true })) == null ? void 0 : _c.pop()) ?? null, puzzleContext, game } }); dispatch({ type: "CPU_MOVE" }); } }, [(_a = game == null ? void 0 : game.history()) == null ? void 0 : _a.length]); (0, import_react2.useEffect)(() => { if (state.status === "solved" && !state.onSolveInvoked && onSolve) { onSolve(puzzleContext); dispatch({ type: "MARK_SOLVE_INVOKED" }); } }, [state.status, state.onSolveInvoked]); (0, import_react2.useEffect)(() => { if (state.status === "failed" && !state.onFailInvoked && onFail) { onFail(puzzleContext); dispatch({ type: "MARK_FAIL_INVOKED" }); } }, [state.status, state.onFailInvoked]); return puzzleContext; }; // src/components/ChessPuzzle/parts/Root.tsx var import_react_chess_game3 = require("@react-chess-tools/react-chess-game"); // src/hooks/useChessPuzzleContext.ts var import_react3 = __toESM(require("react"), 1); var ChessPuzzleContext = import_react3.default.createContext(null); var useChessPuzzleContext = () => { const context = import_react3.default.useContext(ChessPuzzleContext); if (!context) { throw new Error( `useChessPuzzleContext must be used within a ChessPuzzle component. Make sure your component is wrapped with <ChessPuzzle.Root> or ensure the ChessPuzzle component is properly rendered in the component tree.` ); } return context; }; // src/theme/context.tsx var import_react4 = __toESM(require("react"), 1); var ChessPuzzleThemeContext = (0, import_react4.createContext)(defaultPuzzleTheme); var useChessPuzzleTheme = () => { return (0, import_react4.useContext)(ChessPuzzleThemeContext); }; var PuzzleThemeProvider = ({ theme, children }) => { return /* @__PURE__ */ import_react4.default.createElement(ChessPuzzleThemeContext.Provider, { value: theme }, children); }; // src/theme/utils.ts var import_lodash2 = require("lodash"); var mergePuzzleTheme = (partialTheme) => { if (!partialTheme) { return { ...defaultPuzzleTheme }; } return (0, import_lodash2.merge)({}, defaultPuzzleTheme, partialTheme); }; // src/components/ChessPuzzle/parts/Root.tsx var PuzzleRootInner = ({ puzzle, onSolve, onFail, children }) => { const context = useChessPuzzle(puzzle, onSolve, onFail); return /* @__PURE__ */ import_react5.default.createElement(ChessPuzzleContext.Provider, { value: context }, children); }; var Root = ({ puzzle, onSolve, onFail, theme, children }) => { const mergedTheme = import_react5.default.useMemo(() => mergePuzzleTheme(theme), [theme]); return /* @__PURE__ */ import_react5.default.createElement( import_react_chess_game3.ChessGame.Root, { fen: puzzle.fen, orientation: getOrientation(puzzle), theme: mergedTheme }, /* @__PURE__ */ import_react5.default.createElement(PuzzleThemeProvider, { theme: mergedTheme }, /* @__PURE__ */ import_react5.default.createElement(PuzzleRootInner, { puzzle, onSolve, onFail }, children)) ); }; // src/components/ChessPuzzle/parts/PuzzleBoard.tsx var import_react6 = __toESM(require("react"), 1); var import_react_chess_game4 = require("@react-chess-tools/react-chess-game"); var PuzzleBoard = ({ options = {}, ...rest }) => { const puzzleContext = useChessPuzzleContext(); const gameContext = (0, import_react_chess_game4.useChessGameContext)(); const theme = useChessPuzzleTheme(); if (!puzzleContext) { throw new Error("PuzzleContext not found"); } if (!gameContext) { throw new Error("ChessGameContext not found"); } const { game } = gameContext; const { status, hint, isPlayerTurn, nextMove } = puzzleContext; const mergedOptions = (0, import_react_chess_game4.deepMergeChessboardOptions)(options, { squareStyles: getCustomSquareStyles( status, hint, isPlayerTurn, game, stringToMove(game, nextMove), theme ) }); return /* @__PURE__ */ import_react6.default.createElement(import_react_chess_game4.ChessGame.Board, { ...rest, options: mergedOptions }); }; // src/components/ChessPuzzle/parts/Reset.tsx var import_react7 = __toESM(require("react"), 1); var defaultShowOn = ["failed", "solved"]; var Reset = ({ children, asChild, puzzle, onReset, showOn = defaultShowOn }) => { const puzzleContext = useChessPuzzleContext(); if (!puzzleContext) { throw new Error("PuzzleContext not found"); } const { changePuzzle, status } = puzzleContext; const handleClick = () => { changePuzzle(puzzle || puzzleContext.puzzle); onReset == null ? void 0 : onReset(puzzleContext); }; if (!showOn.includes(status)) { return null; } if (asChild) { const child = import_react7.default.Children.only(children); if (isClickableElement(child)) { return import_react7.default.cloneElement(child, { onClick: handleClick }); } else { throw new Error("Change child must be a clickable element"); } } return /* @__PURE__ */ import_react7.default.createElement("button", { type: "button", onClick: handleClick }, children); }; // src/components/ChessPuzzle/parts/Hint.tsx var import_react8 = __toESM(require("react"), 1); var defaultShowOn2 = ["not-started", "in-progress"]; var Hint = ({ children, asChild, showOn = defaultShowOn2 }) => { const puzzleContext = useChessPuzzleContext(); if (!puzzleContext) { throw new Error("PuzzleContext not found"); } const { onHint, status } = puzzleContext; const handleClick = () => { onHint(); }; if (!showOn.includes(status)) { return null; } if (asChild) { const child = import_react8.default.Children.only(children); if (isClickableElement(child)) { return import_react8.default.cloneElement(child, { onClick: handleClick }); } else { throw new Error("Change child must be a clickable element"); } } return /* @__PURE__ */ import_react8.default.createElement("button", { type: "button", onClick: handleClick }, children); }; // src/components/ChessPuzzle/index.ts var ChessPuzzle = { Root, Board: PuzzleBoard, Reset, Hint }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ChessPuzzle, ChessPuzzleThemeContext, defaultPuzzleTheme, mergePuzzleTheme, useChessPuzzleContext, useChessPuzzleTheme }); //# sourceMappingURL=index.cjs.map