From 438ec601072ba2612217907116d4e4e5dad98f1c Mon Sep 17 00:00:00 2001 From: GuillaumeSD Date: Tue, 19 Mar 2024 03:50:53 +0100 Subject: [PATCH] feat : play vs engine v0 --- src/pages/play.tsx | 25 ++-- src/sections/play/board/index.tsx | 19 ++- src/sections/play/gameInProgress.tsx | 51 +++++++ src/sections/play/gameRecap.tsx | 40 ++++++ .../play/gameSettings/gameSettingsButton.tsx | 20 +++ .../play/gameSettings/gameSettingsDialog.tsx | 135 ++++++++++++++++++ src/sections/play/states.ts | 1 + 7 files changed, 271 insertions(+), 20 deletions(-) create mode 100644 src/sections/play/gameInProgress.tsx create mode 100644 src/sections/play/gameRecap.tsx create mode 100644 src/sections/play/gameSettings/gameSettingsButton.tsx create mode 100644 src/sections/play/gameSettings/gameSettingsDialog.tsx diff --git a/src/pages/play.tsx b/src/pages/play.tsx index d399eec..d371b5e 100644 --- a/src/pages/play.tsx +++ b/src/pages/play.tsx @@ -1,7 +1,14 @@ import Board from "@/sections/play/board"; -import { CircularProgress, Divider, Grid, Typography } from "@mui/material"; +import GameInProgress from "@/sections/play/gameInProgress"; +import GameRecap from "@/sections/play/gameRecap"; +import GameSettingsButton from "@/sections/play/gameSettings/gameSettingsButton"; +import { isGameInProgressAtom } from "@/sections/play/states"; +import { Grid } from "@mui/material"; +import { useAtomValue } from "jotai"; export default function Play() { + const isGameInProgress = useAtomValue(isGameInProgressAtom); + return ( @@ -29,19 +36,9 @@ export default function Play() { maxWidth: "1100px", }} > - - Game in progress - - - - + + {!isGameInProgress && } + ); diff --git a/src/sections/play/board/index.tsx b/src/sections/play/board/index.tsx index 204767d..4000017 100644 --- a/src/sections/play/board/index.tsx +++ b/src/sections/play/board/index.tsx @@ -7,6 +7,7 @@ import { gameAtom, playableSquaresAtom, playerColorAtom, + isGameInProgressAtom, } from "../states"; import { Square } from "react-chessboard/dist/chessboard/types"; import { useChessActions } from "@/hooks/useChessActions"; @@ -23,10 +24,11 @@ export default function Board() { const { boardSize } = useScreenSize(); const game = useAtomValue(gameAtom); const playerColor = useAtomValue(playerColorAtom); - const { makeMove: makeBoardMove } = useChessActions(gameAtom); + const { makeMove: makeGameMove } = useChessActions(gameAtom); const setClickedSquares = useSetAtom(clickedSquaresAtom); const setPlayableSquares = useSetAtom(playableSquaresAtom); const engineSkillLevel = useAtomValue(engineSkillLevelAtom); + const isGameInProgress = useAtomValue(isGameInProgressAtom); const engine = useEngine(EngineName.Stockfish16); const gameFen = game.fen(); @@ -34,21 +36,26 @@ export default function Board() { useEffect(() => { const playEngineMove = async () => { - if (!engine?.isReady() || game.turn() === playerColor || isGameFinished) { + if ( + !engine?.isReady() || + game.turn() === playerColor || + isGameFinished || + !isGameInProgress + ) { return; } const move = await engine.getEngineNextMove( gameFen, engineSkillLevel - 1 ); - if (move) makeBoardMove(uciMoveParams(move)); + if (move) makeGameMove(uciMoveParams(move)); }; playEngineMove(); return () => { engine?.stopSearch(); }; - }, [gameFen, engine]); + }, [gameFen, isGameInProgress]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { setClickedSquares([]); @@ -61,7 +68,7 @@ export default function Board() { ): boolean => { if (!piece || piece[0] !== playerColor) return false; try { - const result = makeBoardMove({ + const result = makeGameMove({ from: source, to: target, promotion: piece[1]?.toLowerCase() ?? "q", @@ -125,7 +132,7 @@ export default function Board() { id="AnalysisBoard" position={gameFen} onPieceDrop={onPieceDrop} - boardOrientation={playerColor ? "white" : "black"} + boardOrientation={playerColor === Color.White ? "white" : "black"} customBoardStyle={{ borderRadius: "5px", boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)", diff --git a/src/sections/play/gameInProgress.tsx b/src/sections/play/gameInProgress.tsx new file mode 100644 index 0000000..e09a5d7 --- /dev/null +++ b/src/sections/play/gameInProgress.tsx @@ -0,0 +1,51 @@ +import { Button, CircularProgress, Grid, Typography } from "@mui/material"; +import { useAtom, useAtomValue } from "jotai"; +import { gameAtom, isGameInProgressAtom } from "./states"; +import { useEffect } from "react"; + +export default function GameInProgress() { + const game = useAtomValue(gameAtom); + const [isGameInProgress, setIsGameInProgress] = useAtom(isGameInProgressAtom); + + useEffect(() => { + if (game.isGameOver()) setIsGameInProgress(false); + }, [game, setIsGameInProgress]); + + if (!isGameInProgress) return null; + + return ( + + + Game in progress + + + + + + + + ); +} diff --git a/src/sections/play/gameRecap.tsx b/src/sections/play/gameRecap.tsx new file mode 100644 index 0000000..4153b2d --- /dev/null +++ b/src/sections/play/gameRecap.tsx @@ -0,0 +1,40 @@ +import { useAtomValue } from "jotai"; +import { gameAtom, isGameInProgressAtom, playerColorAtom } from "./states"; +import { Grid, Typography } from "@mui/material"; +import { Color } from "@/types/enums"; + +export default function GameRecap() { + const game = useAtomValue(gameAtom); + const playerColor = useAtomValue(playerColorAtom); + const isGameInProgress = useAtomValue(isGameInProgressAtom); + + if (isGameInProgress) return null; + + const getResultLabel = () => { + if (game.isCheckmate()) { + const winnerColor = game.turn() === "w" ? Color.Black : Color.White; + const winnerLabel = winnerColor === playerColor ? "You" : "Stockfish"; + return `${winnerLabel} won by checkmate !`; + } + if (game.isDraw()) { + if (game.isInsufficientMaterial()) return "Draw by insufficient material"; + if (game.isStalemate()) return "Draw by stalemate"; + if (game.isThreefoldRepetition()) return "Draw by threefold repetition"; + return "Draw by fifty-move rule"; + } + return "You resigned"; + }; + + return ( + + {getResultLabel()} + + ); +} diff --git a/src/sections/play/gameSettings/gameSettingsButton.tsx b/src/sections/play/gameSettings/gameSettingsButton.tsx new file mode 100644 index 0000000..81d9033 --- /dev/null +++ b/src/sections/play/gameSettings/gameSettingsButton.tsx @@ -0,0 +1,20 @@ +import { Button } from "@mui/material"; +import { useState } from "react"; +import GameSettingsDialog from "./gameSettingsDialog"; + +export default function GameSettingsButton() { + const [openDialog, setOpenDialog] = useState(false); + + return ( + <> + + + setOpenDialog(false)} + /> + + ); +} diff --git a/src/sections/play/gameSettings/gameSettingsDialog.tsx b/src/sections/play/gameSettings/gameSettingsDialog.tsx new file mode 100644 index 0000000..6e54ec4 --- /dev/null +++ b/src/sections/play/gameSettings/gameSettingsDialog.tsx @@ -0,0 +1,135 @@ +import Slider from "@/components/slider"; +import { Color, EngineName } from "@/types/enums"; +import { + MenuItem, + Select, + Button, + Dialog, + DialogTitle, + DialogContent, + FormControl, + InputLabel, + OutlinedInput, + DialogActions, + Typography, + Grid, + FormGroup, + FormControlLabel, + Switch, +} from "@mui/material"; +import { useAtomLocalStorage } from "@/hooks/useAtomLocalStorage"; +import { useAtom, useSetAtom } from "jotai"; +import { + engineSkillLevelAtom, + playerColorAtom, + isGameInProgressAtom, + gameAtom, +} from "../states"; +import { useChessActions } from "@/hooks/useChessActions"; + +interface Props { + open: boolean; + onClose: () => void; +} + +export default function GameSettingsDialog({ open, onClose }: Props) { + const [skillLevel, setSkillLevel] = useAtomLocalStorage( + "engine-skill-level", + engineSkillLevelAtom + ); + const [playerColor, setPlayerColor] = useAtom(playerColorAtom); + const setIsGameInProgress = useSetAtom(isGameInProgressAtom); + const { reset: resetGame } = useChessActions(gameAtom); + + const handleGameStart = () => { + onClose(); + resetGame(); + setIsGameInProgress(true); + }; + + return ( + + + Set game parameters + + + + Stockfish 16 is the only engine available now, more engine choices + will come soon ! + + + + + Bot's engine + + + + + + + + { + setPlayerColor( + e.target.checked ? Color.White : Color.Black + ); + }} + /> + } + label={ + playerColor === Color.White + ? "You play as White" + : "You play as Black" + } + /> + + + + + + + + + ); +} + +const engineLabel: Record = { + [EngineName.Stockfish16]: "Stockfish 16", +}; diff --git a/src/sections/play/states.ts b/src/sections/play/states.ts index ee1694d..ce72325 100644 --- a/src/sections/play/states.ts +++ b/src/sections/play/states.ts @@ -5,6 +5,7 @@ import { atom } from "jotai"; export const gameAtom = atom(new Chess()); export const playerColorAtom = atom(Color.White); export const engineSkillLevelAtom = atom(1); +export const isGameInProgressAtom = atom(false); export const clickedSquaresAtom = atom([]); export const playableSquaresAtom = atom([]);