Skip to content

Commit

Permalink
Merge pull request #13 from franciscoBSalgueiro/12-fen-editor-has-inc…
Browse files Browse the repository at this point in the history
…omplete-and-incorrect-information

Fix fen editor has incomplete and incorrect information
  • Loading branch information
franciscoBSalgueiro authored Oct 1, 2023
2 parents aa3fce6 + 0f8e352 commit 024f3db
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 83 deletions.
3 changes: 3 additions & 0 deletions src-tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ pub enum Error {
#[error("Missing reference database")]
MissingReferenceDatabase,

#[error("No opening found")]
NoOpeningFound,

#[error("No match found")]
NoMatchFound,

Expand Down
3 changes: 2 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -204,6 +204,7 @@ fn main() {
download_file,
get_best_moves,
get_opening_from_fen,
search_opening_name,
get_puzzle,
get_games,
get_players,
Expand Down
96 changes: 66 additions & 30 deletions src-tauri/src/opening.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
use std::collections::HashMap;

use log::info;
use serde::{Deserialize, Serialize};
use shakmaty::{
fen::Fen,
san::San,
zobrist::{Zobrist64, ZobristHash},
CastlingMode, Chess, EnPassantMode, Position,
};
use shakmaty::{fen::Fen, san::San, Chess, EnPassantMode, Position};

use lazy_static::lazy_static;
use strsim::jaro_winkler;

use crate::error::Error;

#[derive(Serialize, Debug)]
#[derive(Serialize, Debug, Clone)]
pub struct Opening {
eco: String,
name: String,
fen: String,
}

#[derive(Deserialize)]
Expand All @@ -33,30 +30,70 @@ const TSV_DATA: [&[u8]; 5] = [
];

#[tauri::command]
pub fn get_opening_from_fen(fen: &str) -> Result<&str, &str> {
let fen: Fen = fen.parse().or(Err("Invalid FEN"))?;
let pos: Chess = fen
.into_position(CastlingMode::Standard)
.or(Err("Invalid Position"))?;
let hash: Zobrist64 = pos.zobrist_hash(EnPassantMode::Legal);
pub fn get_opening_from_fen(fen: &str) -> Result<&str, Error> {
OPENINGS
.get(&hash)
.iter()
.find(|o| o.fen == fen)
.map(|o| o.name.as_str())
.ok_or("No opening found")
.ok_or(Error::NoOpeningFound)
}

pub fn get_opening_from_eco(eco: &str) -> Result<&str, &str> {
pub fn get_opening_from_eco(eco: &str) -> Result<&str, Error> {
OPENINGS
.values()
.iter()
.find(|o| o.eco == eco)
.map(|o| o.name.as_str())
.ok_or("No opening found")
.ok_or(Error::NoOpeningFound)
}

#[tauri::command]
pub async fn search_opening_name(query: String) -> Result<Vec<Opening>, Error> {
let mut best_matches: Vec<(Opening, f64)> = Vec::new();

for opening in OPENINGS.iter() {
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)
}
}

lazy_static! {
static ref OPENINGS: HashMap<Zobrist64, Opening> = {
static ref OPENINGS: Vec<Opening> = {
info!("Initializing openings table...");
let mut map = HashMap::new();

let mut positions = vec![
Opening {
eco: "Extra".to_string(),
name: "Starting Position".to_string(),
fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1".to_string(),
},
Opening {
eco: "Extra".to_string(),
name: "Empty Board".to_string(),
fen: "8/8/8/8/8/8/8/8 w - - 0 1".to_string(),
},
];

for tsv in TSV_DATA {
let mut rdr = csv::ReaderBuilder::new().delimiter(b'\t').from_reader(tsv);
for result in rdr.deserialize() {
Expand All @@ -67,16 +104,15 @@ lazy_static! {
pos.play_unchecked(&san.to_move(&pos).expect("legal move"));
}
}
map.insert(
pos.zobrist_hash(EnPassantMode::Legal),
Opening {
eco: record.eco,
name: record.name,
},
);
let fen = Fen::from_position(pos.clone(), EnPassantMode::Legal);
positions.push(Opening {
eco: record.eco,
name: record.name,
fen: fen.to_string(),
});
}
}
map
positions
};
}

Expand Down
116 changes: 67 additions & 49 deletions src/components/panels/info/FenInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement, ItemProps>(function SelectItem(
{ label, value: fen, ...others }: ItemProps,
ref
Expand All @@ -53,12 +38,7 @@ type Castlingrights = {

function FenInput({ currentFen }: { currentFen: string }) {
const dispatch = useContext(TreeDispatchContext);
const [error, setError] = useState<string | undefined>(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;
Expand Down Expand Up @@ -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 (
<Stack spacing="sm">
<Group>
<Stack sx={{ flexGrow: 1 }}>
<Text fw="bold">FEN</Text>
<Select
placeholder="Enter FEN"
value={currentFen}
data={positions}
error={error}
itemComponent={SelectItem}
dropdownPosition="bottom"
onChange={addFen}
onCreate={addFen}
getCreateLabel={(fen) => fen}
searchable
creatable
/>
<FenSearch currentFen={currentFen} />
<Select
data={[
{ label: "White to move", value: "w" },
Expand Down Expand Up @@ -186,4 +138,70 @@ function FenInput({ currentFen }: { currentFen: string }) {
);
}

function FenSearch({ currentFen }: { currentFen: string }) {
const [data, setData] = useState<ItemProps[]>([
{ label: currentFen, value: currentFen },
]);
const [error, setError] = useState<string | undefined>(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 (
<Select
placeholder="Enter FEN"
value={currentFen}
data={data}
error={error}
itemComponent={SelectItem}
dropdownPosition="bottom"
onChange={addFen}
onCreate={addFen}
filter={() => true}
getCreateLabel={(fen) => {
searchOpening(fen);
return null;
}}
searchable
creatable
/>
);
}

export default memo(FenInput);
3 changes: 0 additions & 3 deletions src/utils/chess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,9 +343,6 @@ export async function getOpening(
root: TreeNode,
position: number[]
): Promise<string> {
if (position.length === 0) {
return "";
}
const tree = getNodeAtPath(root, position);
if (tree === null) {
return "";
Expand Down

0 comments on commit 024f3db

Please sign in to comment.