feat : add click on engine lines

This commit is contained in:
GuillaumeSD
2025-05-11 18:21:45 +02:00
parent 74a2adbb7d
commit 6535fce2f4
22 changed files with 285 additions and 156 deletions

View File

@@ -1,56 +0,0 @@
import { Piece } from "react-chessboard/dist/chessboard/types";
export const PIECE_CODES = [
"wP",
"wB",
"wN",
"wR",
"wQ",
"wK",
"bP",
"bB",
"bN",
"bR",
"bQ",
"bK",
] as const satisfies Piece[];
export const PIECE_SETS = [
"alpha",
"anarcandy",
"caliente",
"california",
"cardinal",
"cburnett",
"celtic",
"chess7",
"chessnut",
"companion",
"cooke",
"dubrovny",
"fantasy",
"firi",
"fresca",
"gioco",
"governor",
"horsey",
"icpieces",
"kiwen-suwi",
"kosal",
"leipzig",
"letter",
"maestro",
"merida",
"monarchy",
"mpchess",
"pirouetti",
"pixel",
"reillycraig",
"rhosgfx",
"riohacha",
"shapes",
"spatial",
"staunty",
"tatiana",
"xkcd",
] as const satisfies string[];

View File

@@ -5,6 +5,7 @@ import {
Arrow, Arrow,
CustomPieces, CustomPieces,
CustomSquareRenderer, CustomSquareRenderer,
Piece,
PromotionPieceOption, PromotionPieceOption,
Square, Square,
} from "react-chessboard/dist/chessboard/types"; } from "react-chessboard/dist/chessboard/types";
@@ -15,13 +16,12 @@ 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 { moveClassificationColors } from "@/lib/chess"; import { CLASSIFICATION_COLORS } from "@/constants";
import { Player } from "@/types/game"; import { Player } from "@/types/game";
import PlayerHeader from "./playerHeader"; import PlayerHeader from "./playerHeader";
import Image from "next/image"; import Image from "next/image";
import { boardHueAtom, pieceSetAtom } from "./states"; import { boardHueAtom, pieceSetAtom } from "./states";
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
import { PIECE_CODES } from "./constants";
export interface Props { export interface Props {
id: string; id: string;
@@ -52,7 +52,7 @@ export default function Board({
}: Props) { }: Props) {
const boardRef = useRef<HTMLDivElement>(null); const boardRef = useRef<HTMLDivElement>(null);
const game = useAtomValue(gameAtom); const game = useAtomValue(gameAtom);
const { makeMove: makeGameMove } = useChessActions(gameAtom); const { playMove } = useChessActions(gameAtom);
const clickedSquaresAtom = useMemo(() => atom<Square[]>([]), []); const clickedSquaresAtom = useMemo(() => atom<Square[]>([]), []);
const setClickedSquares = useSetAtom(clickedSquaresAtom); const setClickedSquares = useSetAtom(clickedSquaresAtom);
const playableSquaresAtom = useMemo(() => atom<Square[]>([]), []); const playableSquaresAtom = useMemo(() => atom<Square[]>([]), []);
@@ -86,7 +86,7 @@ export default function Board({
): boolean => { ): boolean => {
if (!isPiecePlayable({ piece })) return false; if (!isPiecePlayable({ piece })) return false;
const result = makeGameMove({ const result = playMove({
from: source, from: source,
to: target, to: target,
promotion: piece[1]?.toLowerCase() ?? "q", promotion: piece[1]?.toLowerCase() ?? "q",
@@ -135,7 +135,7 @@ export default function Board({
return; return;
} }
const result = makeGameMove({ const result = playMove({
from: moveClickFrom, from: moveClickFrom,
to: square, to: square,
}); });
@@ -168,7 +168,7 @@ export default function Board({
const promotionPiece = piece[1]?.toLowerCase() ?? "q"; const promotionPiece = piece[1]?.toLowerCase() ?? "q";
if (moveClickFrom && moveClickTo) { if (moveClickFrom && moveClickTo) {
const result = makeGameMove({ const result = playMove({
from: moveClickFrom, from: moveClickFrom,
to: moveClickTo, to: moveClickTo,
promotion: promotionPiece, promotion: promotionPiece,
@@ -178,7 +178,7 @@ export default function Board({
} }
if (from && to) { if (from && to) {
const result = makeGameMove({ const result = playMove({
from, from,
to, to,
promotion: promotionPiece, promotion: promotionPiece,
@@ -206,7 +206,7 @@ export default function Board({
const bestMoveArrow = [ const bestMoveArrow = [
bestMove.slice(0, 2), bestMove.slice(0, 2),
bestMove.slice(2, 4), bestMove.slice(2, 4),
tinycolor(moveClassificationColors[MoveClassification.Best]) tinycolor(CLASSIFICATION_COLORS[MoveClassification.Best])
.spin(-boardHue) .spin(-boardHue)
.toHexString(), .toHexString(),
] as Arrow; ] as Arrow;
@@ -325,3 +325,18 @@ export default function Board({
</Grid> </Grid>
); );
} }
export const PIECE_CODES = [
"wP",
"wB",
"wN",
"wR",
"wQ",
"wK",
"bP",
"bB",
"bN",
"bR",
"bQ",
"bK",
] as const satisfies Piece[];

View File

@@ -7,7 +7,7 @@ import {
CustomSquareProps, CustomSquareProps,
Square, Square,
} from "react-chessboard/dist/chessboard/types"; } from "react-chessboard/dist/chessboard/types";
import { moveClassificationColors } from "@/lib/chess"; import { CLASSIFICATION_COLORS } from "@/constants";
import { boardHueAtom } from "./states"; import { boardHueAtom } from "./states";
export interface Props { export interface Props {
@@ -110,7 +110,7 @@ const previousMoveSquareStyle = (
width: "100%", width: "100%",
height: "100%", height: "100%",
backgroundColor: moveClassification backgroundColor: moveClassification
? moveClassificationColors[moveClassification] ? CLASSIFICATION_COLORS[moveClassification]
: "#fad541", : "#fad541",
opacity: 0.5, opacity: 0.5,
}); });

View File

@@ -1,5 +1,5 @@
import { PIECE_SETS } from "@/constants";
import { atomWithStorage } from "jotai/utils"; import { atomWithStorage } from "jotai/utils";
import { PIECE_SETS } from "./constants";
export const pieceSetAtom = atomWithStorage<(typeof PIECE_SETS)[number]>( export const pieceSetAtom = atomWithStorage<(typeof PIECE_SETS)[number]>(
"pieceSet", "pieceSet",

91
src/constants.ts Normal file
View File

@@ -0,0 +1,91 @@
import { EngineName, MoveClassification } from "./types/enums";
export const CLASSIFICATION_COLORS: Record<MoveClassification, string> = {
[MoveClassification.Book]: "#d5a47d",
[MoveClassification.Forced]: "#d5a47d",
[MoveClassification.Brilliant]: "#26c2a3",
[MoveClassification.Great]: "#4099ed",
[MoveClassification.Best]: "#3aab18",
[MoveClassification.Excellent]: "#3aab18",
[MoveClassification.Good]: "#81b64c",
[MoveClassification.Inaccuracy]: "#f7c631",
[MoveClassification.Mistake]: "#ffa459",
[MoveClassification.Blunder]: "#fa412d",
};
export const DEFAULT_ENGINE: EngineName = EngineName.Stockfish17Lite;
export const STRONGEST_ENGINE: EngineName = EngineName.Stockfish17;
export const ENGINE_LABELS: Record<
EngineName,
{ small: string; full: string }
> = {
[EngineName.Stockfish17]: {
full: "Stockfish 17 (75MB)",
small: "Stockfish 17",
},
[EngineName.Stockfish17Lite]: {
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",
},
};
export const PIECE_SETS = [
"alpha",
"anarcandy",
"caliente",
"california",
"cardinal",
"cburnett",
"celtic",
"chess7",
"chessnut",
"companion",
"cooke",
"dubrovny",
"fantasy",
"firi",
"fresca",
"gioco",
"governor",
"horsey",
"icpieces",
"kiwen-suwi",
"kosal",
"leipzig",
"letter",
"maestro",
"merida",
"monarchy",
"mpchess",
"pirouetti",
"pixel",
"reillycraig",
"rhosgfx",
"riohacha",
"shapes",
"spatial",
"staunty",
"tatiana",
"xkcd",
] as const satisfies string[];

View File

@@ -67,7 +67,7 @@ export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => {
[copyGame, setGame] [copyGame, setGame]
); );
const makeMove = useCallback( const playMove = useCallback(
(params: { (params: {
from: string; from: string;
to: string; to: string;
@@ -92,6 +92,18 @@ export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => {
[copyGame, setGame] [copyGame, setGame]
); );
const addMoves = useCallback(
(moves: string[]) => {
const newGame = copyGame();
for (const move of moves) {
newGame.move(move);
}
setGame(newGame);
},
[copyGame, setGame]
);
const undoMove = useCallback(() => { const undoMove = useCallback(() => {
const newGame = copyGame(); const newGame = copyGame();
const move = newGame.undo(); const move = newGame.undo();
@@ -127,9 +139,10 @@ export const useChessActions = (chessAtom: PrimitiveAtom<Chess>) => {
return { return {
setPgn, setPgn,
reset, reset,
makeMove, playMove,
undoMove, undoMove,
goToMove, goToMove,
resetToStartingPosition, resetToStartingPosition,
addMoves,
}; };
}; };

View File

@@ -2,7 +2,7 @@ import { EvaluateGameParams, LineEval, PositionEval } from "@/types/eval";
import { Game, Player } from "@/types/game"; 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, MoveClassification } from "@/types/enums"; import { Color } from "@/types/enums";
export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => { export const getEvaluateGameParams = (game: Chess): EvaluateGameParams => {
const history = game.history({ verbose: true }); const history = game.history({ verbose: true });
@@ -327,15 +327,33 @@ export const getLineEvalLabel = (
return "?"; return "?";
}; };
export const moveClassificationColors: Record<MoveClassification, string> = { export const formatUciPv = (fen: string, uciMoves: string[]): string[] => {
[MoveClassification.Book]: "#d5a47d", const castlingRights = fen.split(" ")[2];
[MoveClassification.Forced]: "#d5a47d",
[MoveClassification.Brilliant]: "#26c2a3", let canWhiteCastleKingSide = castlingRights.includes("K");
[MoveClassification.Great]: "#4099ed", let canWhiteCastleQueenSide = castlingRights.includes("Q");
[MoveClassification.Best]: "#3aab18", let canBlackCastleKingSide = castlingRights.includes("k");
[MoveClassification.Excellent]: "#3aab18", let canBlackCastleQueenSide = castlingRights.includes("q");
[MoveClassification.Good]: "#81b64c",
[MoveClassification.Inaccuracy]: "#f7c631", return uciMoves.map((uci) => {
[MoveClassification.Mistake]: "#ffa459", if (uci === "e1h1" && canWhiteCastleKingSide) {
[MoveClassification.Blunder]: "#fa412d", canWhiteCastleKingSide = false;
return "e1g1";
}
if (uci === "e1a1" && canWhiteCastleQueenSide) {
canWhiteCastleQueenSide = false;
return "e1c1";
}
if (uci === "e8h8" && canBlackCastleKingSide) {
canBlackCastleKingSide = false;
return "e8g8";
}
if (uci === "e8a8" && canBlackCastleQueenSide) {
canBlackCastleQueenSide = false;
return "e8c8";
}
return uci;
});
}; };

View File

@@ -1,8 +1,9 @@
import { formatUciPv } from "@/lib/chess";
import { LineEval, PositionEval } from "@/types/eval"; import { LineEval, PositionEval } from "@/types/eval";
export const parseEvaluationResults = ( export const parseEvaluationResults = (
results: string[], results: string[],
whiteToPlay: boolean fen: string
): PositionEval => { ): PositionEval => {
const parsedResults: PositionEval = { const parsedResults: PositionEval = {
lines: [], lines: [],
@@ -18,7 +19,7 @@ export const parseEvaluationResults = (
} }
if (result.startsWith("info")) { if (result.startsWith("info")) {
const pv = getResultPv(result); const pv = getResultPv(result, fen);
const multiPv = getResultProperty(result, "multipv"); const multiPv = getResultProperty(result, "multipv");
const depth = getResultProperty(result, "depth"); const depth = getResultProperty(result, "depth");
if (!pv || !multiPv || !depth) continue; if (!pv || !multiPv || !depth) continue;
@@ -45,6 +46,7 @@ export const parseEvaluationResults = (
parsedResults.lines = Object.values(tempResults).sort(sortLines); parsedResults.lines = Object.values(tempResults).sort(sortLines);
const whiteToPlay = fen.split(" ")[1] === "w";
if (!whiteToPlay) { if (!whiteToPlay) {
parsedResults.lines = parsedResults.lines.map((line) => ({ parsedResults.lines = parsedResults.lines.map((line) => ({
...line, ...line,
@@ -86,7 +88,7 @@ export const getResultProperty = (
return splitResult[propertyIndex + 1]; return splitResult[propertyIndex + 1];
}; };
const getResultPv = (result: string): string[] | undefined => { const getResultPv = (result: string, fen: string): string[] | undefined => {
const splitResult = result.split(" "); const splitResult = result.split(" ");
const pvIndex = splitResult.indexOf("pv"); const pvIndex = splitResult.indexOf("pv");
@@ -94,5 +96,6 @@ const getResultPv = (result: string): string[] | undefined => {
return undefined; return undefined;
} }
return splitResult.slice(pvIndex + 1); const rawPv = splitResult.slice(pvIndex + 1);
return formatUciPv(fen, rawPv);
}; };

View File

@@ -317,9 +317,7 @@ export class UciEngine {
"bestmove" "bestmove"
); );
const whiteToPlay = fen.split(" ")[1] === "w"; return parseEvaluationResults(results, fen);
return parseEvaluationResults(results, whiteToPlay);
} }
public async evaluatePositionWithUpdate({ public async evaluatePositionWithUpdate({
@@ -335,11 +333,9 @@ export class UciEngine {
await this.stopSearch(); await this.stopSearch();
await this.setMultiPv(multiPv); await this.setMultiPv(multiPv);
const whiteToPlay = fen.split(" ")[1] === "w";
const onNewMessage = (messages: string[]) => { const onNewMessage = (messages: string[]) => {
if (!setPartialEval) return; if (!setPartialEval) return;
const parsedResults = parseEvaluationResults(messages, whiteToPlay); const parsedResults = parseEvaluationResults(messages, fen);
setPartialEval(parsedResults); setPartialEval(parsedResults);
}; };
@@ -360,7 +356,7 @@ export class UciEngine {
onNewMessage onNewMessage
); );
return parseEvaluationResults(results, whiteToPlay); return parseEvaluationResults(results, fen);
} }
public async getEngineNextMove( public async getEngineNextMove(

View File

@@ -7,6 +7,7 @@ import {
LichessResponse, LichessResponse,
} from "@/types/lichess"; } from "@/types/lichess";
import { logErrorToSentry } from "./sentry"; import { logErrorToSentry } from "./sentry";
import { formatUciPv } from "./chess";
export const getLichessEval = async ( export const getLichessEval = async (
fen: string, fen: string,
@@ -26,7 +27,7 @@ export const getLichessEval = async (
} }
const lines: LineEval[] = data.pvs.map((pv, index) => ({ const lines: LineEval[] = data.pvs.map((pv, index) => ({
pv: pv.moves.split(" "), pv: formatUciPv(fen, pv.moves.split(" ")),
cp: pv.cp, cp: pv.cp,
mate: pv.mate, mate: pv.mate,
depth: data.depth, depth: data.depth,

View File

@@ -1,15 +1,23 @@
import { LineEval } from "@/types/eval"; import { LineEval } from "@/types/eval";
import { ListItem, Skeleton, Typography } from "@mui/material"; import { Box, ListItem, Skeleton, Typography, useTheme } from "@mui/material";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { boardAtom } from "../../states"; import { boardAtom } from "../../states";
import { getLineEvalLabel, moveLineUciToSan } from "@/lib/chess"; 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",
});
interface Props { interface Props {
line: LineEval; line: LineEval;
} }
export default function LineEvaluation({ line }: Props) { export default function LineEvaluation({ line }: Props) {
const theme = useTheme();
const board = useAtomValue(boardAtom); const board = useAtomValue(boardAtom);
const { addMoves } = useChessActions(boardAtom);
const lineLabel = getLineEvalLabel(line); const lineLabel = getLineEvalLabel(line);
const isBlackCp = const isBlackCp =
@@ -18,6 +26,26 @@ export default function LineEvaluation({ line }: Props) {
const showSkeleton = line.depth < 6; const showSkeleton = line.depth < 6;
const uciToSan = moveLineUciToSan(board.fen());
const initialTurn = board.turn();
const isDarkMode = theme.palette.mode === "dark";
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 moveColor = moveIdx % 2 === 0 ? turn : turn === "w" ? "b" : "w";
const icon = unicodeMap[firstChar][moveColor];
return { icon, text: san.slice(1) };
};
return ( return (
<ListItem disablePadding> <ListItem disablePadding>
<Typography <Typography
@@ -58,9 +86,52 @@ export default function LineEvaluation({ line }: Props) {
{showSkeleton ? ( {showSkeleton ? (
<Skeleton variant="rounded" animation="wave" width="15em" /> <Skeleton variant="rounded" animation="wave" width="15em" />
) : ( ) : (
line.pv.map(moveLineUciToSan(board.fen())).join(", ") line.pv.map((uci, i) => {
const san = uciToSan(uci);
const { icon, text } = formatSan(san, i);
return (
<Box
component="span"
key={i}
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 && (
<Typography
component="span"
fontFamily={myFont.style.fontFamily}
>
{icon}
</Typography>
)}
<Typography component="span">
{text}
{i < line.pv.length - 1 && ","}
</Typography>
</Box>
);
})
)} )}
</Typography> </Typography>
</ListItem> </ListItem>
); );
} }
const unicodeMap: Record<string, Record<"w" | "b", string>> = {
K: { w: "♚", b: "♔" },
Q: { w: "♛", b: "♕" },
R: { w: "♜", b: "♖" },
B: { w: "♝", b: "♗" },
N: { w: "♞", b: "♘" },
};

View File

@@ -6,7 +6,7 @@ import { useMemo } from "react";
import Image from "next/image"; import Image from "next/image";
import { capitalize } from "@/lib/helpers"; import { capitalize } from "@/lib/helpers";
import { useChessActions } from "@/hooks/useChessActions"; import { useChessActions } from "@/hooks/useChessActions";
import { moveClassificationColors } from "@/lib/chess"; import { CLASSIFICATION_COLORS } from "@/constants";
interface Props { interface Props {
classification: MoveClassification; classification: MoveClassification;
@@ -74,7 +74,7 @@ export default function ClassificationRow({ classification }: Props) {
justifyContent="space-evenly" justifyContent="space-evenly"
alignItems="center" alignItems="center"
wrap="nowrap" wrap="nowrap"
color={moveClassificationColors[classification]} color={CLASSIFICATION_COLORS[classification]}
size={12} size={12}
> >
<Grid <Grid

View File

@@ -6,7 +6,7 @@ import { boardAtom, currentPositionAtom, gameAtom } from "../../../states";
import { useChessActions } from "@/hooks/useChessActions"; import { useChessActions } from "@/hooks/useChessActions";
import { useEffect } from "react"; import { useEffect } from "react";
import { isInViewport } from "@/lib/helpers"; import { isInViewport } from "@/lib/helpers";
import { moveClassificationColors } from "@/lib/chess"; import { CLASSIFICATION_COLORS } from "@/constants";
interface Props { interface Props {
san: string; san: string;
@@ -89,7 +89,7 @@ const getMoveColor = (moveClassification?: MoveClassification) => {
return undefined; return undefined;
} }
return moveClassificationColors[moveClassification]; return CLASSIFICATION_COLORS[moveClassification];
}; };
const moveClassificationsToIgnore: MoveClassification[] = [ const moveClassificationsToIgnore: MoveClassification[] = [

View File

@@ -1,6 +1,6 @@
import { DotProps } from "recharts"; import { DotProps } from "recharts";
import { ChartItemData } from "./types"; import { ChartItemData } from "./types";
import { moveClassificationColors } from "@/lib/chess"; import { CLASSIFICATION_COLORS } from "@/constants";
export default function CustomDot({ export default function CustomDot({
cx, cx,
@@ -9,7 +9,7 @@ export default function CustomDot({
payload, payload,
}: DotProps & { payload?: ChartItemData }) { }: DotProps & { payload?: ChartItemData }) {
const moveColor = payload?.moveClassification const moveColor = payload?.moveClassification
? moveClassificationColors[payload.moveClassification] ? CLASSIFICATION_COLORS[payload.moveClassification]
: "grey"; : "grey";
return ( return (

View File

@@ -21,7 +21,7 @@ import type { ReactElement } from "react";
import CustomTooltip from "./tooltip"; import CustomTooltip from "./tooltip";
import { ChartItemData } from "./types"; import { ChartItemData } from "./types";
import { PositionEval } from "@/types/eval"; import { PositionEval } from "@/types/eval";
import { moveClassificationColors } from "@/lib/chess"; import { CLASSIFICATION_COLORS } from "@/constants";
import CustomDot from "./dot"; import CustomDot from "./dot";
import { MoveClassification } from "@/types/enums"; import { MoveClassification } from "@/types/enums";
import { useChessActions } from "@/hooks/useChessActions"; import { useChessActions } from "@/hooks/useChessActions";
@@ -51,7 +51,7 @@ export default function GraphTab(props: GridProps) {
}, [chartData]); }, [chartData]);
const boardMoveColor = currentPosition.eval?.moveClassification const boardMoveColor = currentPosition.eval?.moveClassification
? moveClassificationColors[currentPosition.eval.moveClassification] ? CLASSIFICATION_COLORS[currentPosition.eval.moveClassification]
: "grey"; : "grey";
// Render a dot only on selected classifications (always returns an element) // Render a dot only on selected classifications (always returns an element)

View File

@@ -6,7 +6,7 @@ import { useChessActions } from "@/hooks/useChessActions";
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
export default function NextMoveButton() { export default function NextMoveButton() {
const { makeMove: makeBoardMove } = useChessActions(boardAtom); const { playMove: playBoardMove } = useChessActions(boardAtom);
const game = useAtomValue(gameAtom); const game = useAtomValue(gameAtom);
const board = useAtomValue(boardAtom); const board = useAtomValue(boardAtom);
@@ -27,14 +27,14 @@ export default function NextMoveButton() {
.find((c) => c.fen === nextMove.after)?.comment; .find((c) => c.fen === nextMove.after)?.comment;
if (nextMove) { if (nextMove) {
makeBoardMove({ playBoardMove({
from: nextMove.from, from: nextMove.from,
to: nextMove.to, to: nextMove.to,
promotion: nextMove.promotion, promotion: nextMove.promotion,
comment, comment,
}); });
} }
}, [isButtonEnabled, boardHistory, game, makeBoardMove]); }, [isButtonEnabled, boardHistory, game, playBoardMove]);
useEffect(() => { useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => { const onKeyDown = (e: KeyboardEvent) => {

View File

@@ -1,3 +1,4 @@
import { DEFAULT_ENGINE } from "@/constants";
import { EngineName } from "@/types/enums"; import { EngineName } from "@/types/enums";
import { CurrentPosition, GameEval, SavedEvals } from "@/types/eval"; import { CurrentPosition, GameEval, SavedEvals } from "@/types/eval";
import { Chess } from "chess.js"; import { Chess } from "chess.js";
@@ -12,7 +13,7 @@ export const boardOrientationAtom = atom(true);
export const showBestMoveArrowAtom = atom(true); export const showBestMoveArrowAtom = atom(true);
export const showPlayerMoveIconAtom = atom(true); export const showPlayerMoveIconAtom = atom(true);
export const engineNameAtom = atom<EngineName>(EngineName.Stockfish17Lite); export const engineNameAtom = atom<EngineName>(DEFAULT_ENGINE);
export const engineDepthAtom = atom(14); export const engineDepthAtom = atom(14);
export const engineMultiPvAtom = atom(3); export const engineMultiPvAtom = atom(3);
export const evaluationProgressAtom = atom(0); export const evaluationProgressAtom = atom(0);

View File

@@ -26,7 +26,12 @@ import { isEngineSupported } from "@/lib/engine/shared";
import { Stockfish16_1 } from "@/lib/engine/stockfish16_1"; import { Stockfish16_1 } from "@/lib/engine/stockfish16_1";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { boardHueAtom, pieceSetAtom } from "@/components/board/states"; import { boardHueAtom, pieceSetAtom } from "@/components/board/states";
import { PIECE_SETS } from "@/components/board/constants"; import {
DEFAULT_ENGINE,
ENGINE_LABELS,
PIECE_SETS,
STRONGEST_ENGINE,
} from "@/constants";
interface Props { interface Props {
open: boolean; open: boolean;
@@ -76,10 +81,11 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
size={{ xs: 12, sm: 7, md: 8 }} size={{ xs: 12, sm: 7, md: 8 }}
> >
<Typography> <Typography>
Stockfish 17 Lite is the default engine if your device support its {ENGINE_LABELS[DEFAULT_ENGINE].small} is the default engine if
requirements. It offers the best balance between speed and your device support its requirements. It offers the best balance
strength. Stockfish 17 is the strongest engine available, note between speed and strength.{" "}
that it requires a one time download of 75MB. {ENGINE_LABELS[STRONGEST_ENGINE].small} is the strongest engine
available, note that it requires a one time download of 75MB.
</Typography> </Typography>
</Grid> </Grid>
@@ -105,7 +111,7 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
value={engine} value={engine}
disabled={!isEngineSupported(engine)} disabled={!isEngineSupported(engine)}
> >
{engineLabel[engine].full} {ENGINE_LABELS[engine].full}
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
@@ -183,35 +189,3 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
</Dialog> </Dialog>
); );
} }
export const engineLabel: Record<EngineName, { small: string; full: string }> =
{
[EngineName.Stockfish17]: {
full: "Stockfish 17 (75MB)",
small: "Stockfish 17",
},
[EngineName.Stockfish17Lite]: {
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

@@ -23,7 +23,7 @@ export default function BoardContainer() {
const game = useAtomValue(gameAtom); const game = useAtomValue(gameAtom);
const { white, black } = usePlayersData(gameAtom); const { white, black } = usePlayersData(gameAtom);
const playerColor = useAtomValue(playerColorAtom); const playerColor = useAtomValue(playerColorAtom);
const { makeMove: makeGameMove } = useChessActions(gameAtom); const { playMove } = useChessActions(gameAtom);
const engineElo = useAtomValue(engineEloAtom); const engineElo = useAtomValue(engineEloAtom);
const isGameInProgress = useAtomValue(isGameInProgressAtom); const isGameInProgress = useAtomValue(isGameInProgressAtom);
@@ -41,7 +41,7 @@ export default function BoardContainer() {
return; return;
} }
const move = await engine.getEngineNextMove(gameFen, engineElo); const move = await engine.getEngineNextMove(gameFen, engineElo);
if (move) makeGameMove(uciMoveParams(move)); if (move) playMove(uciMoveParams(move));
}; };
playEngineMove(); playEngineMove();

View File

@@ -32,7 +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"; import { DEFAULT_ENGINE, ENGINE_LABELS, STRONGEST_ENGINE } from "@/constants";
interface Props { interface Props {
open: boolean; open: boolean;
@@ -57,12 +57,12 @@ export default function GameSettingsDialog({ open, onClose }: Props) {
resetGame({ resetGame({
white: { white: {
name: name:
playerColor === Color.White ? "You" : engineLabel[engineName].small, playerColor === Color.White ? "You" : ENGINE_LABELS[engineName].small,
rating: playerColor === Color.White ? undefined : engineElo, rating: playerColor === Color.White ? undefined : engineElo,
}, },
black: { black: {
name: name:
playerColor === Color.Black ? "You" : engineLabel[engineName].small, playerColor === Color.Black ? "You" : ENGINE_LABELS[engineName].small,
rating: playerColor === Color.Black ? undefined : engineElo, rating: playerColor === Color.Black ? undefined : engineElo,
}, },
}); });
@@ -93,10 +93,11 @@ export default function GameSettingsDialog({ open, onClose }: Props) {
</DialogTitle> </DialogTitle>
<DialogContent sx={{ paddingBottom: 0 }}> <DialogContent sx={{ paddingBottom: 0 }}>
<Typography> <Typography>
Stockfish 17 Lite is the default engine if your device support its {ENGINE_LABELS[DEFAULT_ENGINE].small} is the default engine if your
requirements. It offers the best balance between speed and strength. device support its requirements. It offers the best balance between
Stockfish 17 is the strongest engine available, note that it requires speed and strength. {ENGINE_LABELS[STRONGEST_ENGINE].small} is the
a one time download of 75MB. strongest engine available, note that it requires a one time download
of 75MB.
</Typography> </Typography>
<Grid <Grid
marginTop={4} marginTop={4}
@@ -124,7 +125,7 @@ export default function GameSettingsDialog({ open, onClose }: Props) {
value={engine} value={engine}
disabled={!isEngineSupported(engine)} disabled={!isEngineSupported(engine)}
> >
{engineLabel[engine].full} {ENGINE_LABELS[engine].full}
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>

View File

@@ -1,3 +1,4 @@
import { DEFAULT_ENGINE } from "@/constants";
import { Color, EngineName } from "@/types/enums"; import { Color, EngineName } from "@/types/enums";
import { CurrentPosition } from "@/types/eval"; import { CurrentPosition } from "@/types/eval";
import { Chess } from "chess.js"; import { Chess } from "chess.js";
@@ -6,6 +7,6 @@ import { atom } from "jotai";
export const gameAtom = atom(new Chess()); export const gameAtom = atom(new Chess());
export const gameDataAtom = atom<CurrentPosition>({}); export const gameDataAtom = atom<CurrentPosition>({});
export const playerColorAtom = atom<Color>(Color.White); export const playerColorAtom = atom<Color>(Color.White);
export const enginePlayNameAtom = atom<EngineName>(EngineName.Stockfish17Lite); export const enginePlayNameAtom = atom<EngineName>(DEFAULT_ENGINE);
export const engineEloAtom = atom(1320); export const engineEloAtom = atom(1320);
export const isGameInProgressAtom = atom(false); export const isGameInProgressAtom = atom(false);