Squashed commit of the following:

commit d9209a78cff1c05be3e6a87e27cd1a5a4d5f91c5
Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com>
Date:   Wed Jul 24 11:55:35 2024 +0200

    style : UI analysis panel adjustment

commit 3c2e19bdb9d97f3bb7e8ceaefd630aad64d755c4
Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com>
Date:   Wed Jul 24 11:10:07 2024 +0200

    feat : graph dot color match move classification

commit 4a99ccb2fe19d3806ff320370ebc55af984d719a
Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com>
Date:   Wed Jul 24 11:09:35 2024 +0200

    fix : load pgn with no moves

commit 9eeb0e7f2869e544700b7da963b74f707fa6ea2f
Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com>
Date:   Wed Jul 24 00:09:03 2024 +0200

    feat : add current move reference line in graph

commit febb9962a0b366aeac1dc266e0470b75bd619e68
Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com>
Date:   Tue Jul 23 23:08:17 2024 +0200

    fix : handle tab change on new game

commit a105239a728dc05211a0ae99d8fd56f179108a0e
Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com>
Date:   Tue Jul 23 03:46:49 2024 +0200

    style : small chart UI tweaks

commit 4878ebf87b4ddbac75db70619fe452a3a317ca09
Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com>
Date:   Tue Jul 23 03:38:40 2024 +0200

    feat : add eval graph

commit 29c5a001da03ee288d2a2c133426b1d2ca435930
Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com>
Date:   Tue Jul 23 00:30:25 2024 +0200

    refacto : analysis directory

commit a8b966cc07152bb117b8c68f54af3498ca2a5d2f
Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com>
Date:   Tue Jul 23 00:07:07 2024 +0200

    style : add settings floating button

commit 7edc54f09ce7d4b4c4beb310a9c7f985363ff5ee
Author: GuillaumeSD <47183782+GuillaumeSD@users.noreply.github.com>
Date:   Sun Jul 21 22:29:48 2024 +0200

    feat : tab analysis panel
This commit is contained in:
GuillaumeSD
2024-07-24 11:58:42 +02:00
parent 9d5b088ae9
commit 2baf9b76ad
35 changed files with 754 additions and 156 deletions

View File

@@ -0,0 +1,85 @@
import { Icon } from "@iconify/react";
import {
engineDepthAtom,
engineMultiPvAtom,
engineNameAtom,
evaluationProgressAtom,
gameAtom,
gameEvalAtom,
savedEvalsAtom,
} from "../states";
import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { getEvaluateGameParams } from "@/lib/chess";
import { useGameDatabase } from "@/hooks/useGameDatabase";
import { LoadingButton } from "@mui/lab";
import { useEngine } from "@/hooks/useEngine";
import { logAnalyticsEvent } from "@/lib/firebase";
import { SavedEvals } from "@/types/eval";
export default function AnalyzeButton() {
const engineName = useAtomValue(engineNameAtom);
const engine = useEngine(engineName);
const [evaluationProgress, setEvaluationProgress] = useAtom(
evaluationProgressAtom
);
const engineDepth = useAtomValue(engineDepthAtom);
const engineMultiPv = useAtomValue(engineMultiPvAtom);
const { setGameEval, gameFromUrl } = useGameDatabase();
const [gameEval, setEval] = useAtom(gameEvalAtom);
const game = useAtomValue(gameAtom);
const setSavedEvals = useSetAtom(savedEvalsAtom);
const readyToAnalyse =
engine?.isReady() && game.history().length > 0 && !evaluationProgress;
const handleAnalyze = async () => {
const params = getEvaluateGameParams(game);
if (!engine?.isReady() || params.fens.length === 0 || evaluationProgress) {
return;
}
const newGameEval = await engine.evaluateGame({
...params,
depth: engineDepth,
multiPv: engineMultiPv,
setEvaluationProgress,
});
setEval(newGameEval);
setEvaluationProgress(0);
if (gameFromUrl) {
setGameEval(gameFromUrl.id, newGameEval);
}
const gameSavedEvals: SavedEvals = params.fens.reduce((acc, fen, idx) => {
acc[fen] = { ...newGameEval.positions[idx], engine: engineName };
return acc;
}, {} as SavedEvals);
setSavedEvals((prev) => ({
...prev,
...gameSavedEvals,
}));
logAnalyticsEvent("analyze_game", {
engine: engineName,
depth: engineDepth,
multiPv: engineMultiPv,
nbPositions: params.fens.length,
});
};
if (evaluationProgress) return null;
return (
<LoadingButton
variant="contained"
size="small"
startIcon={<Icon icon="streamline:magnifying-glass-solid" />}
onClick={handleAnalyze}
disabled={!readyToAnalyse}
>
{gameEval ? "Analyze again" : "Analyze"}
</LoadingButton>
);
}

