From a351ff1fb834bb5f1a2040b8204d6edc5f717266 Mon Sep 17 00:00:00 2001 From: Francisco Salgueiro Date: Sun, 1 Oct 2023 16:27:39 +0100 Subject: [PATCH 1/2] fix fen editor --- src-tauri/src/main.rs | 3 +- src-tauri/src/opening.rs | 39 +++++++- src/components/panels/info/FenInput.tsx | 116 ++++++++++++++---------- src/utils/chess.ts | 3 - 4 files changed, 107 insertions(+), 54 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index de0503b8..b5813998 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -47,7 +47,7 @@ use crate::{ chess::get_best_moves, db::{edit_db_info, get_db_info, get_games, get_players}, fs::download_file, - opening::get_opening_from_fen, + opening::{get_opening_from_fen, search_opening_name}, }; use tokio::sync::{RwLock, Semaphore}; @@ -204,6 +204,7 @@ fn main() { download_file, get_best_moves, get_opening_from_fen, + search_opening_name, get_puzzle, get_games, get_players, diff --git a/src-tauri/src/opening.rs b/src-tauri/src/opening.rs index 65a0027c..d1a5ea86 100644 --- a/src-tauri/src/opening.rs +++ b/src-tauri/src/opening.rs @@ -10,11 +10,15 @@ use shakmaty::{ }; use lazy_static::lazy_static; +use strsim::jaro_winkler; -#[derive(Serialize, Debug)] +use crate::error::Error; + +#[derive(Serialize, Debug, Clone)] pub struct Opening { eco: String, name: String, + fen: String, } #[derive(Deserialize)] @@ -45,6 +49,37 @@ pub fn get_opening_from_fen(fen: &str) -> Result<&str, &str> { .ok_or("No opening found") } +#[tauri::command] +pub async fn search_opening_name(query: String) -> Result, Error> { + let mut best_matches: Vec<(Opening, f64)> = Vec::new(); + + for opening in OPENINGS.values() { + if best_matches.iter().any(|(m, _)| m.name == opening.name) { + continue; + } + + let score = jaro_winkler(&query, &opening.name); + + if best_matches.len() < 15 { + best_matches.push((opening.clone(), score)); + best_matches.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + } else if let Some(min_score) = best_matches.last().map(|(_, s)| *s) { + if score > min_score { + best_matches.pop(); + best_matches.push((opening.clone(), score)); + best_matches.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + } + } + } + + if !best_matches.is_empty() { + let best_matches_names = best_matches.iter().map(|(o, _)| o.clone()).collect(); + Ok(best_matches_names) + } else { + Err(Error::NoMatchFound) + } +} + pub fn get_opening_from_eco(eco: &str) -> Result<&str, &str> { OPENINGS .values() @@ -67,11 +102,13 @@ lazy_static! { pos.play_unchecked(&san.to_move(&pos).expect("legal move")); } } + let fen = Fen::from_position(pos.clone(), EnPassantMode::Legal); map.insert( pos.zobrist_hash(EnPassantMode::Legal), Opening { eco: record.eco, name: record.name, + fen: fen.to_string(), }, ); } diff --git a/src/components/panels/info/FenInput.tsx b/src/components/panels/info/FenInput.tsx index 2a3617ba..4d57e7b6 100644 --- a/src/components/panels/info/FenInput.tsx +++ b/src/components/panels/info/FenInput.tsx @@ -13,21 +13,6 @@ type ItemProps = { group?: string; }; -const POSITIONS: ItemProps[] = [ - { - label: "Empty position", - value: "8/8/8/8/8/8/8/8 w - - 0 1", - }, - { - label: "Starting position", - value: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", - }, - { - label: "Ruy Lopez", - value: "r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1", - }, -]; - const SelectItem = forwardRef(function SelectItem( { label, value: fen, ...others }: ItemProps, ref @@ -53,12 +38,7 @@ type Castlingrights = { function FenInput({ currentFen }: { currentFen: string }) { const dispatch = useContext(TreeDispatchContext); - const [error, setError] = useState(undefined); - let positions = POSITIONS; - if (!positions.find((p) => p.value === currentFen)) { - positions = [...positions, { label: currentFen, value: currentFen }]; - } let chess: Chess | null; let whiteCastling: Castlingrights; let blackCastling: Castlingrights; @@ -90,40 +70,12 @@ function FenInput({ currentFen }: { currentFen: string }) { } } - function addFen(fen: string) { - if (fen) { - invoke<{ valid: boolean; error?: string }>("validate_fen", { - fen, - }).then((v) => { - if (v.valid) { - dispatch({ type: "SET_FEN", payload: fen }); - setError(undefined); - } else if (v.error) { - setError(capitalize(v.error)); - } - }); - } - return fen; - } - return ( FEN - ([ + { label: currentFen, value: currentFen }, + ]); + const [error, setError] = useState(undefined); + const dispatch = useContext(TreeDispatchContext); + + function addFen(fen: string) { + if (fen) { + invoke<{ valid: boolean; error?: string }>("validate_fen", { + fen, + }).then((v) => { + if (v.valid) { + dispatch({ type: "SET_FEN", payload: fen }); + setError(undefined); + } else if (v.error) { + setError(capitalize(v.error)); + } + }); + } + return fen; + } + + async function searchOpening(name: string) { + const results = await invoke< + { + eco: string; + name: string; + fen: string; + }[] + >("search_opening_name", { + query: name, + }); + setData([ + { + label: name, + value: name, + }, + ...results.map((p) => ({ + label: p.name, + value: p.fen, + })), + ]); + } + + return ( +