feat : add board interactions

This commit is contained in:
GuillaumeSD
2024-02-23 04:01:18 +01:00
parent 2af0959e82
commit 814d3ecf09
19 changed files with 458 additions and 107 deletions

View File

@@ -1,4 +1,4 @@
import { Chess } from "chess.js"; import { Chess, Move } from "chess.js";
import { PrimitiveAtom, useAtom } from "jotai"; import { PrimitiveAtom, useAtom } from "jotai";
export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => { export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => {
@@ -20,10 +20,16 @@ export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => {
return newGame; return newGame;
}; };
const move = (move: { from: string; to: string; promotion?: string }) => { const move = (move: {
from: string;
to: string;
promotion?: string;
}): Move | null => {
const newGame = copyGame(); const newGame = copyGame();
newGame.move(move); const result = newGame.move(move);
setGame(newGame); setGame(newGame);
return result;
}; };
const undo = () => { const undo = () => {

View File

@@ -0,0 +1,31 @@
import { boardAtom, gameAtom, gameEvalAtom } from "@/sections/analysis/states";
import { useAtomValue } from "jotai";
import { useMemo } from "react";
export const useCurrentMove = () => {
const gameEval = useAtomValue(gameEvalAtom);
const game = useAtomValue(gameAtom);
const board = useAtomValue(boardAtom);
const currentEvalMove = useMemo(() => {
if (!gameEval) return undefined;
const boardHistory = board.history();
const gameHistory = game.history();
if (
boardHistory.length >= gameHistory.length ||
gameHistory.slice(0, boardHistory.length).join() !== boardHistory.join()
)
return;
const evalIndex = board.history().length;
return {
...board.history({ verbose: true }).at(-1),
eval: gameEval.moves[evalIndex],
lastEval: evalIndex > 0 ? gameEval.moves[evalIndex - 1] : undefined,
};
}, [gameEval, board, game]);
return currentEvalMove;
};

View File

@@ -55,9 +55,11 @@ export const useGameDatabase = (shouldFetchGames?: boolean) => {
if (!db) throw new Error("Database not initialized"); if (!db) throw new Error("Database not initialized");
const gameToAdd = formatGameToDatabase(game); const gameToAdd = formatGameToDatabase(game);
await db.add("games", gameToAdd as Game); const gameId = await db.add("games", gameToAdd as Game);
loadGames(); loadGames();
return gameId;
}; };
const setGameEval = async (gameId: number, evaluation: GameEval) => { const setGameEval = async (gameId: number, evaluation: GameEval) => {

View File

@@ -1,3 +1,4 @@
import { Engine } from "@/types/enums";
import { GameEval, LineEval, MoveEval } from "@/types/eval"; import { GameEval, LineEval, MoveEval } from "@/types/eval";
export class Stockfish { export class Stockfish {
@@ -25,14 +26,32 @@ export class Stockfish {
public async init(): Promise<void> { public async init(): Promise<void> {
await this.sendCommands(["uci"], "uciok"); await this.sendCommands(["uci"], "uciok");
await this.sendCommands( await this.setMultiPv(3, false);
["setoption name MultiPV value 3", "isready"],
"readyok"
);
this.ready = true; this.ready = true;
console.log("Stockfish initialized"); console.log("Stockfish initialized");
} }
public async setMultiPv(multiPv: number, checkIsReady = true) {
if (checkIsReady) {
this.throwErrorIfNotReady();
}
if (multiPv < 1 || multiPv > 6) {
throw new Error(`Invalid MultiPV value : ${multiPv}`);
}
await this.sendCommands(
[`setoption name MultiPV value ${multiPv}`, "isready"],
"readyok"
);
}
private throwErrorIfNotReady() {
if (!this.ready) {
throw new Error("Stockfish is not ready");
}
}
public shutdown(): void { public shutdown(): void {
this.ready = false; this.ready = false;
this.worker.postMessage("quit"); this.worker.postMessage("quit");
@@ -64,30 +83,53 @@ export class Stockfish {
}); });
} }
public async evaluateGame(fens: string[], depth = 16): Promise<GameEval> { public async evaluateGame(
fens: string[],
depth = 16,
multiPv = 3
): Promise<GameEval> {
this.throwErrorIfNotReady();
this.ready = false; this.ready = false;
console.log("Evaluating game");
await this.setMultiPv(multiPv, false);
await this.sendCommands(["ucinewgame", "isready"], "readyok"); await this.sendCommands(["ucinewgame", "isready"], "readyok");
this.worker.postMessage("position startpos"); this.worker.postMessage("position startpos");
const moves: MoveEval[] = []; const moves: MoveEval[] = [];
for (const fen of fens) { for (const fen of fens) {
console.log(`Evaluating position: ${fen}`); console.log(`Evaluating position: ${fen}`);
const result = await this.evaluatePosition(fen, depth); const result = await this.evaluatePosition(fen, depth, false);
moves.push(result); moves.push(result);
} }
this.ready = true; this.ready = true;
console.log("Game evaluated");
console.log(moves); console.log(moves);
return { moves, accuracy: { white: 82.34, black: 67.49 } }; // TODO: Calculate accuracy return {
moves,
accuracy: { white: 82.34, black: 67.49 }, // TODO: Calculate accuracy
settings: {
name: Engine.Stockfish16,
date: new Date().toISOString(),
depth,
multiPv,
},
};
} }
public async evaluatePosition(fen: string, depth = 16): Promise<MoveEval> { public async evaluatePosition(
fen: string,
depth = 16,
checkIsReady = true
): Promise<MoveEval> {
if (checkIsReady) {
this.throwErrorIfNotReady();
}
const results = await this.sendCommands( const results = await this.sendCommands(
[`position fen ${fen}`, `go depth ${depth}`], [`position fen ${fen}`, `go depth ${depth}`],
"bestmove" "bestmove"
); );
return this.parseResults(results); return this.parseResults(results);
} }

View File

@@ -12,6 +12,7 @@ import { useCallback, useMemo } from "react";
import { red } from "@mui/material/colors"; import { red } from "@mui/material/colors";
import LoadGameButton from "@/sections/loadGame/loadGameButton"; import LoadGameButton from "@/sections/loadGame/loadGameButton";
import { useGameDatabase } from "@/hooks/useGameDatabase"; import { useGameDatabase } from "@/hooks/useGameDatabase";
import { useRouter } from "next/router";
const gridLocaleText: GridLocaleText = { const gridLocaleText: GridLocaleText = {
...GRID_DEFAULT_LOCALE_TEXT, ...GRID_DEFAULT_LOCALE_TEXT,
@@ -20,6 +21,9 @@ const gridLocaleText: GridLocaleText = {
export default function GameDatabase() { export default function GameDatabase() {
const { games, deleteGame } = useGameDatabase(true); const { games, deleteGame } = useGameDatabase(true);
const router = useRouter();
console.log(games);
const handleDeleteGameRow = useCallback( const handleDeleteGameRow = useCallback(
(id: GridRowId) => async () => { (id: GridRowId) => async () => {
@@ -77,7 +81,29 @@ export default function GameDatabase() {
align: "center", align: "center",
}, },
{ {
field: "actions", field: "openEvaluation",
type: "actions",
headerName: "Analyze",
width: 100,
cellClassName: "actions",
getActions: ({ id }) => {
return [
<GridActionsCellItem
icon={
<Icon icon="streamline:magnifying-glass-solid" width="20px" />
}
label="Open Evaluation"
onClick={() =>
router.push({ pathname: "/", query: { gameId: id } })
}
color="inherit"
key={`${id}-open-eval-button`}
/>,
];
},
},
{
field: "delete",
type: "actions", type: "actions",
headerName: "Delete", headerName: "Delete",
width: 100, width: 100,
@@ -88,7 +114,7 @@ export default function GameDatabase() {
icon={ icon={
<Icon icon="mdi:delete-outline" color={red[400]} width="20px" /> <Icon icon="mdi:delete-outline" color={red[400]} width="20px" />
} }
label="Supprimer" label="Delete"
onClick={handleDeleteGameRow(id)} onClick={handleDeleteGameRow(id)}
color="inherit" color="inherit"
key={`${id}-delete-button`} key={`${id}-delete-button`}
@@ -97,7 +123,7 @@ export default function GameDatabase() {
}, },
}, },
], ],
[handleDeleteGameRow] [handleDeleteGameRow, router]
); );
return ( return (

View File

@@ -5,29 +5,47 @@ import { useEffect, useState } from "react";
import { gameAtom, gameEvalAtom } from "./states"; import { gameAtom, gameEvalAtom } from "./states";
import { useAtomValue, useSetAtom } from "jotai"; import { useAtomValue, useSetAtom } from "jotai";
import { getFens } from "@/lib/chess"; import { getFens } from "@/lib/chess";
import { useGameDatabase } from "@/hooks/useGameDatabase";
import { useRouter } from "next/router";
export default function AnalyzeButton() { export default function AnalyzeButton() {
const [engine, setEngine] = useState<Stockfish | null>(null); const [engine, setEngine] = useState<Stockfish | null>(null);
const [evaluationInProgress, setEvaluationInProgress] = useState(false);
const { setGameEval } = useGameDatabase();
const setEval = useSetAtom(gameEvalAtom); const setEval = useSetAtom(gameEvalAtom);
const game = useAtomValue(gameAtom); const game = useAtomValue(gameAtom);
const router = useRouter();
const { gameId } = router.query;
useEffect(() => { useEffect(() => {
const engine = new Stockfish(); const engine = new Stockfish();
engine.init(); engine.init().then(() => {
setEngine(engine); setEngine(engine);
});
return () => { return () => {
engine.shutdown(); engine.shutdown();
}; };
}, []); }, []);
const readyToAnalyse = engine?.isReady() && game.history().length > 0; const readyToAnalyse =
engine?.isReady() && game.history().length > 0 && !evaluationInProgress;
const handleAnalyze = async () => { const handleAnalyze = async () => {
const gameFens = getFens(game); const gameFens = getFens(game);
if (engine?.isReady() && gameFens.length) { if (!engine?.isReady() || gameFens.length === 0 || evaluationInProgress)
const newGameEval = await engine.evaluateGame(gameFens); return;
setEval(newGameEval);
setEvaluationInProgress(true);
const newGameEval = await engine.evaluateGame(gameFens);
setEval(newGameEval);
setEvaluationInProgress(false);
if (typeof gameId === "string") {
setGameEval(parseInt(gameId), newGameEval);
console.log("Game Eval saved to database");
} }
}; };

View File

@@ -1,10 +1,56 @@
import { Grid, Typography } from "@mui/material"; import { Grid, Typography } from "@mui/material";
import { Chessboard } from "react-chessboard"; import { Chessboard } from "react-chessboard";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { boardAtom } from "./states"; import { boardAtom, boardOrientationAtom, gameAtom } from "./states";
import { Arrow, Square } from "react-chessboard/dist/chessboard/types";
import { useChessActions } from "@/hooks/useChess";
import { useCurrentMove } from "@/hooks/useCurrentMove";
import { useMemo } from "react";
export default function Board() { export default function Board() {
const board = useAtomValue(boardAtom); const board = useAtomValue(boardAtom);
const game = useAtomValue(gameAtom);
const boardOrientation = useAtomValue(boardOrientationAtom);
const boardActions = useChessActions(boardAtom);
const currentMove = useCurrentMove();
const onPieceDrop = (source: Square, target: Square): boolean => {
try {
const result = boardActions.move({
from: source,
to: target,
promotion: "q", // TODO: Let the user choose the promotion
});
return !!result;
} catch {
return false;
}
};
const customArrows: Arrow[] = useMemo(() => {
if (!currentMove?.lastEval) return [];
const bestMoveArrow = [
currentMove.lastEval.bestMove.slice(0, 2),
currentMove.lastEval.bestMove.slice(2, 4),
"#3aab18",
] as Arrow;
if (
!currentMove.from ||
!currentMove.to ||
(currentMove.from === bestMoveArrow[0] &&
currentMove.to === bestMoveArrow[1])
) {
return [bestMoveArrow];
}
return [[currentMove.from, currentMove.to, "#ffaa00"], bestMoveArrow];
}, [currentMove]);
const whiteLabel = game.header()["White"] || "White Player (?)";
const blackLabel = game.header()["Black"] || "Black Player (?)";
return ( return (
<Grid <Grid
@@ -16,15 +62,33 @@ export default function Board() {
xs={12} xs={12}
md={6} md={6}
> >
<Typography variant="h4" align="center"> <Grid item container xs={12} justifyContent="center" alignItems="center">
White Player (?) <Typography variant="h4" align="center">
</Typography> {boardOrientation ? blackLabel : whiteLabel}
</Typography>
</Grid>
<Chessboard id="BasicBoard" position={board.fen()} /> <Grid
item
container
justifyContent="center"
alignItems="center"
maxWidth={"80vh"}
>
<Chessboard
id="BasicBoard"
position={board.fen()}
onPieceDrop={onPieceDrop}
boardOrientation={boardOrientation ? "white" : "black"}
customArrows={customArrows}
/>
</Grid>
<Typography variant="h4" align="center"> <Grid item container xs={12} justifyContent="center" alignItems="center">
Black Player (?) <Typography variant="h4" align="center">
</Typography> {boardOrientation ? whiteLabel : blackLabel}
</Typography>
</Grid>
</Grid> </Grid>
); );
} }

View File

@@ -1,42 +1,61 @@
import { Grid } from "@mui/material"; import { Grid } from "@mui/material";
import LoadGameButton from "../loadGame/loadGameButton"; import LoadGameButton from "../loadGame/loadGameButton";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect } from "react"; import { useCallback, useEffect } from "react";
import { useChessActions } from "@/hooks/useChess"; import { useChessActions } from "@/hooks/useChess";
import { boardAtom, gameAtom } from "./states"; import {
boardAtom,
boardOrientationAtom,
gameAtom,
gameEvalAtom,
} from "./states";
import { useGameDatabase } from "@/hooks/useGameDatabase"; import { useGameDatabase } from "@/hooks/useGameDatabase";
import { useAtomValue, useSetAtom } from "jotai";
import { Chess } from "chess.js";
export default function LoadGame() { export default function LoadGame() {
const router = useRouter(); const router = useRouter();
const { gameId } = router.query; const { gameId } = router.query;
const game = useAtomValue(gameAtom);
const gameActions = useChessActions(gameAtom); const gameActions = useChessActions(gameAtom);
const boardActions = useChessActions(boardAtom); const boardActions = useChessActions(boardAtom);
const { getGame } = useGameDatabase(); const { getGame } = useGameDatabase();
const setEval = useSetAtom(gameEvalAtom);
const setBoardOrientation = useSetAtom(boardOrientationAtom);
const resetAndSetGamePgn = useCallback(
(pgn: string) => {
boardActions.reset();
setEval(undefined);
setBoardOrientation(true);
gameActions.setPgn(pgn);
},
[boardActions, gameActions, setEval, setBoardOrientation]
);
useEffect(() => { useEffect(() => {
const loadGame = async () => { const loadGame = async () => {
if (typeof gameId !== "string") return; if (typeof gameId !== "string") return;
const game = await getGame(parseInt(gameId)); const gamefromDb = await getGame(parseInt(gameId));
if (!game) return; if (!gamefromDb) return;
boardActions.reset(); const gamefromDbChess = new Chess();
gameActions.setPgn(game.pgn); gamefromDbChess.loadPgn(gamefromDb.pgn);
if (game.history().join() === gamefromDbChess.history().join()) return;
resetAndSetGamePgn(gamefromDb.pgn);
setEval(gamefromDb.eval);
}; };
loadGame(); loadGame();
}, [gameId]); }, [gameId, getGame, game, resetAndSetGamePgn, setEval]);
if (!router.isReady || gameId) return null; if (!router.isReady || gameId) return null;
return ( return (
<Grid item container xs={12} justifyContent="center" alignItems="center"> <Grid item container xs={12} justifyContent="center" alignItems="center">
<LoadGameButton <LoadGameButton setGame={(game) => resetAndSetGamePgn(game.pgn())} />
setGame={(game) => {
boardActions.reset();
gameActions.setPgn(game.pgn());
}}
/>
</Grid> </Grid>
); );
} }

View File

@@ -1,16 +1,31 @@
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import { Divider, Grid, List, Typography } from "@mui/material"; import { Divider, Grid, List, Typography } from "@mui/material";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { boardAtom, gameEvalAtom } from "./states"; import { boardAtom, gameAtom } from "./states";
import LineEvaluation from "./lineEvaluation"; import LineEvaluation from "./lineEvaluation";
import { useCurrentMove } from "@/hooks/useCurrentMove";
export default function ReviewPanelBody() { export default function ReviewPanelBody() {
const gameEval = useAtomValue(gameEvalAtom); const game = useAtomValue(gameAtom);
if (!gameEval) return null;
const board = useAtomValue(boardAtom); const board = useAtomValue(boardAtom);
const evalIndex = board.history().length;
const moveEval = gameEval.moves[evalIndex]; const move = useCurrentMove();
const getBestMoveLabel = () => {
const bestMove = move?.lastEval?.bestMove;
if (bestMove) {
return `${bestMove} was the best move`;
}
const boardHistory = board.history();
const gameHistory = game.history();
if (game.isGameOver() && boardHistory.join() === gameHistory.join()) {
return "Game is over";
}
return null;
};
return ( return (
<> <>
@@ -35,12 +50,12 @@ export default function ReviewPanelBody() {
</Grid> </Grid>
<Typography variant="h6" align="center"> <Typography variant="h6" align="center">
{moveEval ? `${moveEval.bestMove} is the best move` : "Game is over"} {getBestMoveLabel()}
</Typography> </Typography>
<Grid item container xs={12} justifyContent="center" alignItems="center"> <Grid item container xs={12} justifyContent="center" alignItems="center">
<List> <List>
{moveEval?.lines.map((line) => ( {move?.eval?.lines.map((line) => (
<LineEvaluation key={line.pv[0]} line={line} /> <LineEvaluation key={line.pv[0]} line={line} />
))} ))}
</List> </List>

View File

@@ -1,51 +0,0 @@
import { Divider, Grid, IconButton } from "@mui/material";
import { Icon } from "@iconify/react";
import { useAtomValue } from "jotai";
import { boardAtom, gameAtom } from "./states";
import { useChessActions } from "@/hooks/useChess";
export default function ReviewPanelToolBar() {
const game = useAtomValue(gameAtom);
const board = useAtomValue(boardAtom);
const boardActions = useChessActions(boardAtom);
const addNextMoveToGame = () => {
const nextMoveIndex = board.history().length;
const nextMove = game.history({ verbose: true })[nextMoveIndex];
if (nextMove) {
boardActions.move({
from: nextMove.from,
to: nextMove.to,
promotion: nextMove.promotion,
});
}
};
return (
<>
<Divider sx={{ width: "90%", marginY: 3 }} />
<Grid container item justifyContent="center" alignItems="center" xs={12}>
<IconButton>
<Icon icon="eva:flip-fill" />
</IconButton>
<IconButton onClick={() => boardActions.reset()}>
<Icon icon="ri:skip-back-line" />
</IconButton>
<IconButton onClick={() => boardActions.undo()}>
<Icon icon="ri:arrow-left-s-line" height={30} />
</IconButton>
<IconButton onClick={() => addNextMoveToGame()}>
<Icon icon="ri:arrow-right-s-line" height={30} />
</IconButton>
<IconButton>
<Icon icon="ri:skip-forward-line" />
</IconButton>
<IconButton>
<Icon icon="ri:save-3-line" />
</IconButton>
</Grid>
</>
);
}

View File

@@ -0,0 +1,14 @@
import { useSetAtom } from "jotai";
import { boardOrientationAtom } from "../states";
import { IconButton } from "@mui/material";
import { Icon } from "@iconify/react";
export default function FlipBoardButton() {
const setBoardOrientation = useSetAtom(boardOrientationAtom);
return (
<IconButton onClick={() => setBoardOrientation((prev) => !prev)}>
<Icon icon="eva:flip-fill" />
</IconButton>
);
}

View File

@@ -0,0 +1,28 @@
import { Icon } from "@iconify/react";
import { IconButton } from "@mui/material";
import { useAtomValue } from "jotai";
import { boardAtom, gameAtom } from "../states";
import { useChessActions } from "@/hooks/useChess";
export default function GoToLastPositionButton() {
const boardActions = useChessActions(boardAtom);
const game = useAtomValue(gameAtom);
const board = useAtomValue(boardAtom);
const gameHistory = game.history();
const boardHistory = board.history();
const isButtonDisabled = boardHistory >= gameHistory;
return (
<IconButton
onClick={() => {
if (isButtonDisabled) return;
boardActions.setPgn(game.pgn());
}}
disabled={isButtonDisabled}
>
<Icon icon="ri:skip-forward-line" />
</IconButton>
);
}

View File

@@ -0,0 +1,46 @@
import { Divider, Grid, IconButton } from "@mui/material";
import { Icon } from "@iconify/react";
import { useAtomValue } from "jotai";
import { boardAtom } from "../states";
import { useChessActions } from "@/hooks/useChess";
import FlipBoardButton from "./flipBoardButton";
import NextMoveButton from "./nextMoveButton";
import GoToLastPositionButton from "./goToLastPositionButton";
import SaveButton from "./saveButton";
export default function ReviewPanelToolBar() {
const board = useAtomValue(boardAtom);
const boardActions = useChessActions(boardAtom);
const boardHistory = board.history();
return (
<>
<Divider sx={{ width: "90%", marginY: 3 }} />
<Grid container item justifyContent="center" alignItems="center" xs={12}>
<FlipBoardButton />
<IconButton
onClick={() => boardActions.reset()}
disabled={boardHistory.length === 0}
>
<Icon icon="ri:skip-back-line" />
</IconButton>
<IconButton
onClick={() => boardActions.undo()}
disabled={boardHistory.length === 0}
>
<Icon icon="ri:arrow-left-s-line" height={30} />
</IconButton>
<NextMoveButton />
<GoToLastPositionButton />
<SaveButton />
</Grid>
</>
);
}

View File

@@ -0,0 +1,42 @@
import { Icon } from "@iconify/react";
import { IconButton } from "@mui/material";
import { useAtomValue } from "jotai";
import { boardAtom, gameAtom } from "../states";
import { useChessActions } from "@/hooks/useChess";
export default function NextMoveButton() {
const boardActions = useChessActions(boardAtom);
const game = useAtomValue(gameAtom);
const board = useAtomValue(boardAtom);
const gameHistory = game.history();
const boardHistory = board.history();
const isButtonEnabled =
boardHistory.length < gameHistory.length &&
gameHistory.slice(0, boardHistory.length).join() === boardHistory.join();
const addNextGameMoveToBoard = () => {
if (!isButtonEnabled) return;
const nextMoveIndex = boardHistory.length;
const nextMove = game.history({ verbose: true })[nextMoveIndex];
if (nextMove) {
boardActions.move({
from: nextMove.from,
to: nextMove.to,
promotion: nextMove.promotion,
});
}
};
return (
<IconButton
onClick={() => addNextGameMoveToBoard()}
disabled={!isButtonEnabled}
>
<Icon icon="ri:arrow-right-s-line" height={30} />
</IconButton>
);
}

View File

@@ -0,0 +1,36 @@
import { useGameDatabase } from "@/hooks/useGameDatabase";
import { Icon } from "@iconify/react";
import { IconButton } from "@mui/material";
import { useAtomValue } from "jotai";
import { useRouter } from "next/router";
import { gameAtom } from "../states";
export default function SaveButton() {
const game = useAtomValue(gameAtom);
const { addGame } = useGameDatabase();
const router = useRouter();
const { gameId } = router.query;
const isButtonEnabled = router.isReady && typeof gameId === undefined;
return (
<IconButton
onClick={async () => {
if (!isButtonEnabled) return;
const gameId = await addGame(game);
router.replace(
{
query: { gameId: gameId },
pathname: router.pathname,
},
undefined,
{ shallow: true, scroll: false }
);
}}
disabled={!isButtonEnabled}
>
<Icon icon="ri:save-3-line" />
</IconButton>
);
}

View File

@@ -5,3 +5,4 @@ import { atom } from "jotai";
export const gameEvalAtom = atom<GameEval | undefined>(undefined); export const gameEvalAtom = atom<GameEval | undefined>(undefined);
export const gameAtom = atom(new Chess()); export const gameAtom = atom(new Chess());
export const boardAtom = atom(new Chess()); export const boardAtom = atom(new Chess());
export const boardOrientationAtom = atom(true);

View File

@@ -26,10 +26,8 @@ export default function NavBar({ darkMode, switchDarkMode }: Props) {
<Box sx={{ flexGrow: 1, display: "flex" }}> <Box sx={{ flexGrow: 1, display: "flex" }}>
<AppBar <AppBar
position="static" position="static"
sx={{ sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}
zIndex: (theme) => theme.zIndex.drawer + 1, enableColorOnDark
backgroundColor: "primary.main",
}}
> >
<Toolbar> <Toolbar>
<IconButton <IconButton

View File

@@ -3,3 +3,7 @@ export enum GameOrigin {
ChessCom = "chesscom", ChessCom = "chesscom",
Lichess = "lichess", Lichess = "lichess",
} }
export enum Engine {
Stockfish16 = "stockfish_16",
}

View File

@@ -1,3 +1,5 @@
import { Engine } from "./enums";
export interface MoveEval { export interface MoveEval {
bestMove: string; bestMove: string;
lines: LineEval[]; lines: LineEval[];
@@ -14,7 +16,15 @@ export interface Accuracy {
black: number; black: number;
} }
export interface EngineSettings {
name: Engine;
depth: number;
multiPv: number;
date: string;
}
export interface GameEval { export interface GameEval {
moves: MoveEval[]; moves: MoveEval[];
accuracy: Accuracy; accuracy: Accuracy;
settings: EngineSettings;
} }