@react-chess-tools/react-chess-game
Version:
react-chess-game is a React component bridging chess.js with react-chessboard to offer a full-featured, ready-to-integrate chess board experience.
150 lines (133 loc) • 3.51 kB
text/typescript
import React, { useEffect } from "react";
import { Chess, Color } from "chess.js";
import { cloneGame, getCurrentFen, getGameInfo } from "../utils/chess";
export type useChessGameProps = {
fen?: string;
orientation?: Color;
};
export const useChessGame = ({
fen,
orientation: initialOrientation,
}: useChessGameProps = {}) => {
const [game, setGame] = React.useState(() => {
try {
return new Chess(fen);
} catch (e) {
console.error("Invalid FEN:", fen, e);
return new Chess(); // Return empty board
}
});
useEffect(() => {
try {
setGame(new Chess(fen));
} catch (e) {
console.error("Invalid FEN:", fen, e);
setGame(new Chess());
}
}, [fen]);
const [orientation, setOrientation] = React.useState<Color>(
initialOrientation ?? "w",
);
const [currentMoveIndex, setCurrentMoveIndex] = React.useState(-1);
const history = React.useMemo(() => game.history(), [game]);
const isLatestMove = React.useMemo(
() => currentMoveIndex === history.length - 1 || currentMoveIndex === -1,
[currentMoveIndex, history.length],
);
const info = React.useMemo(
() => getGameInfo(game, orientation),
[game, orientation],
);
const currentFen = React.useMemo(
() => getCurrentFen(fen, game, currentMoveIndex),
[game, currentMoveIndex],
);
const currentPosition = React.useMemo(
() => game.history()[currentMoveIndex],
[game, currentMoveIndex],
);
const setPosition = React.useCallback((fen: string, orientation: Color) => {
try {
const newGame = new Chess();
newGame.load(fen);
setOrientation(orientation);
setGame(newGame);
setCurrentMoveIndex(-1);
} catch (e) {
console.error("Failed to load FEN:", fen, e);
}
}, []);
const makeMove = React.useCallback(
(move: Parameters<Chess["move"]>[0]): boolean => {
// Only allow moves when we're at the latest position
if (!isLatestMove) {
return false;
}
try {
const copy = cloneGame(game);
copy.move(move);
setGame(copy);
setCurrentMoveIndex(copy.history().length - 1);
return true;
} catch (e) {
return false;
}
},
[isLatestMove, game],
);
const flipBoard = React.useCallback(() => {
setOrientation((orientation) => (orientation === "w" ? "b" : "w"));
}, []);
const goToMove = React.useCallback(
(moveIndex: number) => {
if (moveIndex < -1 || moveIndex >= history.length) return;
setCurrentMoveIndex(moveIndex);
},
[history.length],
);
const goToStart = React.useCallback(() => goToMove(-1), []);
const goToEnd = React.useCallback(
() => goToMove(history.length - 1),
[history.length],
);
const goToPreviousMove = React.useCallback(
() => goToMove(currentMoveIndex - 1),
[currentMoveIndex],
);
const goToNextMove = React.useCallback(
() => goToMove(currentMoveIndex + 1),
[currentMoveIndex],
);
const methods = React.useMemo(
() => ({
makeMove,
setPosition,
flipBoard,
goToMove,
goToStart,
goToEnd,
goToPreviousMove,
goToNextMove,
}),
[
makeMove,
setPosition,
flipBoard,
goToMove,
goToStart,
goToEnd,
goToPreviousMove,
goToNextMove,
],
);
return {
game,
currentFen,
currentPosition,
orientation,
currentMoveIndex,
isLatestMove,
info,
methods,
};
};