feat : open played game in analysis
This commit is contained in:
@@ -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]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,
|
||||||
if (!headers.Event) {
|
params: { whiteName?: string; blackName?: string; resigned?: Color } = {}
|
||||||
board.header("Event", "Freechess Game");
|
): Chess => {
|
||||||
}
|
game.header("Event", "Freechess Game");
|
||||||
|
game.header("Site", "Freechess");
|
||||||
if (!headers.Site) {
|
game.header(
|
||||||
board.header("Site", "Freechess");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!headers.Date) {
|
|
||||||
board.header(
|
|
||||||
"Date",
|
"Date",
|
||||||
new Date().toISOString().split("T")[0].replaceAll("-", ".")
|
new Date().toISOString().split("T")[0].replaceAll("-", ".")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { whiteName, blackName, resigned } = params;
|
||||||
|
|
||||||
|
if (whiteName) game.header("White", whiteName);
|
||||||
|
if (blackName) game.header("Black", blackName);
|
||||||
|
|
||||||
|
const whiteNameToUse = game.header().White || "White";
|
||||||
|
const blackNameToUse = game.header().Black || "Black";
|
||||||
|
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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";
|
||||||
return "Draw by fifty-move rule";
|
if (game.isDraw()) "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}
|
||||||
>
|
>
|
||||||
|
<Grid item container xs={12} justifyContent="center">
|
||||||
<Typography>{getResultLabel()}</Typography>
|
<Typography>{getResultLabel()}</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Button variant="outlined" onClick={handleOpenGameAnalysis}>
|
||||||
|
Open game analysis
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user