feat: refine wasm interfacing

- allow loading wordlist from internal data
- no unwraps in wasm branches
This commit is contained in:
2026-08-20 12:23:38 +02:00
parent c6e8849b28
commit ffc5b9d38e
5 changed files with 89 additions and 50 deletions
+9 -3
View File
@@ -13,7 +13,7 @@ pub fn do_simulation(valid_words: WordList, target_words: WordList, opener: Opti
if let Some(first_guess) = opener {
match game.guess(&first_guess) {
Ok(pat) => {
solver.apply_guess(pat);
solver.apply_guess(pat).unwrap();
println!("{}", solver.tabular_format());
}
Err(n) => {
@@ -24,12 +24,18 @@ pub fn do_simulation(valid_words: WordList, target_words: WordList, opener: Opti
}
loop {
let best_word = solver.evaluate_all_words().first().unwrap().word.clone();
let best_word = solver
.evaluate_all_words()
.unwrap()
.first()
.unwrap()
.word
.clone();
println!("{}", solver.best_word_format(5));
match game.guess(&best_word) {
Ok(pat) => {
solver.apply_guess(pat);
solver.apply_guess(pat).unwrap();
println!("{}", solver.tabular_format());
}
Err(n) => {
+1 -1
View File
@@ -36,7 +36,7 @@ impl Pattern {
))
} else {
Ok(Pattern {
chars: word.chars().collect(),
chars: word.chars().map(|c| c.to_ascii_uppercase()).collect(),
stats,
})
}
+11 -9
View File
@@ -53,13 +53,13 @@ impl Solver {
}
}
pub fn apply_guess(&mut self, guess: Pattern) {
pub fn apply_guess(&mut self, guess: Pattern) -> Result<(), String> {
let new_possible_solutions = self.possible_solutions.apply_guess(&guess);
let information_gained = self.possible_solutions.equal_likelieness_entropy()
- new_possible_solutions.equal_likelieness_entropy();
if new_possible_solutions.len() == 0 {
panic!("Solution space is empty");
return Err("Solution space is empty".to_string());
}
self.possible_solutions = new_possible_solutions;
@@ -68,6 +68,8 @@ impl Solver {
pattern: guess,
information_gained,
});
Ok(())
}
fn estimate_guesses(&self, expected_information_gained: f64) -> f64 {
@@ -78,10 +80,10 @@ impl Solver {
}
}
pub fn evaluate_word(&self, word: &str) -> WordStats {
pub fn evaluate_word(&self, word: &str) -> Result<WordStats, String> {
let solution_probability = self.possible_solutions.word_probability(word);
let expected_information_gained = self.possible_solutions.entropy_if_guessed(word);
let expected_information_gained = self.possible_solutions.entropy_if_guessed(word)?;
let score = (self.guesses.len() + 1) as f64;
@@ -89,29 +91,29 @@ impl Solver {
+ (1.0 - solution_probability)
* (score + self.estimate_guesses(expected_information_gained));
WordStats {
Ok(WordStats {
word: word.to_string(),
solution_probability,
expected_information_gained,
expected_score_after_guess,
}
})
}
pub fn evaluate_all_words(&mut self) -> Vec<WordStats> {
pub fn evaluate_all_words(&mut self) -> Result<Vec<WordStats>, String> {
self.best_words = self
.valid_words
.words()
.par_bridge()
.progress_count(self.valid_words.len() as u64)
.map(|w| self.evaluate_word(w))
.collect();
.collect::<Result<_, String>>()?;
self.best_words.sort_by(|a, b| {
a.expected_score_after_guess
.total_cmp(&b.expected_score_after_guess)
});
self.best_words.clone()
Ok(self.best_words.clone())
}
pub fn best_word_format(&self, n: usize) -> String {
+48 -13
View File
@@ -1,25 +1,60 @@
use std::io;
use wasm_bindgen::prelude::*;
use crate::{solver, word_list::WordList};
use crate::{
pattern::{CharStatus, Pattern},
solver,
word_list::WordList,
};
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet() {
alert("Hello, wasm-on-web!");
}
static DEFAULT_VALID_WORDS: &str = include_str!("../../valid-words.txt");
static DEFAULT_ANSWERS: &str = include_str!("../../answers.txt");
#[wasm_bindgen]
struct Solver(solver::Solver);
#[wasm_bindgen]
pub enum Color {
GREY,
YELLOW,
GREEN,
}
#[wasm_bindgen]
extern "C" {
pub fn alert(msg: &str);
}
#[allow(dead_code)]
#[wasm_bindgen]
impl Solver {
pub fn new() -> Solver {
let wl = WordList::from(["test", "yooo"].iter());
Solver(solver::Solver::from_single_word_list(wl))
pub fn new() -> Result<Solver, String> {
let valid_words =
WordList::try_from(io::BufReader::new(io::Cursor::new(DEFAULT_VALID_WORDS)))?;
let answers = WordList::try_from(io::BufReader::new(io::Cursor::new(DEFAULT_ANSWERS)))?;
Ok(Solver(solver::Solver::new(valid_words, answers)))
}
pub fn guess(&mut self, word: &str, colors: Vec<Color>) -> Result<(), String> {
let pat = Pattern::try_new(
word,
colors
.iter()
.map(|c| match c {
Color::GREY => CharStatus::GREY,
Color::YELLOW => CharStatus::YELLOW,
Color::GREEN => CharStatus::GREEN,
})
.collect(),
)?;
alert(&format!("{:?}", pat));
self.0.apply_guess(pat)?;
Ok(())
}
pub fn stats(&self) -> String {
+20 -24
View File
@@ -2,7 +2,7 @@ use rand::{rng, seq::IteratorRandom};
use std::{
collections::{hash_set, HashSet},
fs::{self},
io::{self, BufRead},
io::{self, BufRead, Read},
path::Path,
};
@@ -16,27 +16,12 @@ pub struct WordList {
words: HashSet<String>,
}
impl<I, S> From<I> for WordList
where
I: Iterator<Item = S>,
S: AsRef<str>,
{
fn from(value: I) -> Self {
WordList {
words: value.into_iter().map(|s| s.as_ref().to_owned()).collect(),
}
}
}
impl WordList {
pub fn len(&self) -> usize {
self.words.len()
}
pub fn from_file(file: &Path) -> Result<WordList, String> {
let file = fs::File::open(file).map_err(|e| e.to_string())?;
impl<R: Read> TryFrom<io::BufReader<R>> for WordList {
type Error = String;
fn try_from(value: io::BufReader<R>) -> Result<WordList, String> {
let mut len = None;
let words: Result<HashSet<String>, String> = io::BufReader::new(file)
let words: Result<HashSet<String>, String> = value
.lines()
.map_while(Result::ok)
.map(|s| s.trim_ascii().to_ascii_uppercase())
@@ -63,6 +48,17 @@ impl WordList {
Ok(WordList { words: words? })
}
}
impl WordList {
pub fn len(&self) -> usize {
self.words.len()
}
pub fn from_file(file: &Path) -> Result<WordList, String> {
let file = fs::File::open(file).map_err(|e| e.to_string())?;
WordList::try_from(io::BufReader::new(file))
}
pub fn apply_guess(&self, pat: &Pattern) -> WordList {
WordList {
@@ -82,13 +78,13 @@ impl WordList {
.count()
}
pub fn entropy_if_guessed(&self, word: &str) -> f64 {
pub fn entropy_if_guessed(&self, word: &str) -> Result<f64, String> {
let mut e = 0.0;
let mut observed_patterns = HashSet::new();
for w in self.words.iter() {
let pat = Pattern::from_guess(w, word).unwrap();
let pat = Pattern::from_guess(w, word)?;
if observed_patterns.contains(&pat) {
continue;
}
@@ -100,7 +96,7 @@ impl WordList {
}
}
e
Ok(e)
}
pub fn word_probability(&self, word: &str) -> f64 {