@react-chess-puzzle-fix/react-chess-puzzle
Version:
A lightweight, customizable React component library for rendering and interacting with chess puzzles.
406 lines (395 loc) • 11 kB
JavaScript
// src/components/ChessPuzzle/parts/Root.tsx
import React3 from "react";
// src/utils/index.ts
import { Chess } from "chess.js";
import React from "react";
import _ from "lodash";
var FAIL_COLOR = "rgba(201, 52, 48, 0.5)";
var SUCCESS_COLOR = "rgba(172, 206, 89, 0.5)";
var HINT_COLOR = "rgba(27, 172, 166, 0.5)";
var getOrientation = (puzzle) => {
const fen = puzzle.fen;
const game = new Chess(fen);
if (puzzle.makeFirstMove) {
game.move(puzzle.moves[0]);
}
return game.turn();
};
var isClickableElement = (element) => React.isValidElement(element);
var getCustomSquareStyles = (status, hint, isPlayerTurn, game, nextMove) => {
const customSquareStyles = {};
const lastMove = _.last(game.history({ verbose: true }));
if (status === "failed" && lastMove) {
customSquareStyles[lastMove.from] = {
backgroundColor: FAIL_COLOR
};
customSquareStyles[lastMove.to] = {
backgroundColor: FAIL_COLOR
};
}
if (lastMove && (status === "solved" || status !== "failed" && !isPlayerTurn)) {
customSquareStyles[lastMove.from] = {
backgroundColor: SUCCESS_COLOR
};
customSquareStyles[lastMove.to] = {
backgroundColor: SUCCESS_COLOR
};
}
if (hint === "piece") {
if (nextMove) {
customSquareStyles[nextMove.from] = {
backgroundColor: HINT_COLOR
};
}
}
if (hint === "move") {
if (nextMove) {
customSquareStyles[nextMove.from] = {
backgroundColor: HINT_COLOR
};
customSquareStyles[nextMove.to] = {
backgroundColor: HINT_COLOR
};
}
}
return customSquareStyles;
};
var stringToMove = (game, move) => {
const copy = new Chess(game.fen());
if (move === null || move === void 0) {
return null;
}
try {
return copy.move(move);
} catch (e) {
return null;
}
};
// src/hooks/useChessPuzzle.ts
import { useEffect, useReducer } from "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
};
};
var autoRetryPuzzle = ({ puzzle }, currentMove) => {
return {
puzzle,
currentMoveIndex: currentMove,
status: "in-progress",
nextMove: puzzle.moves[currentMove],
hint: "none",
cpuMove: null,
needCpuMove: false,
isPlayerTurn: true
};
};
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, onSolve, onFail, changePuzzle } = 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) {
if (onFail) {
onFail(changePuzzle);
}
return {
...state,
status: "failed",
nextMove: null,
hint: "none",
isPlayerTurn: false
};
}
if (isPuzzleSolved) {
if (onSolve) {
onSolve(changePuzzle);
}
return {
...state,
status: "solved",
nextMove: null,
hint: "none",
isPlayerTurn: false
};
}
return {
...state,
hint: "none",
currentMoveIndex: state.currentMoveIndex + 1,
nextMove: state.puzzle.moves[state.currentMoveIndex + 1],
status: "in-progress",
needCpuMove: true,
isPlayerTurn: false
};
}
case "AUTO_RETRY": {
return {
...state,
...autoRetryPuzzle(action.payload, state.currentMoveIndex)
};
}
default:
return state;
}
};
// src/hooks/useChessPuzzle.ts
import { useChessGameContext } from "react-chess-game-fix";
var ANIMATION_MS = 300;
var useChessPuzzle = (puzzle, onSolve, onFail) => {
var _a;
const gameContext = useChessGameContext();
const [state, dispatch] = useReducer(reducer, { puzzle }, initializePuzzle);
const {
game,
methods: { makeMove, setPosition },
currentFen
} = gameContext;
useEffect(() => {
if (gameContext && game.fen() !== puzzle.fen) {
setPosition(puzzle.fen, getOrientation(puzzle));
}
}, []);
const changePuzzle = (puzzle2) => {
dispatch({ type: "INITIALIZE", payload: { puzzle: puzzle2 } });
setPosition(puzzle2.fen, getOrientation(puzzle2));
};
const autoRetry = () => {
dispatch({ type: "AUTO_RETRY", payload: { puzzle } });
};
useEffect(() => {
if (gameContext && game.fen() === puzzle.fen && state.needCpuMove) {
setTimeout(
() => dispatch({
type: "CPU_MOVE"
}),
0
);
}
}, [gameContext, state.needCpuMove]);
useEffect(() => {
if (state.cpuMove) {
makeMove(state.cpuMove);
}
}, [state.cpuMove]);
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,
onSolve,
onFail,
changePuzzle,
game
}
});
setTimeout(() => dispatch({ type: "CPU_MOVE" }), ANIMATION_MS + 20);
}
}, [(_a = game == null ? void 0 : game.history()) == null ? void 0 : _a.length]);
if (!gameContext) {
throw new Error("useChessPuzzle must be used within a ChessGameContext");
}
const onHint = () => {
dispatch({ type: "TOGGLE_HINT" });
};
return {
status: state.status,
changePuzzle,
autoRetry,
currentMoveIndex: state.currentMoveIndex,
puzzle,
hint: state.hint,
onHint,
nextMove: state.nextMove,
isPlayerTurn: state.isPlayerTurn
};
};
// src/components/ChessPuzzle/parts/Root.tsx
import { ChessGame } from "react-chess-game-fix";
// src/hooks/useChessPuzzleContext.ts
import React2 from "react";
var ChessPuzzleContext = React2.createContext(null);
var useChessPuzzleContext = () => {
const context = React2.useContext(ChessPuzzleContext);
if (!context) {
throw new Error(
"useChessGameContext must be used within a ChessGameProvider"
);
}
return context;
};
// src/components/ChessPuzzle/parts/Root.tsx
var PuzzleRoot = ({
puzzle,
onSolve,
onFail,
children
}) => {
const context = useChessPuzzle(puzzle, onSolve, onFail);
return /* @__PURE__ */ React3.createElement(ChessPuzzleContext.Provider, { value: context }, children);
};
var Root = ({
puzzle,
onSolve,
onFail,
children
}) => {
return /* @__PURE__ */ React3.createElement(ChessGame.Root, { fen: puzzle.fen, orientation: getOrientation(puzzle) }, /* @__PURE__ */ React3.createElement(PuzzleRoot, { puzzle, onSolve, onFail }, children));
};
// src/components/ChessPuzzle/parts/PuzzleBoard.tsx
import React4 from "react";
import {
ChessGame as ChessGame2,
useChessGameContext as useChessGameContext2
} from "react-chess-game-fix";
var PuzzleBoard = ({ ...rest }) => {
const puzzleContext = useChessPuzzleContext();
const gameContext = useChessGameContext2();
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;
return /* @__PURE__ */ React4.createElement(
ChessGame2.Board,
{
customSquareStyles: getCustomSquareStyles(
status,
hint,
isPlayerTurn,
game,
stringToMove(game, nextMove)
),
...rest
}
);
};
// src/components/ChessPuzzle/parts/Reset.tsx
import React5 from "react";
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();
};
if (!showOn.includes(status)) {
return null;
}
if (asChild) {
const child = React5.Children.only(children);
if (isClickableElement(child)) {
return React5.cloneElement(child, {
onClick: handleClick
});
} else {
throw new Error("Change child must be a clickable element");
}
}
return /* @__PURE__ */ React5.createElement("button", { type: "button", onClick: handleClick }, children);
};
// src/components/ChessPuzzle/parts/Hint.tsx
import React6 from "react";
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 = React6.Children.only(children);
if (isClickableElement(child)) {
return React6.cloneElement(child, {
onClick: handleClick
});
} else {
throw new Error("Change child must be a clickable element");
}
}
return /* @__PURE__ */ React6.createElement("button", { type: "button", onClick: handleClick }, children);
};
// src/components/ChessPuzzle/index.ts
var ChessPuzzle = {
Root,
Board: PuzzleBoard,
Reset,
Hint
};
export {
ChessPuzzle,
useChessPuzzleContext
};
//# sourceMappingURL=index.mjs.map