From b09617c35e175d37bce30587cb5f6804cdc270b3 Mon Sep 17 00:00:00 2001 From: Francisco Salgueiro Date: Sat, 11 Nov 2023 16:29:29 +0000 Subject: [PATCH] add multipv to report --- src-tauri/src/chess.rs | 42 ++++++++++++------- src/bindings.ts | 2 +- .../panels/analysis/ReportModal.tsx | 1 + src/utils/score.ts | 18 +++++--- src/utils/tests/score.test.ts | 21 ++++++++++ src/utils/treeReducer.ts | 30 +++++++------ 6 files changed, 80 insertions(+), 34 deletions(-) diff --git a/src-tauri/src/chess.rs b/src-tauri/src/chess.rs index 61792eab..9f62d4d5 100644 --- a/src-tauri/src/chess.rs +++ b/src-tauri/src/chess.rs @@ -400,7 +400,7 @@ pub async fn get_best_moves( let multipv = best_moves.multipv; let cur_depth = best_moves.depth; proc.best_moves.push(best_moves); - if multipv == proc.options.multipv { + if multipv == proc.real_multipv { if proc.best_moves.iter().all(|x| x.depth == cur_depth) && cur_depth >= proc.last_depth { @@ -425,7 +425,7 @@ pub async fn get_best_moves( #[derive(Serialize, Debug, Default, Type)] pub struct MoveAnalysis { - best: BestMoves, + best: Vec, novelty: bool, } @@ -451,7 +451,7 @@ pub async fn analyze_game( let path = PathBuf::from(&engine); let mut analysis: Vec = Vec::new(); - let (mut process, mut reader) = EngineProcess::new(path)?; + let (mut proc, mut reader) = EngineProcess::new(path)?; let fen = Fen::from_ascii(options.fen.as_bytes())?; @@ -462,7 +462,9 @@ pub async fn analyze_game( let san = San::from_ascii(m.as_bytes()).unwrap(); let m = san.to_move(&chess).unwrap(); chess.play_unchecked(&m); - fens.push(Fen::from_position(chess.clone(), EnPassantMode::Legal)); + if !chess.is_game_over() { + fens.push(Fen::from_position(chess.clone(), EnPassantMode::Legal)); + } }); if options.reversed { @@ -481,23 +483,33 @@ pub async fn analyze_game( }, )?; - process - .set_options(EngineOptions { - threads: 4, - multipv: 1, - fen: fen.to_string(), - extra_options: Vec::new(), - }) - .await?; + proc.set_options(EngineOptions { + threads: 4, + multipv: 2, + fen: fen.to_string(), + extra_options: Vec::new(), + }) + .await?; - process.go(&go_mode).await?; + proc.go(&go_mode).await?; let mut current_analysis = MoveAnalysis::default(); while let Ok(Some(line)) = reader.next_line().await { match parse_one(&line) { UciMessage::Info(attrs) => { - if let Ok(best_moves) = parse_uci_attrs(attrs, fen) { - current_analysis.best = best_moves; + if let Ok(best_moves) = parse_uci_attrs(attrs, &proc.options.fen.parse()?) { + let multipv = best_moves.multipv; + let cur_depth = best_moves.depth; + proc.best_moves.push(best_moves); + if multipv == proc.real_multipv { + if proc.best_moves.iter().all(|x| x.depth == cur_depth) + && cur_depth >= proc.last_depth + { + current_analysis.best = proc.best_moves.clone(); + proc.last_depth = cur_depth; + } + proc.best_moves.clear(); + } } } UciMessage::BestMove { .. } => { diff --git a/src/bindings.ts b/src/bindings.ts index 7c15cbb1..4c2df77b 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -20,7 +20,7 @@ try { else return { status: "error", error: e as any }; } }, -async analyzeGame(moves: string[], engine: string, goMode: GoMode, options: AnalysisOptions) : Promise<__Result__<{ best: BestMoves; novelty: boolean }[], string>> { +async analyzeGame(moves: string[], engine: string, goMode: GoMode, options: AnalysisOptions) : Promise<__Result__<{ best: BestMoves[]; novelty: boolean }[], string>> { try { return { status: "ok", data: await TAURI_INVOKE("plugin:tauri-specta|analyze_game", { moves, engine, goMode, options }) }; } catch (e) { diff --git a/src/components/panels/analysis/ReportModal.tsx b/src/components/panels/analysis/ReportModal.tsx index f863bf95..d85660ab 100644 --- a/src/components/panels/analysis/ReportModal.tsx +++ b/src/components/panels/analysis/ReportModal.tsx @@ -54,6 +54,7 @@ function ReportModal({ function analyze() { setInProgress(true); toggleReportingMode(); + console.log(form.values); commands .analyzeGame( moves, diff --git a/src/utils/score.ts b/src/utils/score.ts index 29238a25..f63d1c2a 100644 --- a/src/utils/score.ts +++ b/src/utils/score.ts @@ -1,7 +1,7 @@ import { minMax } from "@tiptap/react"; import { Color } from "chess.js"; import { Annotation } from "./chess"; -import { Score } from "@/bindings"; +import { BestMoves, Score } from "@/bindings"; export const INITIAL_SCORE: Score = { type: "cp", @@ -69,9 +69,9 @@ export function getAccuracy(prev: Score, next: Score, color: Color): number { const { prevCP, nextCP } = normalizeScores(prev, next, color); return minMax( 103.1668 * - Math.exp(-0.04354 * (getWinChance(prevCP) - getWinChance(nextCP))) - - 3.1669 + - 1, + Math.exp(-0.04354 * (getWinChance(prevCP) - getWinChance(nextCP))) - + 3.1669 + + 1, 0, 100 ); @@ -86,7 +86,8 @@ export function getCPLoss(prev: Score, next: Score, color: Color): number { export function getAnnotation( prev: Score, next: Score, - color: Color + color: Color, + prevMoves: BestMoves[] ): Annotation { const { prevCP, nextCP } = normalizeScores(prev, next, color); const winChanceDiff = getWinChance(prevCP) - getWinChance(nextCP); @@ -98,5 +99,12 @@ export function getAnnotation( } else if (winChanceDiff > 5) { return "?!"; } + + if (prevMoves.length > 1) { + const scores = normalizeScores(prevMoves[0].score, prevMoves[1].score, color); + if (getWinChance(scores.prevCP) - getWinChance(scores.nextCP) > 10) { + return "!"; + } + } return ""; } diff --git a/src/utils/tests/score.test.ts b/src/utils/tests/score.test.ts index 2c6f6f26..16f64be8 100644 --- a/src/utils/tests/score.test.ts +++ b/src/utils/tests/score.test.ts @@ -1,6 +1,7 @@ import { formatScore, getAccuracy, + getAnnotation, getCPLoss, getWinChance, parseScore, @@ -45,3 +46,23 @@ test("should calculate the cp loss correctly", () => { expect(getCPLoss({ type: "cp", value: 0 }, { type: "cp", value: 50 }, "b")).toBe(50); expect(getCPLoss({ type: "mate", value: -1 }, { type: "cp", value: 0 }, "b")).toBe(1000); }); + +test("should annotate as ??", () => { + expect(getAnnotation({ type: "cp", value: 0 }, { type: "cp", value: -500 }, "w", [])).toBe("??"); + expect(getAnnotation({ type: "cp", value: 0 }, { type: "cp", value: 500 }, "b", [])).toBe("??"); +}); + +test("should annotate as ?", () => { + expect(getAnnotation({ type: "cp", value: 0 }, { type: "cp", value: -200 }, "w", [])).toBe("?"); + expect(getAnnotation({ type: "cp", value: 0 }, { type: "cp", value: 200 }, "b", [])).toBe("?"); +}); + +test("should annotate as ?!", () => { + expect(getAnnotation({ type: "cp", value: 0 }, { type: "cp", value: -100 }, "w", [])).toBe("?!"); + expect(getAnnotation({ type: "cp", value: 0 }, { type: "cp", value: 100 }, "b", [])).toBe("?!"); +}); + +test("should not annotate", () => { + expect(getAnnotation({ type: "cp", value: 0 }, { type: "cp", value: -50 }, "w", [])).toBe(""); + expect(getAnnotation({ type: "cp", value: 0 }, { type: "cp", value: 50 }, "b", [])).toBe(""); +}); \ No newline at end of file diff --git a/src/utils/treeReducer.ts b/src/utils/treeReducer.ts index b86cbe9d..fea634ff 100644 --- a/src/utils/treeReducer.ts +++ b/src/utils/treeReducer.ts @@ -200,7 +200,7 @@ export type TreeAction = | { type: "SET_FEN"; payload: string } | { type: "SET_SCORE"; payload: Score } | { type: "SET_SHAPES"; payload: DrawShape[] } - | { type: "ADD_ANALYSIS"; payload: { best: BestMoves, novelty: boolean }[] } + | { type: "ADD_ANALYSIS"; payload: { best: BestMoves[], novelty: boolean }[] } | { type: "PROMOTE_VARIATION"; payload: number[] }; const treeReducer = (state: TreeState, action: TreeAction) => { @@ -388,23 +388,27 @@ export function getColorFromFen(fen: string): "w" | "b" { return "b"; } -function addAnalysis(state: TreeState, analysis: { best: BestMoves, novelty: boolean }[]) { +function addAnalysis(state: TreeState, analysis: { best: BestMoves[], novelty: boolean }[]) { let cur = state.root; let i = 0; const initialColor = getColorFromFen(state.root.fen); while (cur !== undefined && i < analysis.length) { - cur.score = analysis[i].best.score; - if (analysis[i].novelty) { - cur.commentHTML = "Novelty"; - cur.commentText = "Novelty"; - } - let prevScore: Score = { type: "cp", value: 0 }; - if (i > 0) { - prevScore = analysis[i - 1].best.score; + if (!(new Chess(cur.fen).isGameOver())) { + cur.score = analysis[i].best[0].score; + if (analysis[i].novelty) { + cur.commentHTML = "Novelty"; + cur.commentText = "Novelty"; + } + let prevScore: Score = { type: "cp", value: 0 }; + let prevMoves: BestMoves[] = []; + if (i > 0) { + prevScore = analysis[i - 1].best[0].score; + prevMoves = analysis[i - 1].best; + } + const curScore = analysis[i].best[0].score; + const color = i % 2 === (initialColor === "w" ? 1 : 0) ? "w" : "b"; + cur.annotation = getAnnotation(prevScore, curScore, color, prevMoves); } - const curScore = analysis[i].best.score; - const color = i % 2 === (initialColor === "w" ? 1: 0) ? "w" : "b"; - cur.annotation = getAnnotation(prevScore, curScore, color); cur = cur.children[0]; i++; }