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 (
+
+ );
+}
+
+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([]);