refacto : board player header

This commit is contained in:
GuillaumeSD
2025-05-08 00:43:55 +02:00
parent 8167b9b621
commit 8c934ab3b0
15 changed files with 219 additions and 154 deletions

View File

@@ -9,6 +9,7 @@
"eslint:recommended", "eslint:recommended",
"plugin:import/recommended", "plugin:import/recommended",
"plugin:import/typescript", "plugin:import/typescript",
"plugin:@tanstack/eslint-plugin-query/recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended", "plugin:prettier/recommended",
"plugin:deprecation/recommended", "plugin:deprecation/recommended",
@@ -21,7 +22,7 @@
"sourceType": "module" "sourceType": "module"
}, },
"ignorePatterns": [".out/*"], "ignorePatterns": [".out/*"],
"plugins": ["@typescript-eslint", "import", "prettier"], "plugins": ["@typescript-eslint", "import", "prettier", "@tanstack/query"],
"rules": { "rules": {
"quotes": ["error", "double", { "avoidEscape": true }], "quotes": ["error", "double", { "avoidEscape": true }],
"prettier/prettier": ["error", {"endOfLine": "auto"}], "prettier/prettier": ["error", {"endOfLine": "auto"}],

45
package-lock.json generated
View File

@@ -17,6 +17,7 @@
"@mui/material": "^6.3.0", "@mui/material": "^6.3.0",
"@mui/x-data-grid": "^7.23.5", "@mui/x-data-grid": "^7.23.5",
"@sentry/nextjs": "^8.47.0", "@sentry/nextjs": "^8.47.0",
"@tanstack/react-query": "^5.75.5",
"chess.js": "^1.2.0", "chess.js": "^1.2.0",
"firebase": "^11.1.0", "firebase": "^11.1.0",
"idb": "^8.0.1", "idb": "^8.0.1",
@@ -28,6 +29,7 @@
"recharts": "^2.15.0" "recharts": "^2.15.0"
}, },
"devDependencies": { "devDependencies": {
"@tanstack/eslint-plugin-query": "^5.74.7",
"@types/node": "^22.10.2", "@types/node": "^22.10.2",
"@types/react": "18.2.11", "@types/react": "18.2.11",
"@types/react-dom": "^18.3.5", "@types/react-dom": "^18.3.5",
@@ -3644,6 +3646,49 @@
"tslib": "^2.8.0" "tslib": "^2.8.0"
} }
}, },
"node_modules/@tanstack/eslint-plugin-query": {
"version": "5.74.7",
"resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.74.7.tgz",
"integrity": "sha512-EeHuaaYiCOD+XOGyB7LMNEx9OEByAa5lkgP+S3ZggjKJpmIO6iRWeoIYYDKo2F8uc3qXcVhTfC7pn7NddQiNtA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/utils": "^8.18.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0"
}
},
"node_modules/@tanstack/query-core": {
"version": "5.75.5",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.75.5.tgz",
"integrity": "sha512-kPDOxtoMn2Ycycb76Givx2fi+2pzo98F9ifHL/NFiahEDpDwSVW6o12PRuQ0lQnBOunhRG5etatAhQij91M3MQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.75.5",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.75.5.tgz",
"integrity": "sha512-QrLCJe40BgBVlWdAdf2ZEVJ0cISOuEy/HKupId1aTKU6gPJZVhSvZpH+Si7csRflCJphzlQ77Yx6gUxGW9o0XQ==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.75.5"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18 || ^19"
}
},
"node_modules/@types/connect": { "node_modules/@types/connect": {
"version": "3.4.36", "version": "3.4.36",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz",

View File

@@ -19,6 +19,7 @@
"@mui/material": "^6.3.0", "@mui/material": "^6.3.0",
"@mui/x-data-grid": "^7.23.5", "@mui/x-data-grid": "^7.23.5",
"@sentry/nextjs": "^8.47.0", "@sentry/nextjs": "^8.47.0",
"@tanstack/react-query": "^5.75.5",
"chess.js": "^1.2.0", "chess.js": "^1.2.0",
"firebase": "^11.1.0", "firebase": "^11.1.0",
"idb": "^8.0.1", "idb": "^8.0.1",
@@ -30,6 +31,7 @@
"recharts": "^2.15.0" "recharts": "^2.15.0"
}, },
"devDependencies": { "devDependencies": {
"@tanstack/eslint-plugin-query": "^5.74.7",
"@types/node": "^22.10.2", "@types/node": "^22.10.2",
"@types/react": "18.2.11", "@types/react": "18.2.11",
"@types/react-dom": "^18.3.5", "@types/react-dom": "^18.3.5",

View File

@@ -1,4 +1,4 @@
import { Grid2 as Grid, Typography } from "@mui/material"; import { Grid2 as Grid } from "@mui/material";
import { Chessboard } from "react-chessboard"; import { Chessboard } from "react-chessboard";
import { PrimitiveAtom, atom, useAtomValue, useSetAtom } from "jotai"; import { PrimitiveAtom, atom, useAtomValue, useSetAtom } from "jotai";
import { import {
@@ -14,19 +14,17 @@ import { Chess } from "chess.js";
import { getSquareRenderer } from "./squareRenderer"; import { getSquareRenderer } from "./squareRenderer";
import { CurrentPosition } from "@/types/eval"; import { CurrentPosition } from "@/types/eval";
import EvaluationBar from "./evaluationBar"; import EvaluationBar from "./evaluationBar";
import CapturedPieces from "./capturedPieces";
import { moveClassificationColors } from "@/lib/chess"; import { moveClassificationColors } from "@/lib/chess";
import Avatar from "@mui/material/Avatar"; import { Player } from "@/types/game";
import PlayerHeader from "./playerHeader";
export interface Props { export interface Props {
id: string; id: string;
canPlay?: Color | boolean; canPlay?: Color | boolean;
gameAtom: PrimitiveAtom<Chess>; gameAtom: PrimitiveAtom<Chess>;
boardSize?: number; boardSize?: number;
whitePlayer?: string; whitePlayer: Player;
blackPlayer?: string; blackPlayer: Player;
whiteAvatar?: string;
blackAvatar?: string;
boardOrientation?: Color; boardOrientation?: Color;
currentPositionAtom?: PrimitiveAtom<CurrentPosition>; currentPositionAtom?: PrimitiveAtom<CurrentPosition>;
showBestMoveArrow?: boolean; showBestMoveArrow?: boolean;
@@ -41,8 +39,6 @@ export default function Board({
boardSize, boardSize,
whitePlayer, whitePlayer,
blackPlayer, blackPlayer,
whiteAvatar,
blackAvatar,
boardOrientation = Color.White, boardOrientation = Color.White,
currentPositionAtom = atom({}), currentPositionAtom = atom({}),
showBestMoveArrow = false, showBestMoveArrow = false,
@@ -247,30 +243,11 @@ export default function Board({
paddingLeft={showEvaluationBar ? 2 : 0} paddingLeft={showEvaluationBar ? 2 : 0}
size="grow" size="grow"
> >
<Grid <PlayerHeader
container
justifyContent="center"
alignItems="center"
columnGap={2}
size={12}
>
{/* Player avatar, only render if URL is available */}
{(boardOrientation === Color.White ? blackAvatar : whiteAvatar) && (
<Avatar
src={boardOrientation === Color.White ? blackAvatar : whiteAvatar}
variant="circular"
sx={{ width: 24, height: 24 }}
/>
) }
<Typography>
{boardOrientation === Color.White ? blackPlayer : whitePlayer}
</Typography>
<CapturedPieces
fen={gameFen}
color={boardOrientation === Color.White ? Color.Black : Color.White} color={boardOrientation === Color.White ? Color.Black : Color.White}
fen={gameFen}
player={boardOrientation === Color.White ? blackPlayer : whitePlayer}
/> />
</Grid>
<Grid <Grid
container container
@@ -304,27 +281,11 @@ export default function Board({
/> />
</Grid> </Grid>
<Grid <PlayerHeader
container color={boardOrientation}
justifyContent="center" fen={gameFen}
alignItems="center" player={boardOrientation === Color.White ? whitePlayer : blackPlayer}
columnGap={2}
size={12}
>
{/* Player avatar, only render if URL is available */}
{ (boardOrientation === Color.White ? whiteAvatar : blackAvatar) && (
<Avatar
src={boardOrientation === Color.White ? whiteAvatar : blackAvatar}
variant="circular"
sx={{ width: 24, height: 24 }}
/> />
) }
<Typography>
{boardOrientation === Color.White ? whitePlayer : blackPlayer}
</Typography>
<CapturedPieces fen={gameFen} color={boardOrientation} />
</Grid>
</Grid> </Grid>
</Grid> </Grid>
); );

View File

@@ -0,0 +1,35 @@
import { Color } from "@/types/enums";
import { Player } from "@/types/game";
import { Avatar, Grid2 as Grid, Typography } from "@mui/material";
import CapturedPieces from "./capturedPieces";
export interface Props {
player: Player;
color: Color;
fen: string;
}
export default function PlayerHeader({ color, player, fen }: Props) {
return (
<Grid
container
justifyContent="center"
alignItems="center"
columnGap={2}
size={12}
>
{player.avatarUrl && (
<Avatar
src={player.avatarUrl}
variant="circular"
sx={{ width: 24, height: 24 }}
/>
)}
<Typography>
{player.rating ? `${player.name} (${player.rating})` : player.name}
</Typography>
<CapturedPieces fen={fen} color={color} />
</Grid>
);
}

View File

@@ -1,9 +1,13 @@
import { Chess } from "chess.js"; import { Chess } from "chess.js";
import { PrimitiveAtom, useAtomValue } from "jotai"; import { PrimitiveAtom, useAtomValue } from "jotai";
import { useGameDatabase } from "./useGameDatabase"; import { useGameDatabase } from "./useGameDatabase";
import { useState, useEffect } from "react"; import { useQuery } from "@tanstack/react-query";
import { getChessComUserAvatar } from "@/lib/chessCom";
import { Player } from "@/types/game";
export const usePlayersNames = (gameAtom: PrimitiveAtom<Chess>) => { export const usePlayersData = (
gameAtom: PrimitiveAtom<Chess>
): { white: Player; black: Player } => {
const game = useAtomValue(gameAtom); const game = useAtomValue(gameAtom);
const { gameFromUrl } = useGameDatabase(); const { gameFromUrl } = useGameDatabase();
const headers = game.getHeaders(); const headers = game.getHeaders();
@@ -16,57 +20,49 @@ export const usePlayersNames = (gameAtom: PrimitiveAtom<Chess>) => {
const whiteName = gameFromUrl?.white?.name || headersWhiteName || "White"; const whiteName = gameFromUrl?.white?.name || headersWhiteName || "White";
const blackName = gameFromUrl?.black?.name || headersBlackName || "Black"; const blackName = gameFromUrl?.black?.name || headersBlackName || "Black";
const whiteElo = gameFromUrl?.white?.rating || headers.WhiteElo || undefined; const whiteElo =
const blackElo = gameFromUrl?.black?.rating || headers.BlackElo || undefined; gameFromUrl?.white?.rating || Number(headers.WhiteElo) || undefined;
const blackElo =
gameFromUrl?.black?.rating || Number(headers.BlackElo) || undefined;
// Determine if this game came from Chess.com (via PGN header or URL) const siteHeader = gameFromUrl?.site || headers.Site || "unknown";
const siteHeader = gameFromUrl?.site || headers.Site || "";
const isChessCom = siteHeader.toLowerCase().includes("chess.com"); const isChessCom = siteHeader.toLowerCase().includes("chess.com");
// Avatars fetched only for Chess.com games const whiteAvatarUrl = usePlayerAvatarUrl(
const [whiteAvatar, setWhiteAvatar] = useState<string | undefined>(undefined); whiteName,
const [blackAvatar, setBlackAvatar] = useState<string | undefined>(undefined); isChessCom && !!whiteName && whiteName !== "White"
);
// Fetch white avatar const blackAvatarUrl = usePlayerAvatarUrl(
useEffect(() => { blackName,
if (isChessCom && whiteName && whiteName !== "White") { isChessCom && !!blackName && blackName !== "Black"
// Normalize and encode username );
const trimmedWhiteName = whiteName.trim().toLowerCase();
const usernameParam = encodeURIComponent(trimmedWhiteName);
fetch(`https://api.chess.com/pub/player/${usernameParam}`)
.then((res) => res.json())
.then((data) => setWhiteAvatar(data.avatar || undefined))
.catch(() => {
setWhiteAvatar(undefined);
});
} else {
setWhiteAvatar(undefined);
}
}, [isChessCom, whiteName]);
// Fetch black avatar
useEffect(() => {
if (isChessCom && blackName && blackName !== "Black") {
// Normalize and encode username
const trimmedBlackName = blackName.trim().toLowerCase();
const usernameParamBlack = encodeURIComponent(trimmedBlackName);
fetch(`https://api.chess.com/pub/player/${usernameParamBlack}`)
.then((res) => res.json())
.then((data) => setBlackAvatar(data.avatar || undefined))
.catch(() => {
setBlackAvatar(undefined);
});
} else {
setBlackAvatar(undefined);
}
}, [isChessCom, blackName]);
return { return {
whiteName, white: {
blackName, name: whiteName,
whiteElo, rating: whiteElo,
blackElo, avatarUrl: whiteAvatarUrl ?? undefined,
whiteAvatar, },
blackAvatar, black: {
name: blackName,
rating: blackElo,
avatarUrl: blackAvatarUrl ?? undefined,
},
}; };
}; };
const usePlayerAvatarUrl = (
playerName: string,
enabled: boolean
): string | null | undefined => {
const { data: avatarUrl } = useQuery({
queryKey: ["CCAvatar", playerName],
enabled,
queryFn: () => getChessComUserAvatar(playerName),
staleTime: 1000 * 60 * 60, // 1 hour
gcTime: 1000 * 60 * 60 * 24, // 1 day
});
return avatarUrl;
};

View File

@@ -34,11 +34,11 @@ export const formatGameToDatabase = (game: Chess): Omit<Game, "id"> => {
date: headers.Date, date: headers.Date,
round: headers.Round ?? "?", round: headers.Round ?? "?",
white: { white: {
name: headers.White, name: headers.White || "White",
rating: headers.WhiteElo ? Number(headers.WhiteElo) : undefined, rating: headers.WhiteElo ? Number(headers.WhiteElo) : undefined,
}, },
black: { black: {
name: headers.Black, name: headers.Black || "Black",
rating: headers.BlackElo ? Number(headers.BlackElo) : undefined, rating: headers.BlackElo ? Number(headers.BlackElo) : undefined,
}, },
result: headers.Result, result: headers.Result,

View File

@@ -39,3 +39,15 @@ export const getChessComUserRecentGames = async (
return gamesToReturn; return gamesToReturn;
}; };
export const getChessComUserAvatar = async (
username: string
): Promise<string | null> => {
const usernameParam = encodeURIComponent(username.trim().toLowerCase());
const res = await fetch(`https://api.chess.com/pub/player/${usernameParam}`);
const data = await res.json();
const avatarUrl = data?.avatar;
return typeof avatarUrl === "string" ? avatarUrl : null;
};

View File

@@ -4,11 +4,16 @@ import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css"; import "@fontsource/roboto/700.css";
import { AppProps } from "next/app"; import { AppProps } from "next/app";
import Layout from "@/sections/layout"; import Layout from "@/sections/layout";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
export default function MyApp({ Component, pageProps }: AppProps) { export default function MyApp({ Component, pageProps }: AppProps) {
return ( return (
<QueryClientProvider client={queryClient}>
<Layout> <Layout>
<Component {...pageProps} /> <Component {...pageProps} />
</Layout> </Layout>
</QueryClientProvider>
); );
} }

View File

@@ -11,14 +11,13 @@ import { useMemo } from "react";
import { useScreenSize } from "@/hooks/useScreenSize"; import { useScreenSize } from "@/hooks/useScreenSize";
import { Color } from "@/types/enums"; import { Color } from "@/types/enums";
import Board from "@/components/board"; import Board from "@/components/board";
import { usePlayersNames } from "@/hooks/usePlayerNames"; import { usePlayersData } from "@/hooks/usePlayerNames";
export default function BoardContainer() { 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, whiteElo, blackName, blackElo, whiteAvatar, blackAvatar } = const { white, black } = usePlayersData(gameAtom);
usePlayersNames(gameAtom);
const boardSize = useMemo(() => { const boardSize = useMemo(() => {
const width = screenSize.width; const width = screenSize.width;
@@ -38,10 +37,8 @@ export default function BoardContainer() {
boardSize={boardSize} boardSize={boardSize}
canPlay={true} canPlay={true}
gameAtom={boardAtom} gameAtom={boardAtom}
whitePlayer={whiteElo ? `${whiteName} (${whiteElo})` : whiteName} whitePlayer={white}
blackPlayer={blackElo ? `${blackName} (${blackElo})` : blackName} blackPlayer={black}
whiteAvatar={whiteAvatar}
blackAvatar={blackAvatar}
boardOrientation={boardOrientation ? Color.White : Color.Black} boardOrientation={boardOrientation ? Color.White : Color.Black}
currentPositionAtom={currentPositionAtom} currentPositionAtom={currentPositionAtom}
showBestMoveArrow={showBestMoveArrow} showBestMoveArrow={showBestMoveArrow}

View File

@@ -1,4 +1,4 @@
import { usePlayersNames } from "@/hooks/usePlayerNames"; import { usePlayersData } from "@/hooks/usePlayerNames";
import { Grid2 as Grid, Typography } from "@mui/material"; import { Grid2 as 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";
@@ -6,7 +6,7 @@ import ClassificationRow from "./classificationRow";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
export default function MovesClassificationsRecap() { export default function MovesClassificationsRecap() {
const { whiteName, blackName } = usePlayersNames(gameAtom); const { white, black } = usePlayersData(gameAtom);
const gameEval = useAtomValue(gameEvalAtom); const gameEval = useAtomValue(gameEvalAtom);
if (!gameEval?.positions.length) return null; if (!gameEval?.positions.length) return null;
@@ -29,13 +29,13 @@ export default function MovesClassificationsRecap() {
size={12} size={12}
> >
<Typography width="12rem" align="center" noWrap fontSize="0.9rem"> <Typography width="12rem" align="center" noWrap fontSize="0.9rem">
{whiteName} {white.name}
</Typography> </Typography>
<Typography width="7rem" /> <Typography width="7rem" />
<Typography width="12rem" align="center" noWrap fontSize="0.9rem"> <Typography width="12rem" align="center" noWrap fontSize="0.9rem">
{blackName} {black.name}
</Typography> </Typography>
</Grid> </Grid>

View File

@@ -92,7 +92,7 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
value={engine} value={engine}
disabled={!isEngineSupported(engine)} disabled={!isEngineSupported(engine)}
> >
{engineLabel[engine]} {engineLabel[engine].full}
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
@@ -129,12 +129,34 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
); );
} }
const engineLabel: Record<EngineName, string> = { export const engineLabel: Record<EngineName, { small: string; full: string }> =
[EngineName.Stockfish17]: "Stockfish 17 (75MB)", {
[EngineName.Stockfish17Lite]: "Stockfish 17 Lite (6MB)", [EngineName.Stockfish17]: {
[EngineName.Stockfish16_1]: "Stockfish 16.1 (64MB)", full: "Stockfish 17 (75MB)",
[EngineName.Stockfish16_1Lite]: "Stockfish 16.1 Lite (6MB)", small: "Stockfish 17",
[EngineName.Stockfish16NNUE]: "Stockfish 16 (40MB)", },
[EngineName.Stockfish16]: "Stockfish 16 Lite (HCE)", [EngineName.Stockfish17Lite]: {
[EngineName.Stockfish11]: "Stockfish 11", full: "Stockfish 17 Lite (6MB)",
small: "Stockfish 17 Lite",
},
[EngineName.Stockfish16_1]: {
full: "Stockfish 16.1 (64MB)",
small: "Stockfish 16.1",
},
[EngineName.Stockfish16_1Lite]: {
full: "Stockfish 16.1 Lite (6MB)",
small: "Stockfish 16.1 Lite",
},
[EngineName.Stockfish16NNUE]: {
full: "Stockfish 16 (40MB)",
small: "Stockfish 16",
},
[EngineName.Stockfish16]: {
full: "Stockfish 16 Lite (HCE)",
small: "Stockfish 16 Lite",
},
[EngineName.Stockfish11]: {
full: "Stockfish 11 (HCE)",
small: "Stockfish 11",
},
}; };

View File

@@ -10,17 +10,18 @@ import {
import { useChessActions } from "@/hooks/useChessActions"; import { useChessActions } from "@/hooks/useChessActions";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { useScreenSize } from "@/hooks/useScreenSize"; import { useScreenSize } from "@/hooks/useScreenSize";
import { Color } from "@/types/enums";
import { useEngine } from "@/hooks/useEngine"; import { useEngine } from "@/hooks/useEngine";
import { uciMoveParams } from "@/lib/chess"; import { uciMoveParams } from "@/lib/chess";
import Board from "@/components/board"; import Board from "@/components/board";
import { useGameData } from "@/hooks/useGameData"; import { useGameData } from "@/hooks/useGameData";
import { usePlayersData } from "@/hooks/usePlayerNames";
export default function BoardContainer() { export default function BoardContainer() {
const screenSize = useScreenSize(); const screenSize = useScreenSize();
const engineName = useAtomValue(enginePlayNameAtom); const engineName = useAtomValue(enginePlayNameAtom);
const engine = useEngine(engineName, 1); const engine = useEngine(engineName, 1);
const game = useAtomValue(gameAtom); const game = useAtomValue(gameAtom);
const { white, black } = usePlayersData(gameAtom);
const playerColor = useAtomValue(playerColorAtom); const playerColor = useAtomValue(playerColorAtom);
const { makeMove: makeGameMove } = useChessActions(gameAtom); const { makeMove: makeGameMove } = useChessActions(gameAtom);
const engineSkillLevel = useAtomValue(engineSkillLevelAtom); const engineSkillLevel = useAtomValue(engineSkillLevelAtom);
@@ -72,16 +73,8 @@ export default function BoardContainer() {
canPlay={isGameInProgress ? playerColor : false} canPlay={isGameInProgress ? playerColor : false}
gameAtom={gameAtom} gameAtom={gameAtom}
boardSize={boardSize} boardSize={boardSize}
whitePlayer={ whitePlayer={white}
playerColor === Color.White blackPlayer={black}
? "You 🧠"
: `Stockfish level ${engineSkillLevel} 🤖`
}
blackPlayer={
playerColor === Color.Black
? "You 🧠"
: `Stockfish level ${engineSkillLevel} 🤖`
}
boardOrientation={playerColor} boardOrientation={playerColor}
currentPositionAtom={gameDataAtom} currentPositionAtom={gameDataAtom}
/> />

View File

@@ -32,6 +32,7 @@ import { logAnalyticsEvent } from "@/lib/firebase";
import { useEffect } from "react"; import { useEffect } from "react";
import { isEngineSupported } from "@/lib/engine/shared"; import { isEngineSupported } from "@/lib/engine/shared";
import { Stockfish16_1 } from "@/lib/engine/stockfish16_1"; import { Stockfish16_1 } from "@/lib/engine/stockfish16_1";
import { engineLabel } from "@/sections/engineSettings/engineSettingsDialog";
interface Props { interface Props {
open: boolean; open: boolean;
@@ -55,9 +56,13 @@ export default function GameSettingsDialog({ open, onClose }: Props) {
onClose(); onClose();
resetGame({ resetGame({
whiteName: whiteName:
playerColor === Color.White ? "You" : `Stockfish level ${skillLevel}`, playerColor === Color.White
? "You"
: `${engineLabel[engineName].small} level ${skillLevel}`,
blackName: blackName:
playerColor === Color.Black ? "You" : `Stockfish level ${skillLevel}`, playerColor === Color.Black
? "You"
: `${engineLabel[engineName].small} level ${skillLevel}`,
}); });
playGameStartSound(); playGameStartSound();
setIsGameInProgress(true); setIsGameInProgress(true);
@@ -117,7 +122,7 @@ export default function GameSettingsDialog({ open, onClose }: Props) {
value={engine} value={engine}
disabled={!isEngineSupported(engine)} disabled={!isEngineSupported(engine)}
> >
{engineLabel[engine]} {engineLabel[engine].full}
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
@@ -166,13 +171,3 @@ export default function GameSettingsDialog({ open, onClose }: Props) {
</Dialog> </Dialog>
); );
} }
const engineLabel: Record<EngineName, string> = {
[EngineName.Stockfish17]: "Stockfish 17 (75MB)",
[EngineName.Stockfish17Lite]: "Stockfish 17 Lite (6MB)",
[EngineName.Stockfish16_1]: "Stockfish 16.1 (64MB)",
[EngineName.Stockfish16_1Lite]: "Stockfish 16.1 Lite (6MB)",
[EngineName.Stockfish16NNUE]: "Stockfish 16 (40MB)",
[EngineName.Stockfish16]: "Stockfish 16 Lite (HCE)",
[EngineName.Stockfish11]: "Stockfish 11",
};

View File

@@ -16,6 +16,7 @@ export interface Game {
} }
export interface Player { export interface Player {
name?: string; name: string;
rating?: number; rating?: number;
avatarUrl?: string;
} }