Files
chesskit/src/components/board/index.tsx
GuillaumeSD daaf145fa9 Squashed commit of the following:
commit c769a4d3bfbd22804ea4eeb324e7afeaaaa55f82
Author: GuillaumeSD <gsd.lfny@gmail.com>
Date:   Fri Apr 12 02:49:21 2024 +0200

    style : capturedPieces UI final touches

commit 838ad95bfa1746a3a8de38ace2520fe93ec6a0af
Author: GuillaumeSD <gsd.lfny@gmail.com>
Date:   Thu Apr 11 03:00:01 2024 +0200

    feat : captured pieces wip

commit e4bb4dcc346aa5dea5527fef4389b01673645e76
Merge: 785dba2 e9e772c
Author: GuillaumeSD <gsd.lfny@gmail.com>
Date:   Thu Apr 11 01:15:31 2024 +0200

    Merge branch 'main' into feat/add-board-captured-pieces

commit 785dba28509ac04655d4cab736a18a821ca52433
Author: GuillaumeSD <gsd.lfny@gmail.com>
Date:   Tue Apr 9 02:25:29 2024 +0200

    feat : add board captured pieces init
2024-04-12 02:49:48 +02:00

297 lines
7.9 KiB
TypeScript

import { Grid, Typography } from "@mui/material";
import { Chessboard } from "react-chessboard";
import { PrimitiveAtom, atom, useAtomValue, useSetAtom } from "jotai";
import {
Arrow,
CustomSquareRenderer,
PromotionPieceOption,
Square,
} from "react-chessboard/dist/chessboard/types";
import { useChessActions } from "@/hooks/useChessActions";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Color, MoveClassification } from "@/types/enums";
import { Chess } from "chess.js";
import { getSquareRenderer, moveClassificationColors } from "./squareRenderer";
import { CurrentPosition } from "@/types/eval";
import EvaluationBar from "./evaluationBar";
import CapturedPieces from "./capturedPieces";
export interface Props {
id: string;
canPlay?: Color | boolean;
gameAtom: PrimitiveAtom<Chess>;
boardSize?: number;
whitePlayer?: string;
blackPlayer?: string;
boardOrientation?: Color;
currentPositionAtom?: PrimitiveAtom<CurrentPosition>;
showBestMoveArrow?: boolean;
showPlayerMoveIconAtom?: PrimitiveAtom<boolean>;
showEvaluationBar?: boolean;
}
export default function Board({
id: boardId,
canPlay,
gameAtom,
boardSize,
whitePlayer,
blackPlayer,
boardOrientation = Color.White,
currentPositionAtom = atom({}),
showBestMoveArrow = false,
showPlayerMoveIconAtom,
showEvaluationBar = false,
}: Props) {
const boardRef = useRef<HTMLDivElement>(null);
const game = useAtomValue(gameAtom);
const { makeMove: makeGameMove } = useChessActions(gameAtom);
const clickedSquaresAtom = useMemo(() => atom<Square[]>([]), []);
const setClickedSquares = useSetAtom(clickedSquaresAtom);
const playableSquaresAtom = useMemo(() => atom<Square[]>([]), []);
const setPlayableSquares = useSetAtom(playableSquaresAtom);
const position = useAtomValue(currentPositionAtom);
const [showPromotionDialog, setShowPromotionDialog] = useState(false);
const [moveClickFrom, setMoveClickFrom] = useState<Square | null>(null);
const [moveClickTo, setMoveClickTo] = useState<Square | null>(null);
const gameFen = game.fen();
useEffect(() => {
setClickedSquares([]);
}, [gameFen, setClickedSquares]);
const isPiecePlayable = useCallback(
({ piece }: { piece: string }): boolean => {
if (game.isGameOver() || !canPlay) return false;
if (canPlay === true || canPlay === piece[0]) return true;
return false;
},
[canPlay, game]
);
const onPieceDrop = (
source: Square,
target: Square,
piece: string
): boolean => {
if (!isPiecePlayable({ piece })) return false;
const result = makeGameMove({
from: source,
to: target,
promotion: piece[1]?.toLowerCase() ?? "q",
});
return !!result;
};
const resetMoveClick = (square?: Square) => {
setMoveClickFrom(square ?? null);
setMoveClickTo(null);
setShowPromotionDialog(false);
if (square) {
const moves = game.moves({ square, verbose: true });
setPlayableSquares(moves.map((m) => m.to));
} else {
setPlayableSquares([]);
}
};
const handleSquareLeftClick = (square: Square, piece?: string) => {
setClickedSquares([]);
if (!moveClickFrom) {
if (piece && !isPiecePlayable({ piece })) return;
resetMoveClick(square);
return;
}
const validMoves = game.moves({ square: moveClickFrom, verbose: true });
const move = validMoves.find((m) => m.to === square);
if (!move) {
resetMoveClick(square);
return;
}
setMoveClickTo(square);
if (
move.piece === "p" &&
((move.color === "w" && square[1] === "8") ||
(move.color === "b" && square[1] === "1"))
) {
setShowPromotionDialog(true);
return;
}
const result = makeGameMove({
from: moveClickFrom,
to: square,
});
resetMoveClick(result ? undefined : square);
};
const handleSquareRightClick = (square: Square) => {
setClickedSquares((prev) =>
prev.includes(square)
? prev.filter((s) => s !== square)
: [...prev, square]
);
};
const handlePieceDragBegin = (_: string, square: Square) => {
resetMoveClick(square);
};
const handlePieceDragEnd = () => {
resetMoveClick();
};
const onPromotionPieceSelect = (piece?: PromotionPieceOption) => {
if (piece && moveClickFrom && moveClickTo) {
const result = makeGameMove({
from: moveClickFrom,
to: moveClickTo,
promotion: piece[1]?.toLowerCase() ?? "q",
});
resetMoveClick();
return !!result;
}
return false;
};
const customArrows: Arrow[] = useMemo(() => {
const bestMove = position?.lastEval?.bestMove;
const moveClassification = position?.eval?.moveClassification;
if (
bestMove &&
showBestMoveArrow &&
moveClassification !== MoveClassification.Book
) {
const bestMoveArrow = [
bestMove.slice(0, 2),
bestMove.slice(2, 4),
moveClassificationColors[MoveClassification.Best],
] as Arrow;
return [bestMoveArrow];
}
return [];
}, [position, showBestMoveArrow]);
const SquareRenderer: CustomSquareRenderer = useMemo(() => {
return getSquareRenderer({
currentPositionAtom: currentPositionAtom,
clickedSquaresAtom,
playableSquaresAtom,
showPlayerMoveIconAtom,
});
}, [
currentPositionAtom,
clickedSquaresAtom,
playableSquaresAtom,
showPlayerMoveIconAtom,
]);
return (
<Grid
item
container
justifyContent="center"
alignItems="center"
wrap="nowrap"
width={boardSize}
>
{showEvaluationBar && (
<EvaluationBar
height={boardRef?.current?.offsetHeight || boardSize || 400}
boardOrientation={boardOrientation}
currentPositionAtom={currentPositionAtom}
/>
)}
<Grid
item
container
rowGap={1}
justifyContent="center"
alignItems="center"
paddingLeft={showEvaluationBar ? 2 : 0}
xs
>
<Grid
item
container
xs={12}
justifyContent="center"
alignItems="center"
columnGap={2}
>
<Typography>
{boardOrientation === Color.White ? blackPlayer : whitePlayer}
</Typography>
<CapturedPieces
gameAtom={gameAtom}
color={boardOrientation === Color.White ? Color.Black : Color.White}
/>
</Grid>
<Grid
item
container
justifyContent="center"
alignItems="center"
ref={boardRef}
xs={12}
>
<Chessboard
id={`${boardId}-${canPlay}`}
position={gameFen}
onPieceDrop={onPieceDrop}
boardOrientation={
boardOrientation === Color.White ? "white" : "black"
}
customBoardStyle={{
borderRadius: "5px",
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)",
}}
customArrows={customArrows}
isDraggablePiece={isPiecePlayable}
customSquare={SquareRenderer}
onSquareClick={handleSquareLeftClick}
onSquareRightClick={handleSquareRightClick}
onPieceDragBegin={handlePieceDragBegin}
onPieceDragEnd={handlePieceDragEnd}
onPromotionPieceSelect={onPromotionPieceSelect}
showPromotionDialog={showPromotionDialog}
promotionToSquare={moveClickTo}
animationDuration={200}
/>
</Grid>
<Grid
item
container
xs={12}
justifyContent="center"
alignItems="center"
columnGap={2}
>
<Typography>
{boardOrientation === Color.White ? whitePlayer : blackPlayer}
</Typography>
<CapturedPieces gameAtom={gameAtom} color={boardOrientation} />
</Grid>
</Grid>
</Grid>
);
}