diff --git a/src/hooks/useCurrentMove.ts b/src/hooks/useCurrentMove.ts index f67c89e..cbc7bcf 100644 --- a/src/hooks/useCurrentMove.ts +++ b/src/hooks/useCurrentMove.ts @@ -20,6 +20,7 @@ export const useCurrentMove = () => { return; const evalIndex = board.history().length; + return { ...board.history({ verbose: true }).at(-1), eval: gameEval.moves[evalIndex], diff --git a/src/hooks/useGameDatabase.ts b/src/hooks/useGameDatabase.ts index 7227a66..9b850af 100644 --- a/src/hooks/useGameDatabase.ts +++ b/src/hooks/useGameDatabase.ts @@ -4,6 +4,7 @@ import { Game } from "@/types/game"; import { Chess } from "chess.js"; import { openDB, DBSchema, IDBPDatabase } from "idb"; import { atom, useAtom } from "jotai"; +import { useRouter } from "next/router"; import { useCallback, useEffect, useState } from "react"; interface GameDatabaseSchema extends DBSchema { @@ -20,6 +21,7 @@ export const useGameDatabase = (shouldFetchGames?: boolean) => { const [db, setDb] = useState | null>(null); const [games, setGames] = useAtom(gamesAtom); const [fetchGames, setFetchGames] = useAtom(fetchGamesAtom); + const [gameFromUrl, setGameFromUrl] = useState(undefined); useEffect(() => { if (shouldFetchGames !== undefined) { @@ -51,6 +53,17 @@ export const useGameDatabase = (shouldFetchGames?: boolean) => { loadGames(); }, [loadGames]); + const router = useRouter(); + const { gameId } = router.query; + + useEffect(() => { + if (typeof gameId === "string") { + getGame(parseInt(gameId)).then((game) => { + setGameFromUrl(game); + }); + } + }, [gameId, games]); + const addGame = async (game: Chess) => { if (!db) throw new Error("Database not initialized"); @@ -89,5 +102,13 @@ export const useGameDatabase = (shouldFetchGames?: boolean) => { const isReady = db !== null; - return { addGame, setGameEval, getGame, deleteGame, games, isReady }; + return { + addGame, + setGameEval, + getGame, + deleteGame, + games, + isReady, + gameFromUrl, + }; }; diff --git a/src/lib/chess.ts b/src/lib/chess.ts index 47d5a58..f6cb33e 100644 --- a/src/lib/chess.ts +++ b/src/lib/chess.ts @@ -26,3 +26,26 @@ export const formatGameToDatabase = (game: Chess): Omit => { result: headers.Result, }; }; + +export const getGameToSave = (game: Chess, board: Chess): Chess => { + if (game.history().length) return game; + + const headers = board.header(); + + if (!headers.Event) { + board.header("Event", "Freechess Game"); + } + + if (!headers.Site) { + board.header("Site", "Freechess"); + } + + if (!headers.Date) { + board.header( + "Date", + new Date().toISOString().split("T")[0].replaceAll("-", ".") + ); + } + + return board; +}; diff --git a/src/lib/engine/stockfish.ts b/src/lib/engine/stockfish.ts index acdb6ac..8485c51 100644 --- a/src/lib/engine/stockfish.ts +++ b/src/lib/engine/stockfish.ts @@ -130,7 +130,23 @@ export class Stockfish { "bestmove" ); - return this.parseResults(results); + const parsedResults = this.parseResults(results); + + const whiteToPlay = fen.split(" ")[1] === "w"; + + if (!whiteToPlay) { + const lines = parsedResults.lines.map((line) => ({ + ...line, + cp: line.cp ? -line.cp : line.cp, + })); + + return { + ...parsedResults, + lines, + }; + } + + return parsedResults; } private parseResults(results: string[]): MoveEval { @@ -151,7 +167,16 @@ export class Stockfish { if (result.startsWith("info")) { const pv = this.getResultPv(result); const multiPv = this.getResultProperty(result, "multipv"); - if (!pv || !multiPv) continue; + const depth = this.getResultProperty(result, "depth"); + if (!pv || !multiPv || !depth) continue; + + if ( + tempResults[multiPv] && + parseInt(depth) < tempResults[multiPv].depth + ) { + continue; + } + const cp = this.getResultProperty(result, "cp"); const mate = this.getResultProperty(result, "mate"); @@ -159,6 +184,8 @@ export class Stockfish { pv, cp: cp ? parseInt(cp) : undefined, mate: mate ? parseInt(mate) : undefined, + depth: parseInt(depth), + multiPv: parseInt(multiPv), }; } } diff --git a/src/pages/database.tsx b/src/pages/database.tsx index 4ea547b..9bbe1dd 100644 --- a/src/pages/database.tsx +++ b/src/pages/database.tsx @@ -80,6 +80,15 @@ export default function GameDatabase() { headerAlign: "center", align: "center", }, + { + field: "eval", + headerName: "Evaluation", + type: "boolean", + headerAlign: "center", + align: "center", + width: 100, + valueGetter: (params) => !!params.row.eval, + }, { field: "openEvaluation", type: "actions", diff --git a/src/sections/analysis/analyzeButton.tsx b/src/sections/analysis/analyzeButton.tsx index cb2195e..b87fca9 100644 --- a/src/sections/analysis/analyzeButton.tsx +++ b/src/sections/analysis/analyzeButton.tsx @@ -6,16 +6,13 @@ import { gameAtom, gameEvalAtom } from "./states"; import { useAtomValue, useSetAtom } from "jotai"; import { getFens } from "@/lib/chess"; import { useGameDatabase } from "@/hooks/useGameDatabase"; -import { useRouter } from "next/router"; export default function AnalyzeButton() { const [engine, setEngine] = useState(null); const [evaluationInProgress, setEvaluationInProgress] = useState(false); - const { setGameEval } = useGameDatabase(); + const { setGameEval, gameFromUrl } = useGameDatabase(); const setEval = useSetAtom(gameEvalAtom); const game = useAtomValue(gameAtom); - const router = useRouter(); - const { gameId } = router.query; useEffect(() => { const engine = new Stockfish(); @@ -43,9 +40,8 @@ export default function AnalyzeButton() { setEvaluationInProgress(false); - if (typeof gameId === "string") { - setGameEval(parseInt(gameId), newGameEval); - console.log("Game Eval saved to database"); + if (gameFromUrl) { + setGameEval(gameFromUrl.id, newGameEval); } }; diff --git a/src/sections/analysis/loadGame.tsx b/src/sections/analysis/loadGame.tsx index fe8811d..c33492b 100644 --- a/src/sections/analysis/loadGame.tsx +++ b/src/sections/analysis/loadGame.tsx @@ -1,6 +1,5 @@ import { Grid } from "@mui/material"; import LoadGameButton from "../loadGame/loadGameButton"; -import { useRouter } from "next/router"; import { useCallback, useEffect } from "react"; import { useChessActions } from "@/hooks/useChess"; import { @@ -14,12 +13,10 @@ import { useAtomValue, useSetAtom } from "jotai"; import { Chess } from "chess.js"; export default function LoadGame() { - const router = useRouter(); - const { gameId } = router.query; const game = useAtomValue(gameAtom); const gameActions = useChessActions(gameAtom); const boardActions = useChessActions(boardAtom); - const { getGame } = useGameDatabase(); + const { gameFromUrl } = useGameDatabase(); const setEval = useSetAtom(gameEvalAtom); const setBoardOrientation = useSetAtom(boardOrientationAtom); @@ -35,23 +32,20 @@ export default function LoadGame() { useEffect(() => { const loadGame = async () => { - if (typeof gameId !== "string") return; - - const gamefromDb = await getGame(parseInt(gameId)); - if (!gamefromDb) return; + if (!gameFromUrl) return; const gamefromDbChess = new Chess(); - gamefromDbChess.loadPgn(gamefromDb.pgn); + gamefromDbChess.loadPgn(gameFromUrl.pgn); if (game.history().join() === gamefromDbChess.history().join()) return; - resetAndSetGamePgn(gamefromDb.pgn); - setEval(gamefromDb.eval); + resetAndSetGamePgn(gameFromUrl.pgn); + setEval(gameFromUrl.eval); }; loadGame(); - }, [gameId, getGame, game, resetAndSetGamePgn, setEval]); + }, [gameFromUrl, resetAndSetGamePgn, setEval]); - if (!router.isReady || gameId) return null; + if (gameFromUrl) return null; return ( diff --git a/src/sections/analysis/reviewPanelToolbar/saveButton.tsx b/src/sections/analysis/reviewPanelToolbar/saveButton.tsx index 33c98ef..7727f54 100644 --- a/src/sections/analysis/reviewPanelToolbar/saveButton.tsx +++ b/src/sections/analysis/reviewPanelToolbar/saveButton.tsx @@ -3,22 +3,23 @@ import { Icon } from "@iconify/react"; import { IconButton } from "@mui/material"; import { useAtomValue } from "jotai"; import { useRouter } from "next/router"; -import { gameAtom, gameEvalAtom } from "../states"; +import { boardAtom, gameAtom, gameEvalAtom } from "../states"; +import { getGameToSave } from "@/lib/chess"; export default function SaveButton() { const game = useAtomValue(gameAtom); + const board = useAtomValue(boardAtom); const gameEval = useAtomValue(gameEvalAtom); - const { addGame, setGameEval } = useGameDatabase(); + const { addGame, setGameEval, gameFromUrl } = useGameDatabase(); const router = useRouter(); - const { gameId } = router.query; - - const isButtonEnabled = router.isReady && typeof gameId === undefined; const handleSave = async () => { - if (!isButtonEnabled) return; + if (gameFromUrl) return; - const gameId = await addGame(game); + const gameToSave = getGameToSave(game, board); + + const gameId = await addGame(gameToSave); if (gameEval) { await setGameEval(gameId, gameEval); } @@ -34,7 +35,7 @@ export default function SaveButton() { }; return ( - + ); diff --git a/src/types/eval.ts b/src/types/eval.ts index c788762..c8f177f 100644 --- a/src/types/eval.ts +++ b/src/types/eval.ts @@ -9,6 +9,8 @@ export interface LineEval { pv: string[]; cp?: number; mate?: number; + depth: number; + multiPv: number; } export interface Accuracy {