feat : new captured piece icons

This commit is contained in:
GuillaumeSD
2025-05-25 18:16:43 +02:00
parent b1a3239676
commit ea28844d93
5 changed files with 91 additions and 151 deletions

View File

@@ -16,15 +16,15 @@
</div> </div>
<br /> <br />
Chesskit is an open-source chess web app to play, view and analyze your chess games for free on any device with Stockfish ! Chesskit is an open-source chess website to play, view, analyze and review your chess games for free on any device with Stockfish !
## Mission ## Mission
It aims to offer all the features it can from the best chess apps, while being free and open-source. It is designed to be easy to use, fast, and reliable. Chesskit aims to offer all the chess related features it can, while being free and open-source. It is designed to be easy to use, fast, and reliable.
## Features ## Features
- Load and analyze games from [chess.com](https://chess.com) and [lichess.org](https://lichess.org) - Load and review games from [chess.com](https://chess.com) and [lichess.org](https://lichess.org)
- Analysis board with live engine evaluation, custom arrows, evaluation graph, ... - Analysis board with live engine evaluation, custom arrows, evaluation graph, ...
- Moves classification (Brilliant, Great, Good, Mistake, Blunder, ...) - Moves classification (Brilliant, Great, Good, Mistake, Blunder, ...)
- Chess960 and Puzzles support - Chess960 and Puzzles support

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,7 +1,7 @@
import { getCapturedPieces, getMaterialDifference } from "@/lib/chess"; import { getCapturedPieces, getMaterialDifference } from "@/lib/chess";
import { Color } from "@/types/enums"; import { Color } from "@/types/enums";
import { Grid2 as Grid, Typography } from "@mui/material"; import { Box, Grid2 as Grid, Stack, Typography } from "@mui/material";
import { CSSProperties, useMemo } from "react"; import { ReactElement, useMemo } from "react";
export interface Props { export interface Props {
fen: string; fen: string;
@@ -11,9 +11,11 @@ export interface Props {
const PIECE_SCALE = 0.55; const PIECE_SCALE = 0.55;
export default function CapturedPieces({ fen, color }: Props) { export default function CapturedPieces({ fen, color }: Props) {
const cssProps = useMemo(() => { const piecesComponents = useMemo(() => {
const capturedPieces = getCapturedPieces(fen, color); const capturedPieces = getCapturedPieces(fen, color);
return getCapturedPiecesCSSProps(capturedPieces, color); return capturedPieces.map(({ piece, count }) =>
getCapturedPiecesComponents(piece, count)
);
}, [fen, color]); }, [fen, color]);
const materialDiff = useMemo(() => { const materialDiff = useMemo(() => {
@@ -22,19 +24,16 @@ export default function CapturedPieces({ fen, color }: Props) {
}, [fen, color]); }, [fen, color]);
return ( return (
<Grid container alignItems="end" columnGap={0.5} size="auto"> <Grid
{cssProps.map((cssProp, i) => ( container
<span alignItems="end"
key={i} spacing={0.7}
style={{ size="auto"
...cssProp, marginLeft={`-${0.3 * PIECE_SCALE}rem`}
backgroundSize: `${34.2 * PIECE_SCALE}rem ${30.6 * PIECE_SCALE}rem`, >
backgroundImage: "url(/captured-pieces.png)", <Stack direction="row" spacing={0.1}>
backgroundRepeat: "no-repeat", {piecesComponents}
display: "inline-block", </Stack>
}}
/>
))}
{materialDiff > 0 && ( {materialDiff > 0 && (
<Typography <Typography
@@ -49,115 +48,28 @@ export default function CapturedPieces({ fen, color }: Props) {
); );
} }
const getCapturedPiecesCSSProps = ( const getCapturedPiecesComponents = (
capturedPieces: Record<string, number | undefined>, pieceSymbol: string,
color: Color pieceCount: number | undefined
): CSSProperties[] => { ): ReactElement | null => {
const cssProps: CSSProperties[] = []; if (!pieceCount) return null;
if (color === Color.Black) { return (
if (capturedPieces.P) { <Stack
cssProps.push({ direction="row"
backgroundPositionX: `-${18 * PIECE_SCALE}rem`, key={pieceSymbol}
backgroundPositionY: `${ spacing={`-${1.2 * PIECE_SCALE}rem`}
-20.1 * PIECE_SCALE + capturedPieces.P * 2.5 * PIECE_SCALE >
}rem`, {new Array(pieceCount).fill(
width: `${0.6 * PIECE_SCALE + capturedPieces.P * 0.7 * PIECE_SCALE}rem`, <Box
height: `${1.7 * PIECE_SCALE}rem`, width={`${2 * PIECE_SCALE}rem`}
}); height={`${2 * PIECE_SCALE}rem`}
} sx={{
backgroundImage: `url(/piece/cardinal/${pieceSymbol}.svg)`,
if (capturedPieces.B) { backgroundRepeat: "no-repeat",
cssProps.push({ }}
backgroundPosition: `-${24.7 * PIECE_SCALE}rem ${ />
-5.1 * PIECE_SCALE + capturedPieces.B * 2.6 * PIECE_SCALE )}
}rem`, </Stack>
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;
}; };

View File

@@ -3,6 +3,7 @@ import { Game, Player } from "@/types/game";
import { Chess, PieceSymbol, Square } from "chess.js"; import { Chess, PieceSymbol, Square } from "chess.js";
import { getPositionWinPercentage } from "./engine/helpers/winPercentage"; import { getPositionWinPercentage } from "./engine/helpers/winPercentage";
import { Color } from "@/types/enums"; import { Color } from "@/types/enums";
import { Piece } from "react-chessboard/dist/chessboard/types";
export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => { export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => {
const history = game.history({ verbose: true }); const history = game.history({ verbose: true });
@@ -288,29 +289,56 @@ export const isCheck = (fen: string): boolean => {
export const getCapturedPieces = ( export const getCapturedPieces = (
fen: string, fen: string,
color: Color color: Color
): Record<string, number | undefined> => { ): {
const capturedPieces: Record<string, number | undefined> = {}; piece: string;
if (color === Color.White) { count: number;
capturedPieces.p = 8; }[] => {
capturedPieces.r = 2; const capturedPieces =
capturedPieces.n = 2; color === Color.White
capturedPieces.b = 2; ? [
capturedPieces.q = 1; { piece: "p", count: 8 },
} else { { piece: "b", count: 2 },
capturedPieces.P = 8; { piece: "n", count: 2 },
capturedPieces.R = 2; { piece: "r", count: 2 },
capturedPieces.N = 2; { piece: "q", count: 1 },
capturedPieces.B = 2; ]
capturedPieces.Q = 1; : [
} { piece: "P", count: 8 },
{ piece: "B", count: 2 },
{ piece: "N", count: 2 },
{ piece: "R", count: 2 },
{ piece: "Q", count: 1 },
];
const fenPiecePlacement = fen.split(" ")[0]; 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; return capturedPieces.map(({ piece, count }) => {
const piecesLeftCount = fenPiecePlacement.match(
new RegExp(piece, "g")
)?.length;
const newPiece = pieceFenToSymbol[piece] ?? piece;
if (!count) return { piece: newPiece, count };
return {
piece: newPiece,
count: count - (piecesLeftCount ?? 0),
};
});
};
const pieceFenToSymbol: Record<string, Piece | undefined> = {
p: "bP",
b: "bB",
n: "bN",
r: "bR",
q: "bQ",
k: "bK",
P: "wP",
B: "wB",
N: "wN",
R: "wR",
Q: "wQ",
K: "wK",
}; };
export const getLineEvalLabel = ( export const getLineEvalLabel = (

View File

@@ -16,7 +16,7 @@ export default function Layout({ children }: PropsWithChildren) {
main: red[400], main: red[400],
}, },
primary: { primary: {
main: "#5d9948", main: "#5a9943",
}, },
secondary: { secondary: {
main: isDarkMode ? "#424242" : "#ffffff", main: isDarkMode ? "#424242" : "#ffffff",