feat(lib): add async solver capabilities

This commit is contained in:
2026-08-20 21:48:23 +02:00
parent 27c308d632
commit fcb215f4c7
4 changed files with 77 additions and 20 deletions
+60 -8
View File
@@ -1,3 +1,8 @@
use std::sync::{
atomic::{AtomicUsize, Ordering},
mpsc, Arc,
};
use crate::pattern::Pattern;
use indicatif::ParallelProgressIterator;
use rayon::iter::{ParallelBridge, ParallelIterator};
@@ -7,16 +12,29 @@ use crate::word_list::WordList;
use derive_more::Display;
use tabular::{Row, Table};
#[derive(Clone)]
pub struct Guess {
pub pattern: Pattern,
pub information_gained: f64,
}
#[derive(Clone)]
pub struct Solver {
valid_words: WordList,
possible_solutions: WordList,
guesses: Vec<Guess>,
best_words: Vec<WordStats>,
}
pub struct SolverJob {
receiver: mpsc::Receiver<Result<Vec<WordStats>, String>>,
}
impl SolverJob {
pub fn wait(self) -> Result<Vec<WordStats>, String> {
self.receiver
.recv()
.map_err(|_| "calculation task cancelled".to_string())?
}
}
#[derive(Display, Clone)]
@@ -40,7 +58,6 @@ impl Solver {
valid_words: word_list.clone(),
possible_solutions: word_list,
guesses: vec![],
best_words: vec![],
}
}
@@ -49,7 +66,6 @@ impl Solver {
valid_words,
possible_solutions,
guesses: vec![],
best_words: vec![],
}
}
@@ -99,8 +115,44 @@ impl Solver {
})
}
pub fn evaluate_all_words_async<F>(&self, progress: F) -> SolverJob
where
F: Fn(usize, usize) + Send + Sync + 'static,
{
let solver_locked = (*self).clone();
let (tx, rc) = mpsc::channel();
let completed = Arc::new(AtomicUsize::new(0));
rayon::spawn_fifo(move || {
let mut result = solver_locked
.valid_words
.words()
.par_bridge()
.map(|w| {
let r = solver_locked.evaluate_word(w);
let completed = completed.fetch_add(1, Ordering::Relaxed) + 1;
progress(completed, solver_locked.valid_words.len());
r
})
.collect::<Result<Vec<WordStats>, String>>();
if let Ok(result) = &mut result {
result.sort_by(|a, b| {
a.expected_score_after_guess
.total_cmp(&b.expected_score_after_guess)
});
}
let _ = tx.send(result);
});
SolverJob { receiver: rc }
}
pub fn evaluate_all_words(&mut self) -> Result<Vec<WordStats>, String> {
self.best_words = self
let mut best_words: Vec<WordStats> = self
.valid_words
.words()
.par_bridge()
@@ -108,15 +160,15 @@ impl Solver {
.map(|w| self.evaluate_word(w))
.collect::<Result<_, String>>()?;
self.best_words.sort_by(|a, b| {
best_words.sort_by(|a, b| {
a.expected_score_after_guess
.total_cmp(&b.expected_score_after_guess)
});
Ok(self.best_words.clone())
Ok(best_words)
}
pub fn best_word_format(&self, n: usize) -> String {
pub fn best_word_format(best_words: Vec<WordStats>, n: usize) -> String {
let mut table = Table::new("#{:<} {:>} | {:<} {:<} {:<}");
table.add_row(
@@ -127,7 +179,7 @@ impl Solver {
.with_cell("E[I]")
.with_cell("E[score]"),
);
for (i, ws) in self.best_words.iter().take(n).enumerate() {
for (i, ws) in best_words.iter().take(n).enumerate() {
table.add_row(
Row::new()
.with_cell(i + 1)