diff --git a/public/captured-pieces.png b/public/captured-pieces.png new file mode 100644 index 0000000..9caaad9 Binary files /dev/null and b/public/captured-pieces.png differ diff --git a/src/components/board/capturedPieces.tsx b/src/components/board/capturedPieces.tsx new file mode 100644 index 0000000..137b510 --- /dev/null +++ b/src/components/board/capturedPieces.tsx @@ -0,0 +1,167 @@ +import { getCapturedPieces, getMaterialDifference } from "@/lib/chess"; +import { Color } from "@/types/enums"; +import { Grid, Typography } from "@mui/material"; +import { Chess } from "chess.js"; +import { PrimitiveAtom, useAtomValue } from "jotai"; +import { CSSProperties, useMemo } from "react"; + +export interface Props { + gameAtom: PrimitiveAtom; + color: Color; +} + +const PIECE_SCALE = 0.6; + +export default function CapturedPieces({ gameAtom, color }: Props) { + const game = useAtomValue(gameAtom); + const cssProps = useMemo(() => { + const capturedPieces = getCapturedPieces(game.fen(), color); + console.log(capturedPieces, color); + return getCapturedPiecesCSSProps(capturedPieces, color); + }, [game, color]); + + const materialDiff = useMemo(() => { + const materialDiff = getMaterialDifference(game.fen()); + return color === Color.White ? materialDiff : -materialDiff; + }, [game, color]); + + return ( + + {cssProps.map((cssProp, i) => ( + + ))} + + {materialDiff > 0 && ( + + +{materialDiff} + + )} + + ); +} + +const getCapturedPiecesCSSProps = ( + capturedPieces: Record, + color: Color +): CSSProperties[] => { + const cssProps: CSSProperties[] = []; + + if (color === Color.Black) { + if (capturedPieces.P) { + cssProps.push({ + backgroundPositionX: `-${18 * PIECE_SCALE}rem`, + backgroundPositionY: `${ + -20.1 * PIECE_SCALE + capturedPieces.P * 2.5 * PIECE_SCALE + }rem`, + width: `${0.6 * PIECE_SCALE + capturedPieces.P * 0.7 * PIECE_SCALE}rem`, + height: `${1.7 * PIECE_SCALE}rem`, + }); + } + + if (capturedPieces.B) { + cssProps.push({ + backgroundPosition: `-${24.7 * PIECE_SCALE}rem ${ + -5.1 * PIECE_SCALE + capturedPieces.B * 2.6 * PIECE_SCALE + }rem`, + width: `${0.7 * PIECE_SCALE + capturedPieces.B * 0.8 * PIECE_SCALE}rem`, + height: `${ + 1.7 * PIECE_SCALE + capturedPieces.B * 0.1 * PIECE_SCALE + }rem`, + }); + } + + if (capturedPieces.N) { + cssProps.push({ + backgroundPosition: `-${27.5 * PIECE_SCALE}rem ${ + -4.9 * PIECE_SCALE + capturedPieces.N * 2.5 * PIECE_SCALE + }rem`, + width: `${0.9 * PIECE_SCALE + capturedPieces.N * 0.7 * PIECE_SCALE}rem`, + height: `${1.9 * PIECE_SCALE}rem`, + }); + } + + if (capturedPieces.R) { + cssProps.push({ + backgroundPosition: `${ + -30.2 * PIECE_SCALE + capturedPieces.R * 0.1 * PIECE_SCALE + }rem ${-5.1 * PIECE_SCALE + capturedPieces.R * 2.5 * PIECE_SCALE}rem`, + width: `${0.7 * PIECE_SCALE + capturedPieces.R * 0.8 * PIECE_SCALE}rem`, + height: `${1.7 * PIECE_SCALE}rem`, + }); + } + + if (capturedPieces.Q) { + cssProps.push({ + backgroundPosition: `-${32.5 * PIECE_SCALE}rem ${0.1 * PIECE_SCALE}rem`, + width: `${1.8 * PIECE_SCALE}rem`, + height: `${1.9 * PIECE_SCALE}rem`, + }); + } + } else { + if (capturedPieces.p) { + cssProps.push({ + backgroundPositionX: 0, + backgroundPositionY: `${ + -20.1 * PIECE_SCALE + capturedPieces.p * 2.5 * PIECE_SCALE + }rem`, + width: `${0.6 * PIECE_SCALE + capturedPieces.p * 0.7 * PIECE_SCALE}rem`, + height: `${1.7 * PIECE_SCALE}rem`, + }); + } + + if (capturedPieces.b) { + cssProps.push({ + backgroundPosition: `-${6.7 * PIECE_SCALE}rem ${ + -5.1 * PIECE_SCALE + capturedPieces.b * 2.6 * PIECE_SCALE + }rem`, + width: `${0.7 * PIECE_SCALE + capturedPieces.b * 0.8 * PIECE_SCALE}rem`, + height: `${ + 1.7 * PIECE_SCALE + capturedPieces.b * 0.1 * PIECE_SCALE + }rem`, + }); + } + + if (capturedPieces.n) { + cssProps.push({ + backgroundPosition: `-${9.5 * PIECE_SCALE}rem ${ + -4.9 * PIECE_SCALE + capturedPieces.n * 2.5 * PIECE_SCALE + }rem`, + width: `${0.9 * PIECE_SCALE + capturedPieces.n * 0.7 * PIECE_SCALE}rem`, + height: `${1.9 * PIECE_SCALE}rem`, + }); + } + + if (capturedPieces.r) { + cssProps.push({ + backgroundPosition: `${ + -12.2 * PIECE_SCALE + capturedPieces.r * 0.1 * PIECE_SCALE + }rem ${-5.1 * PIECE_SCALE + capturedPieces.r * 2.5 * PIECE_SCALE}rem`, + width: `${0.7 * PIECE_SCALE + capturedPieces.r * 0.8 * PIECE_SCALE}rem`, + height: `${1.7 * PIECE_SCALE}rem`, + }); + } + + if (capturedPieces.q) { + cssProps.push({ + backgroundPosition: `-${14.5 * PIECE_SCALE}rem ${0.1 * PIECE_SCALE}rem`, + width: `${1.8 * PIECE_SCALE}rem`, + height: `${1.9 * PIECE_SCALE}rem`, + }); + } + } + + return cssProps; +}; diff --git a/src/components/board/index.tsx b/src/components/board/index.tsx index 32b4ccb..85514fc 100644 --- a/src/components/board/index.tsx +++ b/src/components/board/index.tsx @@ -14,6 +14,7 @@ import { Chess } from "chess.js"; import { getSquareRenderer, moveClassificationColors } from "./squareRenderer"; import { CurrentPosition } from "@/types/eval"; import EvaluationBar from "./evaluationBar"; +import CapturedPieces from "./capturedPieces"; export interface Props { id: string; @@ -230,10 +231,16 @@ export default function Board({ xs={12} justifyContent="center" alignItems="center" + columnGap={2} > - + {boardOrientation === Color.White ? blackPlayer : whitePlayer} + + - + {boardOrientation === Color.White ? whitePlayer : blackPlayer} + + diff --git a/src/hooks/usePlayerNames.ts b/src/hooks/usePlayerNames.ts index 4dd40ae..31cb6ad 100644 --- a/src/hooks/usePlayerNames.ts +++ b/src/hooks/usePlayerNames.ts @@ -12,10 +12,10 @@ export const usePlayersNames = (gameAtom: PrimitiveAtom) => { gameFromUrl?.black?.name || game.header()["Black"] || "Black"; const whiteElo = - gameFromUrl?.white?.rating || game.header()["WhiteElo"] || "?"; + gameFromUrl?.white?.rating || game.header()["WhiteElo"] || undefined; const blackElo = - gameFromUrl?.black?.rating || game.header()["BlackElo"] || "?"; + gameFromUrl?.black?.rating || game.header()["BlackElo"] || undefined; return { whiteName, diff --git a/src/lib/chess.ts b/src/lib/chess.ts index 8392e4a..10835de 100644 --- a/src/lib/chess.ts +++ b/src/lib/chess.ts @@ -267,3 +267,31 @@ export const isCheck = (fen: string): boolean => { const game = new Chess(fen); return game.inCheck(); }; + +export const getCapturedPieces = ( + fen: string, + color: Color +): Record => { + const capturedPieces: Record = {}; + if (color === Color.White) { + capturedPieces.p = 8; + capturedPieces.r = 2; + capturedPieces.n = 2; + capturedPieces.b = 2; + capturedPieces.q = 1; + } else { + capturedPieces.P = 8; + capturedPieces.R = 2; + capturedPieces.N = 2; + capturedPieces.B = 2; + capturedPieces.Q = 1; + } + + const fenPiecePlacement = fen.split(" ")[0]; + for (const piece of Object.keys(capturedPieces)) { + const count = fenPiecePlacement.match(new RegExp(piece, "g"))?.length; + if (count) capturedPieces[piece] = (capturedPieces[piece] ?? 0) - count; + } + + return capturedPieces; +}; diff --git a/src/sections/analysis/board/index.tsx b/src/sections/analysis/board/index.tsx index bfb313d..e943b7e 100644 --- a/src/sections/analysis/board/index.tsx +++ b/src/sections/analysis/board/index.tsx @@ -38,8 +38,8 @@ export default function BoardContainer() { boardSize={boardSize} canPlay={true} gameAtom={boardAtom} - whitePlayer={`${whiteName} (${whiteElo})`} - blackPlayer={`${blackName} (${blackElo})`} + whitePlayer={whiteElo ? `${whiteName} (${whiteElo})` : whiteName} + blackPlayer={blackElo ? `${blackName} (${blackElo})` : blackName} boardOrientation={boardOrientation ? Color.White : Color.Black} currentPositionAtom={currentPositionAtom} showBestMoveArrow={showBestMoveArrow}