From 1ac075f1f081431c9697b1fb06ac633f0dfad7cd Mon Sep 17 00:00:00 2001 From: titanium_machine <78664175+titaniummachine1@users.noreply.github.com> Date: Thu, 8 May 2025 23:47:09 +0200 Subject: [PATCH] Elo play level selection (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(play): switch engine UI and logic from skill level to Elo rating (100–3200) --- src/components/slider.tsx | 16 ++++------ src/lib/engine/uciEngine.ts | 19 ++++++++++-- src/sections/play/board.tsx | 9 ++---- .../play/gameSettings/gameSettingsDialog.tsx | 30 ++++++++----------- src/sections/play/states.ts | 1 + 5 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/components/slider.tsx b/src/components/slider.tsx index 5459307..117a227 100644 --- a/src/components/slider.tsx +++ b/src/components/slider.tsx @@ -1,13 +1,13 @@ import { Grid2 as Grid, Slider as MuiSlider, Typography } from "@mui/material"; -interface Props { +export interface Props { value: number; setValue: (value: number) => void; min: number; max: number; label: string; size?: number; - marksFilter?: number; + step?: number; } export default function Slider({ @@ -17,7 +17,7 @@ export default function Slider({ value, setValue, size, - marksFilter = 1, + step = 1, }: Props) { return ( - {label} + {`${label}: ${value}`} ({ - value: i + min, - label: `${i + min}`, - })).filter((_, i) => i % marksFilter === 0)} - step={1} - valueLabelDisplay="off" + step={step} + valueLabelDisplay="auto" value={value} onChange={(_, value) => setValue(value as number)} aria-labelledby={`input-${label}`} diff --git a/src/lib/engine/uciEngine.ts b/src/lib/engine/uciEngine.ts index b3c145f..27bdd60 100644 --- a/src/lib/engine/uciEngine.ts +++ b/src/lib/engine/uciEngine.ts @@ -92,6 +92,20 @@ export class UciEngine { this.multiPv = multiPv; } + private async setLimitStrength(on: boolean) { + await this.broadcastCommands( + [`setoption name UCI_LimitStrength value ${on}`, "isready"], + "readyok" + ); + } + + private async setElo(elo: number) { + await this.broadcastCommands( + [`setoption name UCI_Elo value ${elo}`, "isready"], + "readyok" + ); + } + private async setSkillLevel(skillLevel: number, initCase = false) { if (!initCase) { if (skillLevel === this.skillLevel) return; @@ -352,11 +366,12 @@ export class UciEngine { public async getEngineNextMove( fen: string, - skillLevel: number, + elo: number, depth = 16 ): Promise { this.throwErrorIfNotReady(); - await this.setSkillLevel(skillLevel); + await this.setLimitStrength(true); + await this.setElo(elo); console.log(`Evaluating position: ${fen}`); diff --git a/src/sections/play/board.tsx b/src/sections/play/board.tsx index d00b5c3..1f39114 100644 --- a/src/sections/play/board.tsx +++ b/src/sections/play/board.tsx @@ -1,6 +1,6 @@ import { useAtomValue } from "jotai"; import { - engineSkillLevelAtom, + engineEloAtom, gameAtom, playerColorAtom, isGameInProgressAtom, @@ -24,7 +24,7 @@ export default function BoardContainer() { const { white, black } = usePlayersData(gameAtom); const playerColor = useAtomValue(playerColorAtom); const { makeMove: makeGameMove } = useChessActions(gameAtom); - const engineSkillLevel = useAtomValue(engineSkillLevelAtom); + const engineElo = useAtomValue(engineEloAtom); const isGameInProgress = useAtomValue(isGameInProgressAtom); const gameFen = game.fen(); @@ -40,10 +40,7 @@ export default function BoardContainer() { ) { return; } - const move = await engine.getEngineNextMove( - gameFen, - engineSkillLevel - 1 - ); + const move = await engine.getEngineNextMove(gameFen, engineElo); if (move) makeGameMove(uciMoveParams(move)); }; playEngineMove(); diff --git a/src/sections/play/gameSettings/gameSettingsDialog.tsx b/src/sections/play/gameSettings/gameSettingsDialog.tsx index 6c6a21f..e1c6dfc 100644 --- a/src/sections/play/gameSettings/gameSettingsDialog.tsx +++ b/src/sections/play/gameSettings/gameSettingsDialog.tsx @@ -20,7 +20,7 @@ import { import { useAtomLocalStorage } from "@/hooks/useAtomLocalStorage"; import { useAtom, useSetAtom } from "jotai"; import { - engineSkillLevelAtom, + engineEloAtom, playerColorAtom, isGameInProgressAtom, gameAtom, @@ -40,9 +40,9 @@ interface Props { } export default function GameSettingsDialog({ open, onClose }: Props) { - const [skillLevel, setSkillLevel] = useAtomLocalStorage( - "engine-skill-level", - engineSkillLevelAtom + const [engineElo, setEngineElo] = useAtomLocalStorage( + "engine-elo", + engineEloAtom ); const [engineName, setEngineName] = useAtomLocalStorage( "engine-play-name", @@ -56,20 +56,16 @@ export default function GameSettingsDialog({ open, onClose }: Props) { onClose(); resetGame({ whiteName: - playerColor === Color.White - ? "You" - : `${engineLabel[engineName].small} level ${skillLevel}`, + playerColor === Color.White ? "You" : engineLabel[engineName].small, blackName: - playerColor === Color.Black - ? "You" - : `${engineLabel[engineName].small} level ${skillLevel}`, + playerColor === Color.Black ? "You" : engineLabel[engineName].small, }); playGameStartSound(); setIsGameInProgress(true); logAnalyticsEvent("play_game", { engine: engineName, - skillLevel, + engineElo, playerColor, }); }; @@ -130,12 +126,12 @@ export default function GameSettingsDialog({ open, onClose }: Props) { diff --git a/src/sections/play/states.ts b/src/sections/play/states.ts index 0958acd..e5f5394 100644 --- a/src/sections/play/states.ts +++ b/src/sections/play/states.ts @@ -8,4 +8,5 @@ export const gameDataAtom = atom({}); export const playerColorAtom = atom(Color.White); export const enginePlayNameAtom = atom(EngineName.Stockfish17Lite); export const engineSkillLevelAtom = atom(1); +export const engineEloAtom = atom(1200); export const isGameInProgressAtom = atom(false);