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:
GuillaumeSD
2024-04-06 01:38:06 +02:00
parent d9b322d9fa
commit 3d0d1c41a8
18 changed files with 328 additions and 176 deletions

View File

@@ -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}

View File

@@ -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];

View File

@@ -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`}

View File

@@ -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>

View 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>
);
}

View File

@@ -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,
];

View File

@@ -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>
);
}

View 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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}