diff --git a/src/sections/analysis/panelBody/analysisTab/chess_merida_unicode.ttf b/src/components/prettyMoveSan/chess_merida_unicode.ttf similarity index 100% rename from src/sections/analysis/panelBody/analysisTab/chess_merida_unicode.ttf rename to src/components/prettyMoveSan/chess_merida_unicode.ttf diff --git a/src/components/prettyMoveSan/index.tsx b/src/components/prettyMoveSan/index.tsx new file mode 100644 index 0000000..7c011d0 --- /dev/null +++ b/src/components/prettyMoveSan/index.tsx @@ -0,0 +1,71 @@ +import { + Box, + BoxProps, + Typography, + TypographyProps, + useTheme, +} from "@mui/material"; +import localFont from "next/font/local"; +import { useMemo } from "react"; + +const chessFont = localFont({ + src: "./chess_merida_unicode.ttf", +}); + +interface Props { + san: string; + color: "w" | "b"; + additionalText?: string; + typographyProps?: TypographyProps; + boxProps?: BoxProps; +} + +export default function PrettyMoveSan({ + san, + color, + additionalText, + typographyProps, + boxProps, +}: Props) { + const theme = useTheme(); + const isDarkMode = theme.palette.mode === "dark"; + + const { icon, text } = useMemo(() => { + const firstChar = san.charAt(0); + + const isPiece = ["K", "Q", "R", "B", "N"].includes(firstChar); + if (!isPiece) return { text: san }; + + const pieceColor = isDarkMode ? color : color === "w" ? "b" : "w"; + const icon = unicodeMap[firstChar][pieceColor]; + + return { icon, text: san.slice(1) }; + }, [san, color, isDarkMode]); + + return ( + + {icon && ( + + {icon} + + )} + + + {text} + {additionalText} + + + ); +} + +const unicodeMap: Record> = { + K: { w: "♚", b: "♔" }, + Q: { w: "♛", b: "♕" }, + R: { w: "♜", b: "♖" }, + B: { w: "♝", b: "♗" }, + N: { w: "♞", b: "♘" }, +}; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index f728e12..a929c8f 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -86,7 +86,7 @@ export default function GameAnalysis() { gridTemplateRows={ gameEval ? "repeat(2, auto) max-content fit-content(100%) fit-content(100%) auto" - : "repeat(3, auto) fit-content(100%)" + : "repeat(2, auto) max-content fit-content(100%)" } size={{ xs: 12, diff --git a/src/sections/analysis/panelBody/analysisTab/lineEvaluation.tsx b/src/sections/analysis/panelBody/analysisTab/lineEvaluation.tsx index aa7e735..522131d 100644 --- a/src/sections/analysis/panelBody/analysisTab/lineEvaluation.tsx +++ b/src/sections/analysis/panelBody/analysisTab/lineEvaluation.tsx @@ -1,21 +1,16 @@ import { LineEval } from "@/types/eval"; -import { Box, ListItem, Skeleton, Typography, useTheme } from "@mui/material"; +import { ListItem, Skeleton, Typography } from "@mui/material"; import { useAtomValue } from "jotai"; import { boardAtom } from "../../states"; import { getLineEvalLabel, moveLineUciToSan } from "@/lib/chess"; -import localFont from "next/font/local"; import { useChessActions } from "@/hooks/useChessActions"; - -const myFont = localFont({ - src: "./chess_merida_unicode.ttf", -}); +import PrettyMoveSan from "@/components/prettyMoveSan"; interface Props { line: LineEval; } export default function LineEvaluation({ line }: Props) { - const theme = useTheme(); const board = useAtomValue(boardAtom); const { addMoves } = useChessActions(boardAtom); const lineLabel = getLineEvalLabel(line); @@ -27,23 +22,11 @@ export default function LineEvaluation({ line }: Props) { const showSkeleton = line.depth < 6; const uciToSan = moveLineUciToSan(board.fen()); - const initialTurn = board.turn(); - const isDarkMode = theme.palette.mode === "dark"; + const turn = board.turn(); - const formatSan = ( - san: string, - moveIdx: number - ): { icon?: string; text: string } => { - const firstChar = san.charAt(0); - - const isPiece = ["K", "Q", "R", "B", "N"].includes(firstChar); - if (!isPiece) return { text: san }; - - const turn = isDarkMode ? initialTurn : initialTurn === "w" ? "b" : "w"; + const getColorFromMoveIdx = (moveIdx: number): "w" | "b" => { const moveColor = moveIdx % 2 === 0 ? turn : turn === "w" ? "b" : "w"; - const icon = unicodeMap[firstChar][moveColor]; - - return { icon, text: san.slice(1) }; + return moveColor; }; return ( @@ -85,38 +68,28 @@ export default function LineEvaluation({ line }: Props) { ) : ( line.pv.map((uci, i) => { const san = uciToSan(uci); - const { icon, text } = formatSan(san, i); + const moveColor = getColorFromMoveIdx(i); return ( - { - addMoves(line.pv.slice(0, i + 1)); - }} - sx={{ - cursor: "pointer", - ml: i ? 0.5 : 0, - transition: "opacity 0.2s ease-in-out", - "&:hover": { - opacity: 0.5, + san={san} + color={moveColor} + additionalText={i < line.pv.length - 1 ? "," : ""} + boxProps={{ + onClick: () => { + addMoves(line.pv.slice(0, i + 1)); + }, + sx: { + cursor: "pointer", + ml: i ? 0.5 : 0, + transition: "opacity 0.2s ease-in-out", + "&:hover": { + opacity: 0.5, + }, }, }} - > - {icon && ( - - {icon} - - )} - - - {text} - {i < line.pv.length - 1 && ","} - - + /> ); }) )} @@ -124,11 +97,3 @@ export default function LineEvaluation({ line }: Props) { ); } - -const unicodeMap: Record> = { - K: { w: "♚", b: "♔" }, - Q: { w: "♛", b: "♕" }, - R: { w: "♜", b: "♖" }, - B: { w: "♝", b: "♗" }, - N: { w: "♞", b: "♘" }, -}; diff --git a/src/sections/analysis/panelBody/analysisTab/moveInfo.tsx b/src/sections/analysis/panelBody/analysisTab/moveInfo.tsx index 82af1ea..0887c0f 100644 --- a/src/sections/analysis/panelBody/analysisTab/moveInfo.tsx +++ b/src/sections/analysis/panelBody/analysisTab/moveInfo.tsx @@ -5,6 +5,7 @@ import { useMemo } from "react"; import { moveLineUciToSan } from "@/lib/chess"; import { MoveClassification } from "@/types/enums"; import Image from "next/image"; +import PrettyMoveSan from "@/components/prettyMoveSan"; export default function MoveInfo() { const position = useAtomValue(currentPositionAtom); @@ -56,18 +57,13 @@ export default function MoveInfo() { } const moveClassification = position.eval?.moveClassification; - const moveLabel = moveClassification - ? `${position.lastMove?.san} is ${moveClassificationLabels[moveClassification]}` - : null; - const bestMoveLabel = - moveClassification === MoveClassification.Best || - moveClassification === MoveClassification.Opening || - moveClassification === MoveClassification.Forced || - moveClassification === MoveClassification.Splendid || - moveClassification === MoveClassification.Perfect - ? null - : `${bestMoveSan} was the best move`; + const showBestMoveLabel = + moveClassification !== MoveClassification.Best && + moveClassification !== MoveClassification.Opening && + moveClassification !== MoveClassification.Forced && + moveClassification !== MoveClassification.Splendid && + moveClassification !== MoveClassification.Perfect; return ( - {moveLabel && ( + {moveClassification && ( - {moveClassification && ( - move-icon - )} - - {moveLabel} - + move-icon + + )} - {bestMoveLabel && ( + {showBestMoveLabel && ( - - {bestMoveLabel} - + )} diff --git a/src/sections/analysis/panelBody/classificationTab/movesPanel/index.tsx b/src/sections/analysis/panelBody/classificationTab/movesPanel/index.tsx index d310602..7d8e48c 100644 --- a/src/sections/analysis/panelBody/classificationTab/movesPanel/index.tsx +++ b/src/sections/analysis/panelBody/classificationTab/movesPanel/index.tsx @@ -42,7 +42,8 @@ export default function MovesPanel() { container justifyContent="center" alignItems="start" - gap={0.6} + gap={0.7} + paddingY={1} sx={{ scrollbarWidth: "thin", overflowY: "auto" }} height="100%" size={6} diff --git a/src/sections/analysis/panelBody/classificationTab/movesPanel/moveItem.tsx b/src/sections/analysis/panelBody/classificationTab/movesPanel/moveItem.tsx index 7ca2324..b954234 100644 --- a/src/sections/analysis/panelBody/classificationTab/movesPanel/moveItem.tsx +++ b/src/sections/analysis/panelBody/classificationTab/movesPanel/moveItem.tsx @@ -1,5 +1,5 @@ import { MoveClassification } from "@/types/enums"; -import { Grid2 as Grid, Typography } from "@mui/material"; +import { Grid2 as Grid } from "@mui/material"; import Image from "next/image"; import { useAtomValue } from "jotai"; import { boardAtom, currentPositionAtom, gameAtom } from "../../../states"; @@ -7,14 +7,21 @@ import { useChessActions } from "@/hooks/useChessActions"; import { useEffect } from "react"; import { isInViewport } from "@/lib/helpers"; import { CLASSIFICATION_COLORS } from "@/constants"; +import PrettyMoveSan from "@/components/prettyMoveSan"; interface Props { san: string; moveClassification?: MoveClassification; moveIdx: number; + moveColor: "w" | "b"; } -export default function MoveItem({ san, moveClassification, moveIdx }: Props) { +export default function MoveItem({ + san, + moveClassification, + moveIdx, + moveColor, +}: Props) { const game = useAtomValue(gameAtom); const { goToMove } = useChessActions(boardAtom); const position = useAtomValue(currentPositionAtom); @@ -47,7 +54,7 @@ export default function MoveItem({ san, moveClassification, moveIdx }: Props) { width="5rem" wrap="nowrap" onClick={handleClick} - paddingY={0.6} + paddingY={0.5} sx={(theme) => ({ cursor: isCurrentMove ? undefined : "pointer", backgroundColor: @@ -74,9 +81,8 @@ export default function MoveItem({ san, moveClassification, moveIdx }: Props) { }} /> )} - - {san} - + + ); } diff --git a/src/sections/analysis/panelBody/classificationTab/movesPanel/movesLine.tsx b/src/sections/analysis/panelBody/classificationTab/movesPanel/movesLine.tsx index 51acb1d..875aebc 100644 --- a/src/sections/analysis/panelBody/classificationTab/movesPanel/movesLine.tsx +++ b/src/sections/analysis/panelBody/classificationTab/movesPanel/movesLine.tsx @@ -1,5 +1,5 @@ import { MoveClassification } from "@/types/enums"; -import { Grid2 as Grid, Typography } from "@mui/material"; +import { Box, Grid2 as Grid, Typography } from "@mui/material"; import MoveItem from "./moveItem"; interface Props { @@ -20,9 +20,13 @@ export default function MovesLine({ moves, moveNb }: Props) { {moveNb}. - + - + {moves[1] ? ( + + ) : ( + + )} ); }