diff --git a/src/app/wordle_simulation.rs b/src/app/wordle_simulation.rs index de110c2..7b997c1 100644 --- a/src/app/wordle_simulation.rs +++ b/src/app/wordle_simulation.rs @@ -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) => { diff --git a/src/lib/pattern.rs b/src/lib/pattern.rs index 8b279f0..9d9ff64 100644 --- a/src/lib/pattern.rs +++ b/src/lib/pattern.rs @@ -36,7 +36,7 @@ impl Pattern { )) } else { Ok(Pattern { - chars: word.chars().collect(), + chars: word.chars().map(|c| c.to_ascii_uppercase()).collect(), stats, }) } diff --git a/src/lib/solver.rs b/src/lib/solver.rs index 71e7d32..ad11e0e 100644 --- a/src/lib/solver.rs +++ b/src/lib/solver.rs @@ -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 { 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 { + pub fn evaluate_all_words(&mut self) -> Result, 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::>()?; 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 { diff --git a/src/lib/wasm.rs b/src/lib/wasm.rs index 1539ef2..ce9e14c 100644 --- a/src/lib/wasm.rs +++ b/src/lib/wasm.rs @@ -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 { + 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) -> 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 { diff --git a/src/lib/word_list.rs b/src/lib/word_list.rs index ac2c332..88363dc 100644 --- a/src/lib/word_list.rs +++ b/src/lib/word_list.rs @@ -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, } -impl From for WordList -where - I: Iterator, - S: AsRef, -{ - 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 { - let file = fs::File::open(file).map_err(|e| e.to_string())?; +impl TryFrom> for WordList { + type Error = String; + fn try_from(value: io::BufReader) -> Result { let mut len = None; - let words: Result, String> = io::BufReader::new(file) + + let words: Result, 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 { + 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 { 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 {