View File

@@ -0,0 +1,50 @@
import { Grid, Typography } from "@mui/material";
import { useGameDatabase } from "@/hooks/useGameDatabase";
import { useAtomValue } from "jotai";
import { gameAtom } from "../states";
export default function GamePanel() {
const { gameFromUrl } = useGameDatabase();
const game = useAtomValue(gameAtom);
const hasGameInfo = gameFromUrl !== undefined || !!game.header().White;
if (!hasGameInfo) return null;
const termination =
gameFromUrl?.termination || game.header().Termination || "?";
const result =
termination.split(" ").length > 2
? termination
: gameFromUrl?.result || game.header().Result || "?";
return (
<Grid
item
container
xs={11}
justifyContent="space-evenly"
alignItems="center"
rowGap={1}
columnGap={3}
>
<Grid item container xs justifyContent="center" alignItems="center">
<Typography noWrap fontSize="0.9rem">
Site : {gameFromUrl?.site || game.header().Site || "?"}
</Typography>
</Grid>
<Grid item container xs justifyContent="center" alignItems="center">
<Typography noWrap fontSize="0.9rem">
Date : {gameFromUrl?.date || game.header().Date || "?"}
</Typography>
</Grid>
<Grid item container xs justifyContent="center" alignItems="center">
<Typography noWrap fontSize="0.9rem">
Result : {result}
</Typography>
</Grid>
</Grid>
);
}

View File

@@ -0,0 +1,53 @@
import { Icon } from "@iconify/react";
import { Grid, Typography } from "@mui/material";
import GamePanel from "./gamePanel";
import LoadGame from "./loadGame";
import AnalyzeButton from "./analyzeButton";
import LinearProgressBar from "@/components/LinearProgressBar";
import { useAtomValue } from "jotai";
import { evaluationProgressAtom } from "../states";
export default function PanelHeader() {
const evaluationProgress = useAtomValue(evaluationProgressAtom);
return (
<Grid
item
container
justifyContent="center"
alignItems="center"
xs={12}
rowGap={2}
>
<Grid
item
container
xs={12}
justifyContent="center"
alignItems="center"
columnGap={1}
>
<Icon icon="streamline:clipboard-check" height={24} />
<Typography variant="h5" align="center">
Game Review
</Typography>
</Grid>
<Grid
item
container
xs={12}
justifyContent="center"
alignItems="center"
rowGap={2}
columnGap={12}
>
<GamePanel />
<LoadGame />
<AnalyzeButton />
<LinearProgressBar value={evaluationProgress} label="Analyzing..." />
</Grid>
</Grid>
);
}

View File

@@ -0,0 +1,66 @@
import LoadGameButton from "../../loadGame/loadGameButton";
import { useCallback, useEffect } from "react";
import { useChessActions } from "@/hooks/useChessActions";
import {
boardAtom,
boardOrientationAtom,
evaluationProgressAtom,
gameAtom,
gameEvalAtom,
} from "../states";
import { useGameDatabase } from "@/hooks/useGameDatabase";
import { useAtomValue, useSetAtom } from "jotai";
import { Chess } from "chess.js";
import { useRouter } from "next/router";
import { getStartingFen } from "@/lib/chess";
export default function LoadGame() {
const router = useRouter();
const game = useAtomValue(gameAtom);
const { setPgn: setGamePgn } = useChessActions(gameAtom);
const { reset: resetBoard } = useChessActions(boardAtom);
const { gameFromUrl } = useGameDatabase();
const setEval = useSetAtom(gameEvalAtom);
const setBoardOrientation = useSetAtom(boardOrientationAtom);
const evaluationProgress = useAtomValue(evaluationProgressAtom);
const resetAndSetGamePgn = useCallback(
(pgn: string) => {
resetBoard({ fen: getStartingFen({ pgn }) });
setEval(undefined);
setBoardOrientation(true);
setGamePgn(pgn);
},
[resetBoard, setGamePgn, setEval, setBoardOrientation]
);
useEffect(() => {
const loadGame = async () => {
if (!gameFromUrl) return;
const gamefromDbChess = new Chess();
gamefromDbChess.loadPgn(gameFromUrl.pgn);
if (game.history().join() === gamefromDbChess.history().join()) return;
resetAndSetGamePgn(gameFromUrl.pgn);
setEval(gameFromUrl.eval);
};
loadGame();
}, [gameFromUrl, game, resetAndSetGamePgn, setEval]);
const isGameLoaded = gameFromUrl !== undefined || !!game.header().White;
if (evaluationProgress) return null;
return (
<LoadGameButton
label={isGameLoaded ? "Load another game" : "Load game"}
size="small"
setGame={async (game) => {
await router.push("/");
resetAndSetGamePgn(game.pgn());
}}
/>
);
}