From 398cdea5dfd62e9a76d27f38695285fb683f4dfa Mon Sep 17 00:00:00 2001 From: Francisco Salgueiro Date: Tue, 24 Oct 2023 22:00:46 +0100 Subject: [PATCH 1/3] allow infinite searching --- src/atoms/atoms.ts | 29 +++++----- src/components/panels/analysis/BestMoves.tsx | 58 ++++++++----------- .../panels/analysis/DepthSlider.tsx | 26 +++++++-- .../panels/analysis/EngineSettings.tsx | 18 +++--- 4 files changed, 68 insertions(+), 63 deletions(-) diff --git a/src/atoms/atoms.ts b/src/atoms/atoms.ts index 95189cd1..86ab8804 100644 --- a/src/atoms/atoms.ts +++ b/src/atoms/atoms.ts @@ -1,19 +1,19 @@ -import { atomFamily, atomWithStorage, createJSONStorage, loadable } from "jotai/utils"; -import { Tab, genID } from "@/utils/tabs"; -import { MantineColor } from "@mantine/core"; -import { Session } from "../utils/session"; -import { PrimitiveAtom, atom } from "jotai"; +import { GoMode } from "@/bindings"; +import { Card, buildFromTree } from "@/components/files/opening"; +import { LocalOptions } from "@/components/panels/database/DatabasePanel"; import { DatabaseInfo } from "@/utils/db"; +import { Engine } from "@/utils/engines"; +import { LichessGamesOptions, MasterGamesOptions } from "@/utils/lichess/lichessexplorer"; import { MissingMove } from "@/utils/repertoire"; -import { Card, buildFromTree } from "@/components/files/opening"; +import { Tab, genID } from "@/utils/tabs"; import { GameHeaders, TreeNode } from "@/utils/treeReducer"; +import { MantineColor } from "@mantine/core"; +import { BaseDirectory, readTextFile, removeFile, writeTextFile } from "@tauri-apps/api/fs"; +import { PrimitiveAtom, atom } from "jotai"; +import { atomFamily, atomWithStorage, createJSONStorage, loadable } from "jotai/utils"; import { AtomFamily } from "jotai/vanilla/utils/atomFamily"; -import EngineSettings from "@/components/panels/analysis/EngineSettings"; import { AsyncStringStorage } from "jotai/vanilla/utils/atomWithStorage"; -import { BaseDirectory, readTextFile, removeFile, writeTextFile } from "@tauri-apps/api/fs"; -import { Engine } from "@/utils/engines"; -import { LichessGamesOptions, MasterGamesOptions } from "@/utils/lichess/lichessexplorer"; -import { LocalOptions } from "@/components/panels/database/DatabasePanel"; +import { Session } from "../utils/session"; const options = { dir: BaseDirectory.AppData }; @@ -239,7 +239,7 @@ export const deckAtomFamily = atomFamily( export type EngineSettings = { enabled: boolean; - maxDepth: number; + go: GoMode; cores: number; numberLines: number; }; @@ -248,7 +248,10 @@ export const tabEngineSettingsFamily = atomFamily( ({ tab, engine }: { tab: string; engine: string }) => atom({ enabled: false, - maxDepth: 24, + go: { + t: "Depth", + c: 24, + }, cores: 2, numberLines: 3, }), diff --git a/src/components/panels/analysis/BestMoves.tsx b/src/components/panels/analysis/BestMoves.tsx index f18f93e6..15ace127 100644 --- a/src/components/panels/analysis/BestMoves.tsx +++ b/src/components/panels/analysis/BestMoves.tsx @@ -1,8 +1,15 @@ +import { activeTabAtom, tabEngineSettingsFamily } from "@/atoms/atoms"; +import { commands, events } from "@/bindings"; +import { TreeDispatchContext } from "@/components/common/TreeStateContext"; +import { BestMoves, swapMove } from "@/utils/chess"; +import { Engine } from "@/utils/engines"; +import { unwrap } from "@/utils/invoke"; +import { useThrottledEffect } from "@/utils/misc"; +import { formatScore } from "@/utils/score"; import { Accordion, ActionIcon, Box, - createStyles, Group, Progress, Skeleton, @@ -10,6 +17,7 @@ import { Table, Text, Tooltip, + createStyles, useMantineTheme, } from "@mantine/core"; import { useToggle } from "@mantine/hooks"; @@ -19,6 +27,8 @@ import { IconSettings, IconTargetArrow, } from "@tabler/icons-react"; +import { Chess } from "chess.js"; +import { useAtom, useAtomValue } from "jotai"; import { startTransition, useContext, @@ -26,18 +36,9 @@ import { useMemo, useState, } from "react"; -import { BestMoves, swapMove } from "@/utils/chess"; -import { Engine } from "@/utils/engines"; -import { invoke, unwrap } from "@/utils/invoke"; -import { useThrottledEffect } from "@/utils/misc"; -import { TreeDispatchContext } from "@/components/common/TreeStateContext"; -import EngineSettings from "./EngineSettings"; -import { Chess } from "chess.js"; +import { match } from "ts-pattern"; import AnalysisRow from "./AnalysisRow"; -import { useAtom, useAtomValue } from "jotai"; -import { activeTabAtom, tabEngineSettingsFamily } from "@/atoms/atoms"; -import { formatScore } from "@/utils/score"; -import { commands, events } from "@/bindings"; +import EngineSettings from "./EngineSettings"; const useStyles = createStyles((theme) => ({ subtitle: { @@ -80,7 +81,9 @@ export default function BestMovesComponent({ const { classes } = useStyles(); const depth = engineVariations[0]?.depth ?? 0; const nps = Math.floor(engineVariations[0]?.nps / 1000 ?? 0); - const progress = (depth / settings.maxDepth) * 100; + const progress = match(settings.go) + .with({ t: "Depth" }, ({ c }) => (depth / c) * 100) + .otherwise(() => 0); const theme = useMantineTheme(); useEffect(() => { @@ -125,19 +128,11 @@ export default function BestMovesComponent({ setEngineVariation([]); } else { commands - .getBestMoves( - engine.path, - activeTab!, - { - t: "Depth", - c: settings.maxDepth, - }, - { - fen: threat ? swapMove(fen) : fen, - multipv: settings.numberLines, - threads: 2 ** settings.cores, - } - ) + .getBestMoves(engine.path, activeTab!, settings.go, { + fen: threat ? swapMove(fen) : fen, + multipv: settings.numberLines, + threads: 2 ** settings.cores, + }) .then((res) => { unwrap(res); }); @@ -150,7 +145,7 @@ export default function BestMovesComponent({ [ settings.enabled, settings.cores, - settings.maxDepth, + settings.go, settings.numberLines, threat, fen, @@ -245,9 +240,7 @@ export default function BestMovesComponent({ @@ -297,10 +290,7 @@ export default function BestMovesComponent({ ), [ - settings.enabled, - settings.cores, - settings.maxDepth, - settings.numberLines, + settings, theme.primaryColor, isGameOver, engine.name, diff --git a/src/components/panels/analysis/DepthSlider.tsx b/src/components/panels/analysis/DepthSlider.tsx index be770aa3..8c679476 100644 --- a/src/components/panels/analysis/DepthSlider.tsx +++ b/src/components/panels/analysis/DepthSlider.tsx @@ -1,3 +1,4 @@ +import { GoMode } from "@/bindings"; import { Slider } from "@mantine/core"; import { useState } from "react"; @@ -5,8 +6,8 @@ export default function DepthSlider({ value, setValue, }: { - value: number; - setValue: (v: number) => void; + value: GoMode; + setValue: (v: GoMode) => void; }) { const [tempValue, setTempValue] = useState(value); const MARKS = [ @@ -18,14 +19,29 @@ export default function DepthSlider({ { value: 60 }, ]; + const v = tempValue.t === "Infinite" ? 60 : tempValue.c; + const handleSliderChange = (v: number, setState: (v: GoMode) => void) => { + if (v === 60) { + setState({ + t: "Infinite", + }); + } else { + setState({ + t: "Depth", + c: v, + }); + } + }; + return ( <> (v === 60 ? "Infinite" : v)} + onChange={(v) => handleSliderChange(v, setTempValue)} + onChangeEnd={(v) => handleSliderChange(v, setValue)} marks={MARKS} /> diff --git a/src/components/panels/analysis/EngineSettings.tsx b/src/components/panels/analysis/EngineSettings.tsx index d01e647a..aa7daa04 100644 --- a/src/components/panels/analysis/EngineSettings.tsx +++ b/src/components/panels/analysis/EngineSettings.tsx @@ -1,23 +1,19 @@ +import { EngineSettings } from "@/atoms/atoms"; import { Collapse, Group, Text } from "@mantine/core"; import React, { memo } from "react"; import CoresSlide from "./CoresSlider"; import DepthSlider from "./DepthSlider"; import LinesSlider from "./LinesSlider"; -import { EngineSettings } from "@/atoms/atoms"; interface EngineSettingsProps { settingsOn: boolean; - numberLines: number; - maxDepth: number; - cores: number; + settings: EngineSettings; setSettings: React.Dispatch>; } function EngineSettings({ settingsOn, - numberLines, - maxDepth, - cores, + settings, setSettings, }: EngineSettingsProps) { return ( @@ -27,7 +23,7 @@ function EngineSettings({ Number of Lines setSettings((prev) => ({ ...prev, numberLines: v }))} /> @@ -36,8 +32,8 @@ function EngineSettings({ Engine Depth setSettings((prev) => ({ ...prev, maxDepth: v }))} + value={settings.go} + setValue={(v) => setSettings((prev) => ({ ...prev, go: v }))} /> @@ -45,7 +41,7 @@ function EngineSettings({ Number of cores setSettings((prev) => ({ ...prev, cores: v }))} /> From 18b5452ae6f2c25fe6f66fb0e8cd097d7c6a9d4b Mon Sep 17 00:00:00 2001 From: Francisco Salgueiro Date: Sun, 29 Oct 2023 00:20:53 +0100 Subject: [PATCH 2/3] infinite analysis loading --- src/components/panels/analysis/BestMoves.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/panels/analysis/BestMoves.tsx b/src/components/panels/analysis/BestMoves.tsx index 15ace127..38cb3ed8 100644 --- a/src/components/panels/analysis/BestMoves.tsx +++ b/src/components/panels/analysis/BestMoves.tsx @@ -83,6 +83,7 @@ export default function BestMovesComponent({ const nps = Math.floor(engineVariations[0]?.nps / 1000 ?? 0); const progress = match(settings.go) .with({ t: "Depth" }, ({ c }) => (depth / c) * 100) + .with({ t: "Infinite" }, () => 99.9) .otherwise(() => 0); const theme = useMantineTheme(); From c74fb2fbe1a99fd0641747c1e2a492b0253011cd Mon Sep 17 00:00:00 2001 From: Francisco Salgueiro Date: Sun, 29 Oct 2023 11:49:32 +0000 Subject: [PATCH 3/3] add custom settings to engine --- src-tauri/src/chess.rs | 22 +- src/atoms/atoms.ts | 2 + src/bindings.ts | 3 +- src/components/panels/analysis/BestMoves.tsx | 3 +- .../panels/analysis/CoresSlider.tsx | 10 +- .../panels/analysis/DepthSlider.tsx | 6 +- .../panels/analysis/EngineSettings.tsx | 270 ++++++++++++++++-- .../panels/analysis/LinesSlider.tsx | 6 +- 8 files changed, 276 insertions(+), 46 deletions(-) diff --git a/src-tauri/src/chess.rs b/src-tauri/src/chess.rs index a6f4009c..583ec2b6 100644 --- a/src-tauri/src/chess.rs +++ b/src-tauri/src/chess.rs @@ -90,6 +90,11 @@ impl EngineProcess { .await?; self.set_option("MultiPV", &options.multipv.to_string()) .await?; + + for option in options.extra_options { + self.set_option(&option.name, &option.value).await?; + } + self.set_position(&options.fen).await?; self.last_depth = 0; self.best_moves.clear(); @@ -262,10 +267,18 @@ async fn send_command(stdin: &mut ChildStdin, command: impl AsRef) { } #[derive(Deserialize, Debug, Clone, Type)] +#[serde(rename_all = "camelCase")] pub struct EngineOptions { pub multipv: u16, pub threads: u16, pub fen: String, + pub extra_options: Vec, +} + +#[derive(Deserialize, Debug, Clone, Type)] +pub struct EngineOption { + name: String, + value: String, } #[derive(Deserialize, Debug, Clone, Type)] @@ -430,14 +443,6 @@ pub async fn analyze_game( let mut chess: Chess = fen.into_position(CastlingMode::Standard)?; - process - .set_options(EngineOptions { - threads: 4, - multipv: 1, - fen: options.fen.to_string(), - }) - .await?; - let len_moves = moves.len(); let mut novelty_found = false; @@ -465,6 +470,7 @@ pub async fn analyze_game( threads: 4, multipv: 1, fen: fen.to_string(), + extra_options: Vec::new(), }) .await?; diff --git a/src/atoms/atoms.ts b/src/atoms/atoms.ts index 86ab8804..9f3c2a4a 100644 --- a/src/atoms/atoms.ts +++ b/src/atoms/atoms.ts @@ -242,6 +242,7 @@ export type EngineSettings = { go: GoMode; cores: number; numberLines: number; + extraOptions: { name: string, value: string }[]; }; export const tabEngineSettingsFamily = atomFamily( @@ -254,6 +255,7 @@ export const tabEngineSettingsFamily = atomFamily( }, cores: 2, numberLines: 3, + extraOptions: [], }), (a, b) => a.tab === b.tab && a.engine === b.engine ); diff --git a/src/bindings.ts b/src/bindings.ts index 6f8e5927..08b4813d 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -65,7 +65,8 @@ bestMovesPayload: "plugin:tauri-specta:best-moves-payload" export type AnalysisOptions = { fen: string; annotateNovelties: boolean; referenceDb: string | null } export type BestMoves = { depth: number; score: Score; uciMoves: string[]; sanMoves: string[]; multipv: number; nps: number } export type BestMovesPayload = { bestLines: BestMoves[]; engine: string; tab: string } -export type EngineOptions = { multipv: number; threads: number; fen: string } +export type EngineOption = { name: string; value: string } +export type EngineOptions = { multipv: number; threads: number; fen: string; extraOptions: EngineOption[] } export type GoMode = { t: "Depth"; c: number } | { t: "Time"; c: number } | { t: "Nodes"; c: number } | { t: "Infinite" } export type Score = { type: "cp"; value: number } | { type: "mate"; value: number } diff --git a/src/components/panels/analysis/BestMoves.tsx b/src/components/panels/analysis/BestMoves.tsx index 38cb3ed8..037c0790 100644 --- a/src/components/panels/analysis/BestMoves.tsx +++ b/src/components/panels/analysis/BestMoves.tsx @@ -132,7 +132,8 @@ export default function BestMovesComponent({ .getBestMoves(engine.path, activeTab!, settings.go, { fen: threat ? swapMove(fen) : fen, multipv: settings.numberLines, - threads: 2 ** settings.cores, + threads: settings.cores, + extraOptions: settings.extraOptions, }) .then((res) => { unwrap(res); diff --git a/src/components/panels/analysis/CoresSlider.tsx b/src/components/panels/analysis/CoresSlider.tsx index 7daf723f..a4b9c87b 100644 --- a/src/components/panels/analysis/CoresSlider.tsx +++ b/src/components/panels/analysis/CoresSlider.tsx @@ -1,5 +1,5 @@ import { Slider } from "@mantine/core"; -import { useState } from "react"; +import { useEffect, useState } from "react"; export default function CoresSlide({ value, @@ -8,7 +8,7 @@ export default function CoresSlide({ value: number; setValue: (v: number) => void; }) { - const [tempValue, setTempValue] = useState(value); + const [tempValue, setTempValue] = useState(Math.log2(value)); const MARKS = [ { value: 0 }, { value: 1 }, @@ -19,6 +19,10 @@ export default function CoresSlide({ { value: 6 }, ]; + useEffect(() => { + setTempValue(Math.log2(value)); + }, [value]); + return ( <> setValue(2 ** v)} marks={MARKS} label={(value) => 2 ** value} /> diff --git a/src/components/panels/analysis/DepthSlider.tsx b/src/components/panels/analysis/DepthSlider.tsx index 8c679476..f9411093 100644 --- a/src/components/panels/analysis/DepthSlider.tsx +++ b/src/components/panels/analysis/DepthSlider.tsx @@ -1,6 +1,6 @@ import { GoMode } from "@/bindings"; import { Slider } from "@mantine/core"; -import { useState } from "react"; +import { useEffect, useState } from "react"; export default function DepthSlider({ value, @@ -19,6 +19,10 @@ export default function DepthSlider({ { value: 60 }, ]; + useEffect(() => { + setTempValue(value); + }, [value]); + const v = tempValue.t === "Infinite" ? 60 : tempValue.c; const handleSliderChange = (v: number, setState: (v: GoMode) => void) => { if (v === 60) { diff --git a/src/components/panels/analysis/EngineSettings.tsx b/src/components/panels/analysis/EngineSettings.tsx index aa7daa04..23272e33 100644 --- a/src/components/panels/analysis/EngineSettings.tsx +++ b/src/components/panels/analysis/EngineSettings.tsx @@ -1,9 +1,23 @@ import { EngineSettings } from "@/atoms/atoms"; -import { Collapse, Group, Text } from "@mantine/core"; -import React, { memo } from "react"; +import { + ActionIcon, + Button, + Center, + Collapse, + Group, + Modal, + NumberInput, + Select, + SimpleGrid, + Table, + Text, + TextInput, +} from "@mantine/core"; +import React, { memo, useState } from "react"; import CoresSlide from "./CoresSlider"; import DepthSlider from "./DepthSlider"; import LinesSlider from "./LinesSlider"; +import { IconPlus, IconX } from "@tabler/icons-react"; interface EngineSettingsProps { settingsOn: boolean; @@ -16,36 +30,230 @@ function EngineSettings({ settings, setSettings, }: EngineSettingsProps) { + const [advancedOptions, setAdvancedOptions] = useState(false); + return ( - - - - Number of Lines - - setSettings((prev) => ({ ...prev, numberLines: v }))} - /> - - - - Engine Depth - - setSettings((prev) => ({ ...prev, go: v }))} - /> - - - - Number of cores - - setSettings((prev) => ({ ...prev, cores: v }))} - /> - - + <> + + + + + Number of Lines + + + setSettings((prev) => ({ ...prev, numberLines: v })) + } + /> + {settings.go.t === "Infinite" || settings.go.t === "Depth" ? ( + <> + + Depth + + setSettings((prev) => ({ ...prev, go: v }))} + /> + + ) : ( + <> + + {settings.go.t} + + + setSettings((prev) => { + return { + ...prev, + go: { + ...prev.go, + c: v || 1, + }, + }; + }) + } + /> + + )} + + Number of cores + + setSettings((prev) => ({ ...prev, cores: v }))} + /> + + + + + + ); +} + +function AdvancedOptions({ + opened, + setOpened, + settings, + setSettings, +}: { + opened: boolean; + setOpened: React.Dispatch>; + settings: EngineSettings; + setSettings: React.Dispatch>; +}) { + return ( + setOpened(false)} + > + + + + + + + + + + + + + + + {settings.extraOptions.map((option, i) => ( + + + + + ))} + +
+ + {settings.go.t !== "Infinite" && ( + + setSettings((prev) => { + return { + ...prev, + go: { + ...prev.go, + c: v || 1, + }, + }; + }) + } + /> + )} +
Threads + + setSettings((prev) => ({ ...prev, cores: v || 1 })) + } + /> +
MultiPV + + setSettings((prev) => ({ ...prev, numberLines: v || 1 })) + } + /> +
+ { + const newOptions = settings.extraOptions; + newOptions[i].name = e.currentTarget.value; + setSettings((prev) => ({ + ...prev, + extraOptions: newOptions, + })); + }} + /> + + + { + const newOptions = settings.extraOptions; + newOptions[i].value = e.currentTarget.value; + setSettings((prev) => ({ + ...prev, + extraOptions: newOptions, + })); + }} + /> + {/* Remove button */} + { + const newOptions = settings.extraOptions; + newOptions.splice(i, 1); + setSettings((prev) => ({ + ...prev, + extraOptions: newOptions, + })); + }} + > + + + +
+ +
+ +
+
); } diff --git a/src/components/panels/analysis/LinesSlider.tsx b/src/components/panels/analysis/LinesSlider.tsx index b91cd214..51ab0bfb 100644 --- a/src/components/panels/analysis/LinesSlider.tsx +++ b/src/components/panels/analysis/LinesSlider.tsx @@ -1,5 +1,5 @@ import { Slider } from "@mantine/core"; -import { useState } from "react"; +import { useEffect, useState } from "react"; export default function DepthSlider({ value, @@ -17,6 +17,10 @@ export default function DepthSlider({ { value: 5 }, ]; + useEffect(() => { + setTempValue(value); + }, [value]); + return ( <>