diff --git a/src/hooks/useChessActions.ts b/src/hooks/useChessActions.ts index b72586f..d8caa97 100644 --- a/src/hooks/useChessActions.ts +++ b/src/hooks/useChessActions.ts @@ -1,7 +1,14 @@ +import { setGameHeaders } from "@/lib/chess"; import { Chess, Move } from "chess.js"; import { PrimitiveAtom, useAtom } from "jotai"; import { useCallback } from "react"; +export interface resetGameParams { + fen?: string; + whiteName?: string; + blackName?: string; +} + export const useChessActions = (chessAtom: PrimitiveAtom) => { const [game, setGame] = useAtom(chessAtom); @@ -15,8 +22,10 @@ export const useChessActions = (chessAtom: PrimitiveAtom) => { ); const reset = useCallback( - (fen?: string) => { - setGame(new Chess(fen)); + (params?: resetGameParams) => { + const newGame = new Chess(params?.fen); + setGameHeaders(newGame, params); + setGame(newGame); }, [setGame] ); diff --git a/src/lib/chess.ts b/src/lib/chess.ts index 1decfd0..adc1972 100644 --- a/src/lib/chess.ts +++ b/src/lib/chess.ts @@ -2,6 +2,7 @@ import { EvaluateGameParams, PositionEval } from "@/types/eval"; import { Game } from "@/types/game"; import { Chess, PieceSymbol } from "chess.js"; import { getPositionWinPercentage } from "./engine/helpers/winPercentage"; +import { Color } from "@/types/enums"; export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => { const history = game.history({ verbose: true }); @@ -48,25 +49,64 @@ export const formatGameToDatabase = (game: Chess): Omit => { export const getGameToSave = (game: Chess, board: Chess): Chess => { 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) { - board.header("Event", "Freechess Game"); - } + const { whiteName, blackName, resigned } = params; - if (!headers.Site) { - board.header("Site", "Freechess"); - } + if (whiteName) game.header("White", whiteName); + if (blackName) game.header("Black", blackName); - if (!headers.Date) { - board.header( - "Date", - new Date().toISOString().split("T")[0].replaceAll("-", ".") + 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 = ( @@ -197,10 +237,13 @@ const getPieceValue = (piece: PieceSymbol): number => { } }; -export const getStartingFen = (pgn: string): string => { - const game = new Chess(); - game.loadPgn(pgn); +export const getStartingFen = ( + params: { pgn: string } | { game: Chess } +): string => { + const game = "game" in params ? params.game : getGameFromPgn(params.pgn); const history = game.history({ verbose: true }); + if (!history.length) return game.fen(); + return history[0].before; }; diff --git a/src/sections/analysis/reviewPanelHeader/loadGame.tsx b/src/sections/analysis/reviewPanelHeader/loadGame.tsx index 96796ea..58577e1 100644 --- a/src/sections/analysis/reviewPanelHeader/loadGame.tsx +++ b/src/sections/analysis/reviewPanelHeader/loadGame.tsx @@ -26,7 +26,7 @@ export default function LoadGame() { const resetAndSetGamePgn = useCallback( (pgn: string) => { - resetBoard(getStartingFen(pgn)); + resetBoard({ fen: getStartingFen({ pgn }) }); setEval(undefined); setBoardOrientation(true); setGamePgn(pgn); diff --git a/src/sections/analysis/reviewPanelToolbar/index.tsx b/src/sections/analysis/reviewPanelToolbar/index.tsx index 1b5d341..ba59c61 100644 --- a/src/sections/analysis/reviewPanelToolbar/index.tsx +++ b/src/sections/analysis/reviewPanelToolbar/index.tsx @@ -39,7 +39,7 @@ export default function ReviewPanelToolBar() { resetBoard(getStartingFen(board.pgn()))} + onClick={() => resetBoard({ fen: getStartingFen({ game: board }) })} disabled={boardHistory.length === 0} > diff --git a/src/sections/play/board/index.tsx b/src/sections/play/board/index.tsx index 59b1d44..393da1e 100644 --- a/src/sections/play/board/index.tsx +++ b/src/sections/play/board/index.tsx @@ -69,7 +69,7 @@ export default function Board() { target: Square, piece: string ): boolean => { - if (!piece || piece[0] !== playerColor) return false; + if (!piece || piece[0] !== playerColor || !isGameInProgress) return false; try { const result = makeGameMove({ from: source, diff --git a/src/sections/play/gameRecap.tsx b/src/sections/play/gameRecap.tsx index 4153b2d..7969aee 100644 --- a/src/sections/play/gameRecap.tsx +++ b/src/sections/play/gameRecap.tsx @@ -1,14 +1,25 @@ import { useAtomValue } from "jotai"; -import { gameAtom, isGameInProgressAtom, playerColorAtom } from "./states"; -import { Grid, Typography } from "@mui/material"; +import { + gameAtom, + gameDataAtom, + isGameInProgressAtom, + playerColorAtom, +} from "./states"; +import { Button, Grid, Typography } from "@mui/material"; import { Color } from "@/types/enums"; +import { setGameHeaders } from "@/lib/chess"; +import { useGameDatabase } from "@/hooks/useGameDatabase"; +import { useRouter } from "next/router"; export default function GameRecap() { const game = useAtomValue(gameAtom); + const gameData = useAtomValue(gameDataAtom); const playerColor = useAtomValue(playerColorAtom); const isGameInProgress = useAtomValue(isGameInProgressAtom); + const { addGame } = useGameDatabase(); + const router = useRouter(); - if (isGameInProgress) return null; + if (isGameInProgress || !gameData.history.length) return null; const getResultLabel = () => { if (game.isCheckmate()) { @@ -16,15 +27,23 @@ export default function GameRecap() { const winnerLabel = winnerColor === playerColor ? "You" : "Stockfish"; return `${winnerLabel} won by checkmate !`; } - if (game.isDraw()) { - if (game.isInsufficientMaterial()) return "Draw by insufficient material"; - if (game.isStalemate()) return "Draw by stalemate"; - if (game.isThreefoldRepetition()) return "Draw by threefold repetition"; - return "Draw by fifty-move rule"; - } + if (game.isInsufficientMaterial()) return "Draw by insufficient material"; + if (game.isStalemate()) return "Draw by stalemate"; + if (game.isThreefoldRepetition()) return "Draw by threefold repetition"; + if (game.isDraw()) "Draw by fifty-move rule"; + 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 ( - {getResultLabel()} + + {getResultLabel()} + + + ); } diff --git a/src/sections/play/gameSettings/gameSettingsButton.tsx b/src/sections/play/gameSettings/gameSettingsButton.tsx index 81d9033..cae7af2 100644 --- a/src/sections/play/gameSettings/gameSettingsButton.tsx +++ b/src/sections/play/gameSettings/gameSettingsButton.tsx @@ -1,14 +1,17 @@ import { Button } from "@mui/material"; import { useState } from "react"; import GameSettingsDialog from "./gameSettingsDialog"; +import { useAtomValue } from "jotai"; +import { gameDataAtom } from "../states"; export default function GameSettingsButton() { const [openDialog, setOpenDialog] = useState(false); + const gameData = useAtomValue(gameDataAtom); return ( <> { onClose(); - resetGame(); + resetGame({ + whiteName: + playerColor === Color.White ? "You" : `Stockfish level ${skillLevel}`, + blackName: + playerColor === Color.Black ? "You" : `Stockfish level ${skillLevel}`, + }); setIsGameInProgress(true); };