feat : open played game in analysis

This commit is contained in:
GuillaumeSD
2024-03-20 03:50:20 +01:00
parent aca8910bab
commit 32fc196e74
8 changed files with 118 additions and 33 deletions

View File

@@ -1,7 +1,14 @@
import { setGameHeaders } from "@/lib/chess";
import { Chess, Move } from "chess.js"; import { Chess, Move } from "chess.js";
import { PrimitiveAtom, useAtom } from "jotai"; import { PrimitiveAtom, useAtom } from "jotai";
import { useCallback } from "react"; import { useCallback } from "react";
export interface resetGameParams {
fen?: string;
whiteName?: string;
blackName?: string;
}
export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => { export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => {
const [game, setGame] = useAtom(chessAtom); const [game, setGame] = useAtom(chessAtom);
@@ -15,8 +22,10 @@ export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => {
); );
const reset = useCallback( const reset = useCallback(
(fen?: string) => { (params?: resetGameParams) => {
setGame(new Chess(fen)); const newGame = new Chess(params?.fen);
setGameHeaders(newGame, params);
setGame(newGame);
}, },
[setGame] [setGame]
); );

View File

@@ -2,6 +2,7 @@ import { EvaluateGameParams, PositionEval } from "@/types/eval";
import { Game } from "@/types/game"; import { Game } from "@/types/game";
import { Chess, PieceSymbol } from "chess.js"; import { Chess, PieceSymbol } from "chess.js";
import { getPositionWinPercentage } from "./engine/helpers/winPercentage"; import { getPositionWinPercentage } from "./engine/helpers/winPercentage";
import { Color } from "@/types/enums";
export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => { export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => {
const history = game.history({ verbose: true }); const history = game.history({ verbose: true });
@@ -48,25 +49,64 @@ export const formatGameToDatabase = (game: Chess): Omit<Game, "id"> => {
export const getGameToSave = (game: Chess, board: Chess): Chess => { export const getGameToSave = (game: Chess, board: Chess): Chess => {
if (game.history().length) return game; if (game.history().length) return game;
return setGameHeaders(board);
};
const headers = board.header(); export const setGameHeaders = (
game: Chess,
params: { whiteName?: string; blackName?: string; resigned?: Color } = {}
): Chess => {
game.header("Event", "Freechess Game");
game.header("Site", "Freechess");
game.header(
"Date",
new Date().toISOString().split("T")[0].replaceAll("-", ".")
);
if (!headers.Event) { const { whiteName, blackName, resigned } = params;
board.header("Event", "Freechess Game");
}
if (!headers.Site) { if (whiteName) game.header("White", whiteName);
board.header("Site", "Freechess"); if (blackName) game.header("Black", blackName);
}
if (!headers.Date) { const whiteNameToUse = game.header().White || "White";
board.header( const blackNameToUse = game.header().Black || "Black";
"Date",
new Date().toISOString().split("T")[0].replaceAll("-", ".") if (resigned) {
game.header("Result", resigned === "w" ? "0-1" : "1-0");
game.header(
"Termination",
`${resigned === "w" ? blackNameToUse : whiteNameToUse} won by resignation`
); );
} }
return board; if (!game.isGameOver()) return game;
if (game.isCheckmate()) {
game.header("Result", game.turn() === "w" ? "0-1" : "1-0");
game.header(
"Termination",
`${
game.turn() === "w" ? blackNameToUse : whiteNameToUse
} won by checkmate`
);
}
if (game.isInsufficientMaterial()) {
game.header("Result", "1/2-1/2");
game.header("Termination", "Draw by insufficient material");
}
if (game.isStalemate()) {
game.header("Result", "1/2-1/2");
game.header("Termination", "Draw by stalemate");
}
if (game.isThreefoldRepetition()) {
game.header("Result", "1/2-1/2");
game.header("Termination", "Draw by threefold repetition");
}
return game;
}; };
export const moveLineUciToSan = ( export const moveLineUciToSan = (
@@ -197,10 +237,13 @@ const getPieceValue = (piece: PieceSymbol): number => {
} }
}; };
export const getStartingFen = (pgn: string): string => { export const getStartingFen = (
const game = new Chess(); params: { pgn: string } | { game: Chess }
game.loadPgn(pgn); ): string => {
const game = "game" in params ? params.game : getGameFromPgn(params.pgn);
const history = game.history({ verbose: true }); const history = game.history({ verbose: true });
if (!history.length) return game.fen();
return history[0].before; return history[0].before;
}; };

View File

@@ -26,7 +26,7 @@ export default function LoadGame() {
const resetAndSetGamePgn = useCallback( const resetAndSetGamePgn = useCallback(
(pgn: string) => { (pgn: string) => {
resetBoard(getStartingFen(pgn)); resetBoard({ fen: getStartingFen({ pgn }) });
setEval(undefined); setEval(undefined);
setBoardOrientation(true); setBoardOrientation(true);
setGamePgn(pgn); setGamePgn(pgn);

View File

@@ -39,7 +39,7 @@ export default function ReviewPanelToolBar() {
<Tooltip title="Reset board"> <Tooltip title="Reset board">
<Grid> <Grid>
<IconButton <IconButton
onClick={() => resetBoard(getStartingFen(board.pgn()))} onClick={() => resetBoard({ fen: getStartingFen({ game: board }) })}
disabled={boardHistory.length === 0} disabled={boardHistory.length === 0}
> >
<Icon icon="ri:skip-back-line" /> <Icon icon="ri:skip-back-line" />

View File

@@ -69,7 +69,7 @@ export default function Board() {
target: Square, target: Square,
piece: string piece: string
): boolean => { ): boolean => {
if (!piece || piece[0] !== playerColor) return false; if (!piece || piece[0] !== playerColor || !isGameInProgress) return false;
try { try {
const result = makeGameMove({ const result = makeGameMove({
from: source, from: source,

View File

@@ -1,14 +1,25 @@
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { gameAtom, isGameInProgressAtom, playerColorAtom } from "./states"; import {
import { Grid, Typography } from "@mui/material"; gameAtom,
gameDataAtom,
isGameInProgressAtom,
playerColorAtom,
} from "./states";
import { Button, Grid, Typography } from "@mui/material";
import { Color } from "@/types/enums"; import { Color } from "@/types/enums";
import { setGameHeaders } from "@/lib/chess";
import { useGameDatabase } from "@/hooks/useGameDatabase";
import { useRouter } from "next/router";
export default function GameRecap() { export default function GameRecap() {
const game = useAtomValue(gameAtom); const game = useAtomValue(gameAtom);
const gameData = useAtomValue(gameDataAtom);
const playerColor = useAtomValue(playerColorAtom); const playerColor = useAtomValue(playerColorAtom);
const isGameInProgress = useAtomValue(isGameInProgressAtom); const isGameInProgress = useAtomValue(isGameInProgressAtom);
const { addGame } = useGameDatabase();
const router = useRouter();
if (isGameInProgress) return null; if (isGameInProgress || !gameData.history.length) return null;
const getResultLabel = () => { const getResultLabel = () => {
if (game.isCheckmate()) { if (game.isCheckmate()) {
@@ -16,15 +27,23 @@ export default function GameRecap() {
const winnerLabel = winnerColor === playerColor ? "You" : "Stockfish"; const winnerLabel = winnerColor === playerColor ? "You" : "Stockfish";
return `${winnerLabel} won by checkmate !`; return `${winnerLabel} won by checkmate !`;
} }
if (game.isDraw()) { if (game.isInsufficientMaterial()) return "Draw by insufficient material";
if (game.isInsufficientMaterial()) return "Draw by insufficient material"; if (game.isStalemate()) return "Draw by stalemate";
if (game.isStalemate()) return "Draw by stalemate"; if (game.isThreefoldRepetition()) return "Draw by threefold repetition";
if (game.isThreefoldRepetition()) return "Draw by threefold repetition"; if (game.isDraw()) "Draw by fifty-move rule";
return "Draw by fifty-move rule";
}
return "You resigned"; return "You resigned";
}; };
const handleOpenGameAnalysis = async () => {
const gameToAnalysis = setGameHeaders(game, {
resigned: !game.isGameOver() ? playerColor : undefined,
});
const gameId = await addGame(gameToAnalysis);
router.push({ pathname: "/", query: { gameId } });
};
return ( return (
<Grid <Grid
item item
@@ -32,9 +51,15 @@ export default function GameRecap() {
xs={12} xs={12}
justifyContent="center" justifyContent="center"
alignItems="center" alignItems="center"
gap={1} gap={2}
> >
<Typography>{getResultLabel()}</Typography> <Grid item container xs={12} justifyContent="center">
<Typography>{getResultLabel()}</Typography>
</Grid>
<Button variant="outlined" onClick={handleOpenGameAnalysis}>
Open game analysis
</Button>
</Grid> </Grid>
); );
} }

View File

@@ -1,14 +1,17 @@
import { Button } from "@mui/material"; import { Button } from "@mui/material";
import { useState } from "react"; import { useState } from "react";
import GameSettingsDialog from "./gameSettingsDialog"; import GameSettingsDialog from "./gameSettingsDialog";
import { useAtomValue } from "jotai";
import { gameDataAtom } from "../states";
export default function GameSettingsButton() { export default function GameSettingsButton() {
const [openDialog, setOpenDialog] = useState(false); const [openDialog, setOpenDialog] = useState(false);
const gameData = useAtomValue(gameDataAtom);
return ( return (
<> <>
<Button variant="contained" onClick={() => setOpenDialog(true)}> <Button variant="contained" onClick={() => setOpenDialog(true)}>
Start game {gameData.history.length ? "Start new game" : "Start game"}
</Button> </Button>
<GameSettingsDialog <GameSettingsDialog

View File

@@ -43,7 +43,12 @@ export default function GameSettingsDialog({ open, onClose }: Props) {
const handleGameStart = () => { const handleGameStart = () => {
onClose(); onClose();
resetGame(); resetGame({
whiteName:
playerColor === Color.White ? "You" : `Stockfish level ${skillLevel}`,
blackName:
playerColor === Color.Black ? "You" : `Stockfish level ${skillLevel}`,
});
setIsGameInProgress(true); setIsGameInProgress(true);
}; };