Squashed commit of the following:
commit 4810de3b94b0ec0d7e9b8570de58f85792dffa80 Author: GuillaumeSD <gsd.lfny@gmail.com> Date: Sat Apr 6 01:37:42 2024 +0200 fix : lint commit 59e0b571e6089da6c086ab6340ec6a966b2e9739 Author: GuillaumeSD <gsd.lfny@gmail.com> Date: Sat Apr 6 01:36:17 2024 +0200 feat : UI refacto commit 56806a89dca5c7fb2c229b5a57404f9a856fac09 Author: GuillaumeSD <gsd.lfny@gmail.com> Date: Fri Apr 5 03:56:08 2024 +0200 feat : add moves list commit 9e3d2347882074c38ab183e642ecef8153dbfcde Author: GuillaumeSD <gsd.lfny@gmail.com> Date: Thu Apr 4 02:18:52 2024 +0200 feat : init branch, wip
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { setGameHeaders } from "@/lib/chess";
|
||||
import { playIllegalMoveSound, playSoundFromMove } from "@/lib/sounds";
|
||||
import {
|
||||
playGameEndSound,
|
||||
playIllegalMoveSound,
|
||||
playSoundFromMove,
|
||||
} from "@/lib/sounds";
|
||||
import { Chess, Move } from "chess.js";
|
||||
import { PrimitiveAtom, useAtom } from "jotai";
|
||||
import { useCallback } from "react";
|
||||
@@ -76,7 +80,11 @@ export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => {
|
||||
}
|
||||
|
||||
setGame(newGame);
|
||||
playSoundFromMove(lastMove);
|
||||
if (lastMove) {
|
||||
playSoundFromMove(lastMove);
|
||||
} else {
|
||||
playGameEndSound();
|
||||
}
|
||||
},
|
||||
[setGame]
|
||||
);
|
||||
|
||||
@@ -11,8 +11,16 @@ export const usePlayersNames = (gameAtom: PrimitiveAtom<Chess>) => {
|
||||
const blackName =
|
||||
gameFromUrl?.black?.name || game.header()["Black"] || "Black";
|
||||
|
||||
const whiteElo =
|
||||
gameFromUrl?.white?.rating || game.header()["WhiteElo"] || "?";
|
||||
|
||||
const blackElo =
|
||||
gameFromUrl?.black?.rating || game.header()["BlackElo"] || "?";
|
||||
|
||||
return {
|
||||
whiteName,
|
||||
blackName,
|
||||
whiteElo,
|
||||
blackElo,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
getHarmonicMean,
|
||||
getStandardDeviation,
|
||||
getWeightedMean,
|
||||
} from "@/lib/helpers";
|
||||
} from "@/lib/math";
|
||||
import { Accuracy, PositionEval } from "@/types/eval";
|
||||
import { getPositionWinPercentage } from "./winPercentage";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ceilsNumber } from "@/lib/helpers";
|
||||
import { ceilsNumber } from "@/lib/math";
|
||||
import { LineEval, PositionEval } from "@/types/eval";
|
||||
|
||||
export const getPositionWinPercentage = (position: PositionEval): number => {
|
||||
|
||||
@@ -6,36 +6,10 @@ export const capitalize = (s: string) => {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
};
|
||||
|
||||
export const ceilsNumber = (number: number, min: number, max: number) => {
|
||||
if (number > max) return max;
|
||||
if (number < min) return min;
|
||||
return number;
|
||||
};
|
||||
|
||||
export const getHarmonicMean = (array: number[]) => {
|
||||
const sum = array.reduce((acc, curr) => acc + 1 / curr, 0);
|
||||
return array.length / sum;
|
||||
};
|
||||
|
||||
export const getStandardDeviation = (array: number[]) => {
|
||||
const n = array.length;
|
||||
const mean = array.reduce((a, b) => a + b) / n;
|
||||
return Math.sqrt(
|
||||
array.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n
|
||||
export const isInViewport = (element: HTMLElement) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)
|
||||
);
|
||||
};
|
||||
|
||||
export const getWeightedMean = (array: number[], weights: number[]) => {
|
||||
if (array.length > weights.length)
|
||||
throw new Error("Weights array is too short");
|
||||
|
||||
const weightedSum = array.reduce(
|
||||
(acc, curr, index) => acc + curr * weights[index],
|
||||
0
|
||||
);
|
||||
const weightSum = weights
|
||||
.slice(0, array.length)
|
||||
.reduce((acc, curr) => acc + curr, 0);
|
||||
|
||||
return weightedSum / weightSum;
|
||||
};
|
||||
|
||||
33
src/lib/math.ts
Normal file
33
src/lib/math.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export const ceilsNumber = (number: number, min: number, max: number) => {
|
||||
if (number > max) return max;
|
||||
if (number < min) return min;
|
||||
return number;
|
||||
};
|
||||
|
||||
export const getHarmonicMean = (array: number[]) => {
|
||||
const sum = array.reduce((acc, curr) => acc + 1 / curr, 0);
|
||||
return array.length / sum;
|
||||
};
|
||||
|
||||
export const getStandardDeviation = (array: number[]) => {
|
||||
const n = array.length;
|
||||
const mean = array.reduce((a, b) => a + b) / n;
|
||||
return Math.sqrt(
|
||||
array.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n
|
||||
);
|
||||
};
|
||||
|
||||
export const getWeightedMean = (array: number[], weights: number[]) => {
|
||||
if (array.length > weights.length)
|
||||
throw new Error("Weights array is too short");
|
||||
|
||||
const weightedSum = array.reduce(
|
||||
(acc, curr, index) => acc + curr * weights[index],
|
||||
0
|
||||
);
|
||||
const weightSum = weights
|
||||
.slice(0, array.length)
|
||||
.reduce((acc, curr) => acc + curr, 0);
|
||||
|
||||
return weightedSum / weightSum;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useChessActions } from "@/hooks/useChessActions";
|
||||
import Board from "@/sections/analysis/board";
|
||||
import MovesClassificationsRecap from "@/sections/analysis/movesClassificationsRecap";
|
||||
import MovesClassificationsRecap from "@/sections/analysis/reviewPanelBody/movesClassificationsRecap";
|
||||
import ReviewPanelBody from "@/sections/analysis/reviewPanelBody";
|
||||
import ReviewPanelHeader from "@/sections/analysis/reviewPanelHeader";
|
||||
import ReviewPanelToolBar from "@/sections/analysis/reviewPanelToolbar";
|
||||
@@ -15,6 +15,7 @@ import { Chess } from "chess.js";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import MovesPanel from "@/sections/analysis/reviewPanelBody/movesPanel";
|
||||
|
||||
export default function GameReport() {
|
||||
const theme = useTheme();
|
||||
@@ -22,7 +23,7 @@ export default function GameReport() {
|
||||
|
||||
const { reset: resetBoard } = useChessActions(boardAtom);
|
||||
const { setPgn: setGamePgn } = useChessActions(gameAtom);
|
||||
const setEval = useSetAtom(gameEvalAtom);
|
||||
const setGameEval = useSetAtom(gameEvalAtom);
|
||||
const setBoardOrientation = useSetAtom(boardOrientationAtom);
|
||||
|
||||
const router = useRouter();
|
||||
@@ -31,20 +32,19 @@ export default function GameReport() {
|
||||
useEffect(() => {
|
||||
if (!gameId) {
|
||||
resetBoard();
|
||||
setEval(undefined);
|
||||
setGameEval(undefined);
|
||||
setBoardOrientation(true);
|
||||
setGamePgn(new Chess().pgn());
|
||||
}
|
||||
}, [gameId, setEval, setBoardOrientation, resetBoard, setGamePgn]);
|
||||
}, [gameId, setGameEval, setBoardOrientation, resetBoard, setGamePgn]);
|
||||
|
||||
return (
|
||||
<Grid container gap={4} justifyContent="space-evenly" alignItems="start">
|
||||
<Grid container gap={4} justifyContent="space-evenly" alignItems="center">
|
||||
<Board />
|
||||
|
||||
<Grid
|
||||
container
|
||||
item
|
||||
marginTop={{ xs: 0, lg: "2.5em" }}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
borderRadius={2}
|
||||
@@ -61,21 +61,38 @@ export default function GameReport() {
|
||||
padding={3}
|
||||
rowGap={3}
|
||||
style={{
|
||||
maxWidth: "1100px",
|
||||
maxWidth: "1200px",
|
||||
}}
|
||||
maxHeight={{ lg: "calc(100vh - 150px)", xs: "900px" }}
|
||||
display="grid"
|
||||
gridTemplateRows="repeat(4, auto) fit-content(100%)"
|
||||
>
|
||||
{isLgOrGreater ? <ReviewPanelHeader /> : <ReviewPanelToolBar />}
|
||||
|
||||
<Divider sx={{ width: "90%" }} />
|
||||
<Divider sx={{ marginX: "5%" }} />
|
||||
|
||||
<ReviewPanelBody />
|
||||
|
||||
<Divider sx={{ width: "90%" }} />
|
||||
<Divider sx={{ marginX: "5%" }} />
|
||||
|
||||
<Grid
|
||||
container
|
||||
item
|
||||
justifyContent="center"
|
||||
alignItems="start"
|
||||
height="100%"
|
||||
minHeight={{ lg: "50px", xs: undefined }}
|
||||
sx={{ overflow: "hidden" }}
|
||||
>
|
||||
<MovesPanel />
|
||||
|
||||
<MovesClassificationsRecap />
|
||||
</Grid>
|
||||
|
||||
<Divider sx={{ marginX: "5%" }} />
|
||||
|
||||
{isLgOrGreater ? <ReviewPanelToolBar /> : <ReviewPanelHeader />}
|
||||
</Grid>
|
||||
|
||||
<MovesClassificationsRecap />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ export default function BoardContainer() {
|
||||
const screenSize = useScreenSize();
|
||||
const boardOrientation = useAtomValue(boardOrientationAtom);
|
||||
const showBestMoveArrow = useAtomValue(showBestMoveArrowAtom);
|
||||
const { whiteName, blackName } = usePlayersNames(gameAtom);
|
||||
const { whiteName, whiteElo, blackName, blackElo } =
|
||||
usePlayersNames(gameAtom);
|
||||
|
||||
const boardSize = useMemo(() => {
|
||||
const width = screenSize.width;
|
||||
@@ -28,7 +29,7 @@ export default function BoardContainer() {
|
||||
return Math.min(width, height - 150);
|
||||
}
|
||||
|
||||
return Math.min(width - 600, height * 0.95);
|
||||
return Math.min(width - 700, height * 0.95);
|
||||
}, [screenSize]);
|
||||
|
||||
return (
|
||||
@@ -37,8 +38,8 @@ export default function BoardContainer() {
|
||||
boardSize={boardSize}
|
||||
canPlay={true}
|
||||
gameAtom={boardAtom}
|
||||
whitePlayer={whiteName}
|
||||
blackPlayer={blackName}
|
||||
whitePlayer={`${whiteName} (${whiteElo})`}
|
||||
blackPlayer={`${blackName} (${blackElo})`}
|
||||
boardOrientation={boardOrientation ? Color.White : Color.Black}
|
||||
currentPositionAtom={currentPositionAtom}
|
||||
showBestMoveArrow={showBestMoveArrow}
|
||||
|
||||
@@ -26,14 +26,16 @@ export const useCurrentPosition = (engineName?: EngineName) => {
|
||||
lastMove: board.history({ verbose: true }).at(-1),
|
||||
};
|
||||
|
||||
if (gameEval) {
|
||||
const boardHistory = board.history();
|
||||
const gameHistory = game.history();
|
||||
const boardHistory = board.history();
|
||||
const gameHistory = game.history();
|
||||
|
||||
if (
|
||||
boardHistory.length <= gameHistory.length &&
|
||||
gameHistory.slice(0, boardHistory.length).join() === boardHistory.join()
|
||||
) {
|
||||
if (
|
||||
boardHistory.length <= gameHistory.length &&
|
||||
gameHistory.slice(0, boardHistory.length).join() === boardHistory.join()
|
||||
) {
|
||||
position.currentMoveIdx = boardHistory.length;
|
||||
|
||||
if (gameEval) {
|
||||
const evalIndex = boardHistory.length;
|
||||
|
||||
position.eval = gameEval.positions[evalIndex];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Color, MoveClassification } from "@/types/enums";
|
||||
import { Grid, Typography } from "@mui/material";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { boardAtom, gameAtom, gameEvalAtom } from "../states";
|
||||
import { boardAtom, gameAtom, gameEvalAtom } from "../../states";
|
||||
import { useMemo } from "react";
|
||||
import { moveClassificationColors } from "@/components/board/squareRenderer";
|
||||
import Image from "next/image";
|
||||
@@ -68,8 +68,6 @@ export default function ClassificationRow({ classification }: Props) {
|
||||
}
|
||||
};
|
||||
|
||||
if (!gameEval?.positions.length) return null;
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
@@ -99,6 +97,7 @@ export default function ClassificationRow({ classification }: Props) {
|
||||
alignItems="center"
|
||||
width={"7rem"}
|
||||
gap={1}
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Image
|
||||
src={`/icons/${classification}.png`}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { usePlayersNames } from "@/hooks/usePlayerNames";
|
||||
import { Grid, Typography } from "@mui/material";
|
||||
import { gameAtom, gameEvalAtom } from "../states";
|
||||
import { gameAtom, gameEvalAtom } from "../../states";
|
||||
import { MoveClassification } from "@/types/enums";
|
||||
import ClassificationRow from "./classificationRow";
|
||||
import { useAtomValue } from "jotai";
|
||||
@@ -17,20 +17,10 @@ export default function MovesClassificationsRecap() {
|
||||
item
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
borderRadius={2}
|
||||
border={1}
|
||||
borderColor={"secondary.main"}
|
||||
sx={{
|
||||
backgroundColor: "secondary.main",
|
||||
borderColor: "primary.main",
|
||||
borderWidth: 2,
|
||||
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)",
|
||||
}}
|
||||
marginTop={{ xs: 0, lg: "2.5em" }}
|
||||
paddingY={3}
|
||||
rowGap={2}
|
||||
xs
|
||||
style={{ maxWidth: "50rem" }}
|
||||
xs={6}
|
||||
sx={{ scrollbarWidth: "thin", overflowY: "auto" }}
|
||||
maxHeight="100%"
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
@@ -40,13 +30,13 @@ export default function MovesClassificationsRecap() {
|
||||
wrap="nowrap"
|
||||
xs={12}
|
||||
>
|
||||
<Typography width="12rem" align="center">
|
||||
<Typography width="12rem" align="center" noWrap>
|
||||
{whiteName}
|
||||
</Typography>
|
||||
|
||||
<Typography width="7rem" />
|
||||
|
||||
<Typography width="12rem" align="center">
|
||||
<Typography width="12rem" align="center" noWrap>
|
||||
{blackName}
|
||||
</Typography>
|
||||
</Grid>
|
||||
62
src/sections/analysis/reviewPanelBody/movesPanel/index.tsx
Normal file
62
src/sections/analysis/reviewPanelBody/movesPanel/index.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Grid } from "@mui/material";
|
||||
import MovesLine from "./movesLine";
|
||||
import { useMemo } from "react";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { gameAtom, gameEvalAtom } from "../../states";
|
||||
import { MoveClassification } from "@/types/enums";
|
||||
|
||||
export default function MovesPanel() {
|
||||
const game = useAtomValue(gameAtom);
|
||||
const gameEval = useAtomValue(gameEvalAtom);
|
||||
|
||||
const gameMoves = useMemo(() => {
|
||||
const history = game.history();
|
||||
if (!history.length) return undefined;
|
||||
|
||||
const moves: { san: string; moveClassification?: MoveClassification }[][] =
|
||||
[];
|
||||
|
||||
for (let i = 0; i < history.length; i += 2) {
|
||||
const items = [
|
||||
{
|
||||
san: history[i],
|
||||
moveClassification: gameEval?.positions[i + 1]?.moveClassification,
|
||||
},
|
||||
];
|
||||
|
||||
if (history[i + 1]) {
|
||||
items.push({
|
||||
san: history[i + 1],
|
||||
moveClassification: gameEval?.positions[i + 2]?.moveClassification,
|
||||
});
|
||||
}
|
||||
|
||||
moves.push(items);
|
||||
}
|
||||
|
||||
return moves;
|
||||
}, [game, gameEval]);
|
||||
|
||||
if (!gameMoves) return null;
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
item
|
||||
justifyContent="center"
|
||||
alignItems="start"
|
||||
gap={1}
|
||||
sx={{ scrollbarWidth: "thin", overflowY: "auto" }}
|
||||
maxHeight="100%"
|
||||
xs={6}
|
||||
>
|
||||
{gameMoves?.map((moves, idx) => (
|
||||
<MovesLine
|
||||
key={`${moves.map(({ san }) => san).join()}-${idx}`}
|
||||
moves={moves}
|
||||
moveNb={idx + 1}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { MoveClassification } from "@/types/enums";
|
||||
import { Grid, Typography } from "@mui/material";
|
||||
import { moveClassificationColors } from "@/components/board/squareRenderer";
|
||||
import Image from "next/image";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { boardAtom, currentPositionAtom, gameAtom } from "../../states";
|
||||
import { useChessActions } from "@/hooks/useChessActions";
|
||||
import { useEffect } from "react";
|
||||
import { isInViewport } from "@/lib/helpers";
|
||||
|
||||
interface Props {
|
||||
san: string;
|
||||
moveClassification?: MoveClassification;
|
||||
moveIdx: number;
|
||||
}
|
||||
|
||||
export default function MoveItem({ san, moveClassification, moveIdx }: Props) {
|
||||
const game = useAtomValue(gameAtom);
|
||||
const { goToMove } = useChessActions(boardAtom);
|
||||
const position = useAtomValue(currentPositionAtom);
|
||||
const color = getMoveColor(moveClassification);
|
||||
|
||||
const isCurrentMove = position?.currentMoveIdx === moveIdx;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCurrentMove) return;
|
||||
const moveItem = document.getElementById(`move-${moveIdx}`);
|
||||
if (!moveItem || !isInViewport(moveItem)) return;
|
||||
moveItem.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}, [isCurrentMove, moveIdx]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (isCurrentMove) return;
|
||||
goToMove(moveIdx, game);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
gap={1}
|
||||
width="5rem"
|
||||
wrap="nowrap"
|
||||
onClick={handleClick}
|
||||
paddingY={0.5}
|
||||
sx={{
|
||||
cursor: isCurrentMove ? undefined : "pointer",
|
||||
backgroundColor: isCurrentMove ? "#4f4f4f" : undefined,
|
||||
borderRadius: 1,
|
||||
}}
|
||||
id={`move-${moveIdx}`}
|
||||
>
|
||||
{color && (
|
||||
<Image
|
||||
src={`/icons/${moveClassification}.png`}
|
||||
alt="move-icon"
|
||||
width={15}
|
||||
height={15}
|
||||
style={{
|
||||
maxWidth: "3.6vw",
|
||||
maxHeight: "3.6vw",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Typography color={getMoveColor(moveClassification)}>{san}</Typography>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
const getMoveColor = (moveClassification?: MoveClassification) => {
|
||||
if (
|
||||
!moveClassification ||
|
||||
moveClassificationsToIgnore.includes(moveClassification)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return moveClassificationColors[moveClassification];
|
||||
};
|
||||
|
||||
const moveClassificationsToIgnore: MoveClassification[] = [
|
||||
MoveClassification.Good,
|
||||
MoveClassification.Excellent,
|
||||
];
|
||||
@@ -0,0 +1,27 @@
|
||||
import { MoveClassification } from "@/types/enums";
|
||||
import { Grid, Typography } from "@mui/material";
|
||||
import MoveItem from "./moveItem";
|
||||
|
||||
interface Props {
|
||||
moves: { san: string; moveClassification?: MoveClassification }[];
|
||||
moveNb: number;
|
||||
}
|
||||
|
||||
export default function MovesLine({ moves, moveNb }: Props) {
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
item
|
||||
justifyContent="space-evenly"
|
||||
alignItems="start"
|
||||
xs={12}
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Typography width="2rem">{moveNb}.</Typography>
|
||||
|
||||
<MoveItem {...moves[0]} moveIdx={(moveNb - 1) * 2 + 1} />
|
||||
|
||||
<MoveItem {...moves[1]} moveIdx={(moveNb - 1) * 2 + 2} />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
44
src/sections/analysis/reviewPanelHeader/gamePanel.tsx
Normal file
44
src/sections/analysis/reviewPanelHeader/gamePanel.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
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;
|
||||
|
||||
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>
|
||||
Site : {gameFromUrl?.site || game.header().Site || "?"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item container xs justifyContent="center" alignItems="center">
|
||||
<Typography noWrap>
|
||||
Date : {gameFromUrl?.date || game.header().Date || "?"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item container xs justifyContent="center" alignItems="center">
|
||||
<Typography noWrap>
|
||||
Result :{" "}
|
||||
{gameFromUrl?.termination || game.header().Termination || "?"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { Grid, Typography } from "@mui/material";
|
||||
import { useGameDatabase } from "@/hooks/useGameDatabase";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { gameAtom } from "../../states";
|
||||
import PlayerInfo from "./playerInfo";
|
||||
|
||||
export default function GamePanel() {
|
||||
const { gameFromUrl } = useGameDatabase();
|
||||
const game = useAtomValue(gameAtom);
|
||||
|
||||
const hasGameInfo = gameFromUrl !== undefined || !!game.header().White;
|
||||
|
||||
if (!hasGameInfo) return null;
|
||||
|
||||
return (
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
xs={12}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
>
|
||||
<Grid item container xs={12} justifyContent="center" alignItems="center">
|
||||
<PlayerInfo color="white" />
|
||||
|
||||
<Typography marginX={1.5}>vs</Typography>
|
||||
|
||||
<PlayerInfo color="black" />
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
xs={11}
|
||||
justifyContent="space-evenly"
|
||||
alignItems="center"
|
||||
rowGap={1}
|
||||
columnGap={3}
|
||||
>
|
||||
<Grid item container xs justifyContent="center" alignItems="center">
|
||||
<Typography noWrap>
|
||||
Site : {gameFromUrl?.site || game.header().Site || "?"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item container xs justifyContent="center" alignItems="center">
|
||||
<Typography noWrap>
|
||||
Date : {gameFromUrl?.date || game.header().Date || "?"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item container xs justifyContent="center" alignItems="center">
|
||||
<Typography noWrap>
|
||||
Result :{" "}
|
||||
{gameFromUrl?.termination || game.header().Termination || "?"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import { useGameDatabase } from "@/hooks/useGameDatabase";
|
||||
import { Grid, Typography } from "@mui/material";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { gameAtom } from "../../states";
|
||||
|
||||
interface Props {
|
||||
color: "white" | "black";
|
||||
}
|
||||
|
||||
export default function PlayerInfo({ color }: Props) {
|
||||
const { gameFromUrl } = useGameDatabase();
|
||||
const game = useAtomValue(gameAtom);
|
||||
|
||||
const rating =
|
||||
gameFromUrl?.[color]?.rating ||
|
||||
game.header()[color === "white" ? "WhiteElo" : "BlackElo"];
|
||||
|
||||
const playerName =
|
||||
gameFromUrl?.[color]?.name ||
|
||||
game.header()[color === "white" ? "White" : "Black"];
|
||||
|
||||
return (
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
xs={5}
|
||||
justifyContent={color === "white" ? "flex-end" : "flex-start"}
|
||||
alignItems="center"
|
||||
gap={0.5}
|
||||
>
|
||||
<Typography>
|
||||
{playerName || (color === "white" ? "White" : "Black")}
|
||||
</Typography>
|
||||
|
||||
<Typography>{rating ? `(${rating})` : "(?)"}</Typography>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -45,6 +45,7 @@ export interface CurrentPosition {
|
||||
lastMove?: Move;
|
||||
eval?: PositionEval;
|
||||
lastEval?: PositionEval;
|
||||
currentMoveIdx?: number;
|
||||
}
|
||||
|
||||
export interface EvaluateGameParams {
|
||||
|
||||
Reference in New Issue
Block a user