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 { setGameHeaders } from "@/lib/chess";
|
||||||
import { playIllegalMoveSound, playSoundFromMove } from "@/lib/sounds";
|
import {
|
||||||
|
playGameEndSound,
|
||||||
|
playIllegalMoveSound,
|
||||||
|
playSoundFromMove,
|
||||||
|
} from "@/lib/sounds";
|
||||||
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";
|
||||||
@@ -76,7 +80,11 @@ export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setGame(newGame);
|
setGame(newGame);
|
||||||
playSoundFromMove(lastMove);
|
if (lastMove) {
|
||||||
|
playSoundFromMove(lastMove);
|
||||||
|
} else {
|
||||||
|
playGameEndSound();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[setGame]
|
[setGame]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,8 +11,16 @@ export const usePlayersNames = (gameAtom: PrimitiveAtom<Chess>) => {
|
|||||||
const blackName =
|
const blackName =
|
||||||
gameFromUrl?.black?.name || game.header()["Black"] || "Black";
|
gameFromUrl?.black?.name || game.header()["Black"] || "Black";
|
||||||
|
|
||||||
|
const whiteElo =
|
||||||
|
gameFromUrl?.white?.rating || game.header()["WhiteElo"] || "?";
|
||||||
|
|
||||||
|
const blackElo =
|
||||||
|
gameFromUrl?.black?.rating || game.header()["BlackElo"] || "?";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
whiteName,
|
whiteName,
|
||||||
blackName,
|
blackName,
|
||||||
|
whiteElo,
|
||||||
|
blackElo,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
getHarmonicMean,
|
getHarmonicMean,
|
||||||
getStandardDeviation,
|
getStandardDeviation,
|
||||||
getWeightedMean,
|
getWeightedMean,
|
||||||
} from "@/lib/helpers";
|
} from "@/lib/math";
|
||||||
import { Accuracy, PositionEval } from "@/types/eval";
|
import { Accuracy, PositionEval } from "@/types/eval";
|
||||||
import { getPositionWinPercentage } from "./winPercentage";
|
import { getPositionWinPercentage } from "./winPercentage";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ceilsNumber } from "@/lib/helpers";
|
import { ceilsNumber } from "@/lib/math";
|
||||||
import { LineEval, PositionEval } from "@/types/eval";
|
import { LineEval, PositionEval } from "@/types/eval";
|
||||||
|
|
||||||
export const getPositionWinPercentage = (position: PositionEval): number => {
|
export const getPositionWinPercentage = (position: PositionEval): number => {
|
||||||
|
|||||||
@@ -6,36 +6,10 @@ export const capitalize = (s: string) => {
|
|||||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ceilsNumber = (number: number, min: number, max: number) => {
|
export const isInViewport = (element: HTMLElement) => {
|
||||||
if (number > max) return max;
|
const rect = element.getBoundingClientRect();
|
||||||
if (number < min) return min;
|
return (
|
||||||
return number;
|
rect.top >= 0 &&
|
||||||
};
|
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|||||||
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 { useChessActions } from "@/hooks/useChessActions";
|
||||||
import Board from "@/sections/analysis/board";
|
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 ReviewPanelBody from "@/sections/analysis/reviewPanelBody";
|
||||||
import ReviewPanelHeader from "@/sections/analysis/reviewPanelHeader";
|
import ReviewPanelHeader from "@/sections/analysis/reviewPanelHeader";
|
||||||
import ReviewPanelToolBar from "@/sections/analysis/reviewPanelToolbar";
|
import ReviewPanelToolBar from "@/sections/analysis/reviewPanelToolbar";
|
||||||
@@ -15,6 +15,7 @@ import { Chess } from "chess.js";
|
|||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import MovesPanel from "@/sections/analysis/reviewPanelBody/movesPanel";
|
||||||
|
|
||||||
export default function GameReport() {
|
export default function GameReport() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -22,7 +23,7 @@ export default function GameReport() {
|
|||||||
|
|
||||||
const { reset: resetBoard } = useChessActions(boardAtom);
|
const { reset: resetBoard } = useChessActions(boardAtom);
|
||||||
const { setPgn: setGamePgn } = useChessActions(gameAtom);
|
const { setPgn: setGamePgn } = useChessActions(gameAtom);
|
||||||
const setEval = useSetAtom(gameEvalAtom);
|
const setGameEval = useSetAtom(gameEvalAtom);
|
||||||
const setBoardOrientation = useSetAtom(boardOrientationAtom);
|
const setBoardOrientation = useSetAtom(boardOrientationAtom);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -31,20 +32,19 @@ export default function GameReport() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!gameId) {
|
if (!gameId) {
|
||||||
resetBoard();
|
resetBoard();
|
||||||
setEval(undefined);
|
setGameEval(undefined);
|
||||||
setBoardOrientation(true);
|
setBoardOrientation(true);
|
||||||
setGamePgn(new Chess().pgn());
|
setGamePgn(new Chess().pgn());
|
||||||
}
|
}
|
||||||
}, [gameId, setEval, setBoardOrientation, resetBoard, setGamePgn]);
|
}, [gameId, setGameEval, setBoardOrientation, resetBoard, setGamePgn]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container gap={4} justifyContent="space-evenly" alignItems="start">
|
<Grid container gap={4} justifyContent="space-evenly" alignItems="center">
|
||||||
<Board />
|
<Board />
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
item
|
item
|
||||||
marginTop={{ xs: 0, lg: "2.5em" }}
|
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
borderRadius={2}
|
borderRadius={2}
|
||||||
@@ -61,21 +61,38 @@ export default function GameReport() {
|
|||||||
padding={3}
|
padding={3}
|
||||||
rowGap={3}
|
rowGap={3}
|
||||||
style={{
|
style={{
|
||||||
maxWidth: "1100px",
|
maxWidth: "1200px",
|
||||||
}}
|
}}
|
||||||
|
maxHeight={{ lg: "calc(100vh - 150px)", xs: "900px" }}
|
||||||
|
display="grid"
|
||||||
|
gridTemplateRows="repeat(4, auto) fit-content(100%)"
|
||||||
>
|
>
|
||||||
{isLgOrGreater ? <ReviewPanelHeader /> : <ReviewPanelToolBar />}
|
{isLgOrGreater ? <ReviewPanelHeader /> : <ReviewPanelToolBar />}
|
||||||
|
|
||||||
<Divider sx={{ width: "90%" }} />
|
<Divider sx={{ marginX: "5%" }} />
|
||||||
|
|
||||||
<ReviewPanelBody />
|
<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 />}
|
{isLgOrGreater ? <ReviewPanelToolBar /> : <ReviewPanelHeader />}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<MovesClassificationsRecap />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ export default function BoardContainer() {
|
|||||||
const screenSize = useScreenSize();
|
const screenSize = useScreenSize();
|
||||||
const boardOrientation = useAtomValue(boardOrientationAtom);
|
const boardOrientation = useAtomValue(boardOrientationAtom);
|
||||||
const showBestMoveArrow = useAtomValue(showBestMoveArrowAtom);
|
const showBestMoveArrow = useAtomValue(showBestMoveArrowAtom);
|
||||||
const { whiteName, blackName } = usePlayersNames(gameAtom);
|
const { whiteName, whiteElo, blackName, blackElo } =
|
||||||
|
usePlayersNames(gameAtom);
|
||||||
|
|
||||||
const boardSize = useMemo(() => {
|
const boardSize = useMemo(() => {
|
||||||
const width = screenSize.width;
|
const width = screenSize.width;
|
||||||
@@ -28,7 +29,7 @@ export default function BoardContainer() {
|
|||||||
return Math.min(width, height - 150);
|
return Math.min(width, height - 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.min(width - 600, height * 0.95);
|
return Math.min(width - 700, height * 0.95);
|
||||||
}, [screenSize]);
|
}, [screenSize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -37,8 +38,8 @@ export default function BoardContainer() {
|
|||||||
boardSize={boardSize}
|
boardSize={boardSize}
|
||||||
canPlay={true}
|
canPlay={true}
|
||||||
gameAtom={boardAtom}
|
gameAtom={boardAtom}
|
||||||
whitePlayer={whiteName}
|
whitePlayer={`${whiteName} (${whiteElo})`}
|
||||||
blackPlayer={blackName}
|
blackPlayer={`${blackName} (${blackElo})`}
|
||||||
boardOrientation={boardOrientation ? Color.White : Color.Black}
|
boardOrientation={boardOrientation ? Color.White : Color.Black}
|
||||||
currentPositionAtom={currentPositionAtom}
|
currentPositionAtom={currentPositionAtom}
|
||||||
showBestMoveArrow={showBestMoveArrow}
|
showBestMoveArrow={showBestMoveArrow}
|
||||||
|
|||||||
@@ -26,14 +26,16 @@ export const useCurrentPosition = (engineName?: EngineName) => {
|
|||||||
lastMove: board.history({ verbose: true }).at(-1),
|
lastMove: board.history({ verbose: true }).at(-1),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (gameEval) {
|
const boardHistory = board.history();
|
||||||
const boardHistory = board.history();
|
const gameHistory = game.history();
|
||||||
const gameHistory = game.history();
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
boardHistory.length <= gameHistory.length &&
|
boardHistory.length <= gameHistory.length &&
|
||||||
gameHistory.slice(0, boardHistory.length).join() === boardHistory.join()
|
gameHistory.slice(0, boardHistory.length).join() === boardHistory.join()
|
||||||
) {
|
) {
|
||||||
|
position.currentMoveIdx = boardHistory.length;
|
||||||
|
|
||||||
|
if (gameEval) {
|
||||||
const evalIndex = boardHistory.length;
|
const evalIndex = boardHistory.length;
|
||||||
|
|
||||||
position.eval = gameEval.positions[evalIndex];
|
position.eval = gameEval.positions[evalIndex];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Color, MoveClassification } from "@/types/enums";
|
import { Color, MoveClassification } from "@/types/enums";
|
||||||
import { Grid, Typography } from "@mui/material";
|
import { Grid, Typography } from "@mui/material";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { boardAtom, gameAtom, gameEvalAtom } from "../states";
|
import { boardAtom, gameAtom, gameEvalAtom } from "../../states";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { moveClassificationColors } from "@/components/board/squareRenderer";
|
import { moveClassificationColors } from "@/components/board/squareRenderer";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
@@ -68,8 +68,6 @@ export default function ClassificationRow({ classification }: Props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!gameEval?.positions.length) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
@@ -99,6 +97,7 @@ export default function ClassificationRow({ classification }: Props) {
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
width={"7rem"}
|
width={"7rem"}
|
||||||
gap={1}
|
gap={1}
|
||||||
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src={`/icons/${classification}.png`}
|
src={`/icons/${classification}.png`}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { usePlayersNames } from "@/hooks/usePlayerNames";
|
import { usePlayersNames } from "@/hooks/usePlayerNames";
|
||||||
import { Grid, Typography } from "@mui/material";
|
import { Grid, Typography } from "@mui/material";
|
||||||
import { gameAtom, gameEvalAtom } from "../states";
|
import { gameAtom, gameEvalAtom } from "../../states";
|
||||||
import { MoveClassification } from "@/types/enums";
|
import { MoveClassification } from "@/types/enums";
|
||||||
import ClassificationRow from "./classificationRow";
|
import ClassificationRow from "./classificationRow";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
@@ -17,20 +17,10 @@ export default function MovesClassificationsRecap() {
|
|||||||
item
|
item
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="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}
|
rowGap={2}
|
||||||
xs
|
xs={6}
|
||||||
style={{ maxWidth: "50rem" }}
|
sx={{ scrollbarWidth: "thin", overflowY: "auto" }}
|
||||||
|
maxHeight="100%"
|
||||||
>
|
>
|
||||||
<Grid
|
<Grid
|
||||||
item
|
item
|
||||||
@@ -40,13 +30,13 @@ export default function MovesClassificationsRecap() {
|
|||||||
wrap="nowrap"
|
wrap="nowrap"
|
||||||
xs={12}
|
xs={12}
|
||||||
>
|
>
|
||||||
<Typography width="12rem" align="center">
|
<Typography width="12rem" align="center" noWrap>
|
||||||
{whiteName}
|
{whiteName}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography width="7rem" />
|
<Typography width="7rem" />
|
||||||
|
|
||||||
<Typography width="12rem" align="center">
|
<Typography width="12rem" align="center" noWrap>
|
||||||
{blackName}
|
{blackName}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</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;
|
lastMove?: Move;
|
||||||
eval?: PositionEval;
|
eval?: PositionEval;
|
||||||
lastEval?: PositionEval;
|
lastEval?: PositionEval;
|
||||||
|
currentMoveIdx?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EvaluateGameParams {
|
export interface EvaluateGameParams {
|
||||||
|
|||||||
Reference in New Issue
Block a user