feat : play vs engine new page init

This commit is contained in:
GuillaumeSD
2024-03-18 02:48:34 +01:00
parent 1893937c4a
commit cd927ed6d7
11 changed files with 349 additions and 3 deletions

View File

@@ -12,10 +12,11 @@ import {
} from "@mui/material";
const MenuOptions = [
{ text: "Play", icon: "streamline:chess-pawn", href: "/play" },
{ text: "Analysis", icon: "streamline:magnifying-glass-solid", href: "/" },
{
text: "Database",
icon: "streamline:database-solid",
icon: "streamline:database",
href: "/database",
},
];

View File

@@ -0,0 +1,112 @@
import { Grid } from "@mui/material";
import { Chessboard } from "react-chessboard";
import { useAtomValue, useSetAtom } from "jotai";
import {
clickedSquaresAtom,
engineSkillLevelAtom,
gameAtom,
playerColorAtom,
} from "../states";
import { Square } from "react-chessboard/dist/chessboard/types";
import { useChessActions } from "@/hooks/useChessActions";
import { useEffect, useRef } from "react";
import PlayerInfo from "./playerInfo";
import { useScreenSize } from "@/hooks/useScreenSize";
import { Color, EngineName } from "@/types/enums";
import SquareRenderer from "./squareRenderer";
import { useEngine } from "@/hooks/useEngine";
import { uciMoveParams } from "@/lib/chess";
export default function Board() {
const boardRef = useRef<HTMLDivElement>(null);
const { boardSize } = useScreenSize();
const game = useAtomValue(gameAtom);
const playerColor = useAtomValue(playerColorAtom);
const { makeMove: makeBoardMove } = useChessActions(gameAtom);
const setClickedSquares = useSetAtom(clickedSquaresAtom);
const engineSkillLevel = useAtomValue(engineSkillLevelAtom);
const engine = useEngine(EngineName.Stockfish16);
const gameFen = game.fen();
const turn = game.turn();
useEffect(() => {
const playEngineMove = async () => {
if (!engine?.isReady() || turn === playerColor) return;
const move = await engine.getEngineNextMove(
gameFen,
engineSkillLevel - 1
);
makeBoardMove(uciMoveParams(move));
};
playEngineMove();
}, [engine, turn, engine, playerColor, engineSkillLevel]);
useEffect(() => {
setClickedSquares([]);
}, [gameFen, setClickedSquares]);
const onPieceDrop = (
source: Square,
target: Square,
piece: string
): boolean => {
if (!piece || piece[0] !== playerColor) return false;
try {
const result = makeBoardMove({
from: source,
to: target,
promotion: piece[1]?.toLowerCase() ?? "q",
});
return !!result;
} catch {
return false;
}
};
const isPieceDraggable = ({ piece }: { piece: string }): boolean => {
if (!piece) return false;
return playerColor === piece[0];
};
return (
<Grid
item
container
rowGap={1}
justifyContent="center"
alignItems="center"
width={boardSize}
maxWidth="85vh"
>
<PlayerInfo
color={playerColor === Color.White ? Color.Black : Color.White}
/>
<Grid
item
container
justifyContent="center"
alignItems="center"
ref={boardRef}
xs={12}
>
<Chessboard
id="AnalysisBoard"
position={gameFen}
onPieceDrop={onPieceDrop}
boardOrientation={playerColor ? "white" : "black"}
customBoardStyle={{
borderRadius: "5px",
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)",
}}
isDraggablePiece={isPieceDraggable}
customSquare={SquareRenderer}
/>
</Grid>
<PlayerInfo color={playerColor} />
</Grid>
);
}

View File

@@ -0,0 +1,20 @@
import { Grid, Typography } from "@mui/material";
import { useAtomValue } from "jotai";
import { playerColorAtom } from "../states";
import { Color } from "@/types/enums";
interface Props {
color: Color;
}
export default function PlayerInfo({ color }: Props) {
const playerColor = useAtomValue(playerColorAtom);
const playerName = playerColor === color ? "You 🧠" : "Stockfish 🤖";
return (
<Grid item container xs={12} justifyContent="center" alignItems="center">
<Typography variant="h6">{playerName}</Typography>
</Grid>
);
}

View File

@@ -0,0 +1,78 @@
import { clickedSquaresAtom, gameAtom } from "../states";
import { atom, useAtom, useAtomValue } from "jotai";
import { CSSProperties, MouseEventHandler, forwardRef } from "react";
import { CustomSquareProps } from "react-chessboard/dist/chessboard/types";
const rightClickEventSquareAtom = atom<string | null>(null);
const SquareRenderer = forwardRef<HTMLDivElement, CustomSquareProps>(
(props, ref) => {
const { children, square, style } = props;
const game = useAtomValue(gameAtom);
const [clickedSquares, setClickedSquares] = useAtom(clickedSquaresAtom);
const [rightClickEventSquare, setRightClickEventSquare] = useAtom(
rightClickEventSquareAtom
);
const lastMove = game.history({ verbose: true }).at(-1);
const fromSquare = lastMove?.from;
const toSquare = lastMove?.to;
const customSquareStyle: CSSProperties | undefined =
clickedSquares.includes(square)
? {
position: "absolute",
width: "100%",
height: "100%",
backgroundColor: "#eb6150",
opacity: "0.8",
}
: fromSquare === square || toSquare === square
? {
position: "absolute",
width: "100%",
height: "100%",
backgroundColor: "#fad541",
opacity: 0.5,
}
: undefined;
const handleSquareLeftClick: MouseEventHandler<HTMLDivElement> = () => {
setClickedSquares([]);
};
const handleSquareRightClick: MouseEventHandler<HTMLDivElement> = (
event
) => {
if (event.button !== 2) return;
if (rightClickEventSquare !== square) {
setRightClickEventSquare(null);
return;
}
setClickedSquares((prev) =>
prev.includes(square)
? prev.filter((s) => s !== square)
: [...prev, square]
);
};
return (
<div
ref={ref}
style={{ ...style, position: "relative" }}
onClick={handleSquareLeftClick}
onMouseDown={(e) => e.button === 2 && setRightClickEventSquare(square)}
onMouseUp={handleSquareRightClick}
>
{children}
{customSquareStyle && <div style={customSquareStyle} />}
</div>
);
}
);
SquareRenderer.displayName = "CustomSquareRenderer";
export default SquareRenderer;

View File

@@ -0,0 +1,9 @@
import { Color } from "@/types/enums";
import { Chess } from "chess.js";
import { atom } from "jotai";
export const gameAtom = atom(new Chess());
export const playerColorAtom = atom<Color>(Color.White);
export const engineSkillLevelAtom = atom<number>(1);
export const clickedSquaresAtom = atom<string[]>([]);