diff --git a/apps/client/src/components/chessSidebar.tsx b/apps/client/src/components/chessSidebar.tsx index cb78cc2..00ef1ee 100644 --- a/apps/client/src/components/chessSidebar.tsx +++ b/apps/client/src/components/chessSidebar.tsx @@ -1,12 +1,15 @@ import type { GameStateEvent } from "@chesslab/shared/types"; +import type { DifficultyKey } from "@constants/stockfish"; +import { difficulties } from "@constants/stockfish"; -export default function SideBar({ - opponent, - gameState, -}: { +interface SideBarProps { opponent: string | undefined; gameState: GameStateEvent | undefined; -}) { + setDifficulty?: React.Dispatch>; + difficulty?: DifficultyKey; +} + +export default function SideBar({ opponent, gameState, setDifficulty, difficulty }: SideBarProps) { return (
@@ -27,6 +30,20 @@ export default function SideBar({
)}
+ {setDifficulty && ( +
+ +
+ )} ); } diff --git a/apps/client/src/constants/stockfish.ts b/apps/client/src/constants/stockfish.ts new file mode 100644 index 0000000..3becf4e --- /dev/null +++ b/apps/client/src/constants/stockfish.ts @@ -0,0 +1,8 @@ +export const difficulties = { + easy: { elo: 1320 }, + medium: { elo: 1500 }, + hard: { elo: 1900 }, + expert: { elo: 2400 }, +} as const; + +export type DifficultyKey = keyof typeof difficulties; diff --git a/apps/client/src/hooks/game/useComputerGame.ts b/apps/client/src/hooks/game/useComputerGame.ts index 4a55c4f..e5e81d7 100644 --- a/apps/client/src/hooks/game/useComputerGame.ts +++ b/apps/client/src/hooks/game/useComputerGame.ts @@ -5,8 +5,9 @@ import { useState } from "react"; import { getGameOverInfo } from "@chesslab/shared/utils"; import { useCompTimer } from "./useCompTimer"; import { playMoveSound } from "@services/sfx/index"; +import type { DifficultyKey } from "@constants/stockfish"; -export function useComputerGame() { +export function useComputerGame({ difficulty }: { difficulty: DifficultyKey }) { const [chessGame] = useState(() => new Chess()); const [chessPosition, setChessPosition] = useState(() => new Chess().fen()); const [turn, setTurn] = useState<"w" | "b">("w"); @@ -35,6 +36,7 @@ export function useComputerGame() { turn, stockfishSide: side === "w" ? "b" : "w", isGameOver: !!gameOverInfo, + difficulty, onMove: () => { setChessPosition(chessGame.fen()); setTurn(chessGame.turn()); diff --git a/apps/client/src/hooks/game/useStockfish.ts b/apps/client/src/hooks/game/useStockfish.ts index aa2dd37..dbeedcf 100644 --- a/apps/client/src/hooks/game/useStockfish.ts +++ b/apps/client/src/hooks/game/useStockfish.ts @@ -1,6 +1,7 @@ import { useEffect } from "react"; import stockfish from "@stockfish/stockfish"; import type { Chess } from "chess.js"; +import { difficulties, type DifficultyKey } from "@constants/stockfish"; export default function useStockfish({ chessGame, @@ -8,17 +9,23 @@ export default function useStockfish({ stockfishSide, isGameOver, onMove, + difficulty, }: { chessGame: Chess; turn: "w" | "b"; stockfishSide: "w" | "b"; isGameOver: boolean; onMove: () => void; + difficulty: DifficultyKey; }) { useEffect(() => { + if (turn !== stockfishSide || isGameOver) return; + stockfish.postMessage("uci"); stockfish.postMessage("isready"); stockfish.postMessage("ucinewgame"); + stockfish.postMessage("setoption name UCI_LimitStrength value true"); + stockfish.postMessage(`setoption name UCI_Elo value ${difficulties[difficulty].elo}`); stockfish.onmessage = (e) => { const message = e.data; @@ -45,12 +52,12 @@ export default function useStockfish({ stockfish.postMessage("stop"); stockfish.onmessage = null; }; - }, [chessGame, onMove, isGameOver]); + }, [chessGame, onMove, isGameOver, difficulty, stockfishSide, turn]); useEffect(() => { if (turn === stockfishSide && !isGameOver) { stockfish.postMessage(`position fen ${chessGame.fen()}`); - stockfish.postMessage("go depth 15"); + stockfish.postMessage("go depth 15 movetime 1000"); } - }, [turn, chessGame, stockfishSide, isGameOver]); + }, [turn, chessGame, stockfishSide, isGameOver, difficulty]); } diff --git a/apps/client/src/pages/computerGame.tsx b/apps/client/src/pages/computerGame.tsx index 668c2a9..c6583ab 100644 --- a/apps/client/src/pages/computerGame.tsx +++ b/apps/client/src/pages/computerGame.tsx @@ -2,16 +2,24 @@ import ChessBoard from "@components/chessBoard"; import { Timer } from "@components/Timer"; import SideBar from "@components/chessSidebar"; import { useComputerGame } from "@hooks/game/useComputerGame"; +import { useState } from "react"; +import type { DifficultyKey } from "@constants/stockfish"; export default function ComputerChessBoard() { - const { turn, side, timeMs, gameOverInfo, chessboardOptions } = useComputerGame(); + const [difficulty, setDifficulty] = useState("easy"); + const { turn, side, timeMs, gameOverInfo, chessboardOptions } = useComputerGame({ difficulty }); return (
- +
); } diff --git a/apps/client/tsconfig.json b/apps/client/tsconfig.json index cc5b01b..69fa13d 100644 --- a/apps/client/tsconfig.json +++ b/apps/client/tsconfig.json @@ -22,7 +22,8 @@ "@stockfish/*": ["./src/stockfish/*"], "@app-types/*": ["./src/types/*"], "@utils/*": ["./src/utils/*"], - "@services/*": ["./src/services/*"] + "@services/*": ["./src/services/*"], + "@constants/*": ["./src/constants/*"] } }, "include": ["vite.config.ts", "src"] diff --git a/apps/client/vite.config.ts b/apps/client/vite.config.ts index a7169db..c57db3d 100644 --- a/apps/client/vite.config.ts +++ b/apps/client/vite.config.ts @@ -21,6 +21,7 @@ export default defineConfig({ "@app-types": path.resolve(__dirname, "./src/types"), "@utils": path.resolve(__dirname, "./src/utils"), "@services": path.resolve(__dirname, "./src/services"), + "@constants": path.resolve(__dirname, "./src/constants"), }, }, server: {