@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
JavaScript
;
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