feat: refine wasm interfacing
- allow loading wordlist from internal data - no unwraps in wasm branches
This commit is contained in:
@@ -13,7 +13,7 @@ pub fn do_simulation(valid_words: WordList, target_words: WordList, opener: Opti
|
|||||||
if let Some(first_guess) = opener {
|
if let Some(first_guess) = opener {
|
||||||
match game.guess(&first_guess) {
|
match game.guess(&first_guess) {
|
||||||
Ok(pat) => {
|
Ok(pat) => {
|
||||||
solver.apply_guess(pat);
|
solver.apply_guess(pat).unwrap();
|
||||||
println!("{}", solver.tabular_format());
|
println!("{}", solver.tabular_format());
|
||||||
}
|
}
|
||||||
Err(n) => {
|
Err(n) => {
|
||||||
@@ -24,12 +24,18 @@ pub fn do_simulation(valid_words: WordList, target_words: WordList, opener: Opti
|
|||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
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));
|
println!("{}", solver.best_word_format(5));
|
||||||
|
|
||||||
match game.guess(&best_word) {
|
match game.guess(&best_word) {
|
||||||
Ok(pat) => {
|
Ok(pat) => {
|
||||||
solver.apply_guess(pat);
|
solver.apply_guess(pat).unwrap();
|
||||||
println!("{}", solver.tabular_format());
|
println!("{}", solver.tabular_format());
|
||||||
}
|
}
|
||||||
Err(n) => {
|
Err(n) => {
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ impl Pattern {
|
|||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(Pattern {
|
Ok(Pattern {
|
||||||
chars: word.chars().collect(),
|
chars: word.chars().map(|c| c.to_ascii_uppercase()).collect(),
|
||||||
stats,
|
stats,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-9
@@ -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 new_possible_solutions = self.possible_solutions.apply_guess(&guess);
|
||||||
let information_gained = self.possible_solutions.equal_likelieness_entropy()
|
let information_gained = self.possible_solutions.equal_likelieness_entropy()
|
||||||
- new_possible_solutions.equal_likelieness_entropy();
|
- new_possible_solutions.equal_likelieness_entropy();
|
||||||
|
|
||||||
if new_possible_solutions.len() == 0 {
|
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;
|
self.possible_solutions = new_possible_solutions;
|
||||||
@@ -68,6 +68,8 @@ impl Solver {
|
|||||||
pattern: guess,
|
pattern: guess,
|
||||||
information_gained,
|
information_gained,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn estimate_guesses(&self, expected_information_gained: f64) -> f64 {
|
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 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;
|
let score = (self.guesses.len() + 1) as f64;
|
||||||
|
|
||||||
@@ -89,29 +91,29 @@ impl Solver {
|
|||||||
+ (1.0 - solution_probability)
|
+ (1.0 - solution_probability)
|
||||||
* (score + self.estimate_guesses(expected_information_gained));
|
* (score + self.estimate_guesses(expected_information_gained));
|
||||||
|
|
||||||
WordStats {
|
Ok(WordStats {
|
||||||
word: word.to_string(),
|
word: word.to_string(),
|
||||||
solution_probability,
|
solution_probability,
|
||||||
expected_information_gained,
|
expected_information_gained,
|
||||||
expected_score_after_guess,
|
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
|
self.best_words = self
|
||||||
.valid_words
|
.valid_words
|
||||||
.words()
|
.words()
|
||||||
.par_bridge()
|
.par_bridge()
|
||||||
.progress_count(self.valid_words.len() as u64)
|
.progress_count(self.valid_words.len() as u64)
|
||||||
.map(|w| self.evaluate_word(w))
|
.map(|w| self.evaluate_word(w))
|
||||||
.collect();
|
.collect::<Result<_, String>>()?;
|
||||||
|
|
||||||
self.best_words.sort_by(|a, b| {
|
self.best_words.sort_by(|a, b| {
|
||||||
a.expected_score_after_guess
|
a.expected_score_after_guess
|
||||||
.total_cmp(&b.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 {
|
pub fn best_word_format(&self, n: usize) -> String {
|
||||||
|
|||||||
+48
-13
@@ -1,25 +1,60 @@
|
|||||||
|
use std::io;
|
||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use crate::{solver, word_list::WordList};
|
use crate::{
|
||||||
|
pattern::{CharStatus, Pattern},
|
||||||
|
solver,
|
||||||
|
word_list::WordList,
|
||||||
|
};
|
||||||
|
|
||||||
#[wasm_bindgen]
|
static DEFAULT_VALID_WORDS: &str = include_str!("../../valid-words.txt");
|
||||||
extern "C" {
|
static DEFAULT_ANSWERS: &str = include_str!("../../answers.txt");
|
||||||
fn alert(s: &str);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn greet() {
|
|
||||||
alert("Hello, wasm-on-web!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
struct Solver(solver::Solver);
|
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]
|
#[wasm_bindgen]
|
||||||
impl Solver {
|
impl Solver {
|
||||||
pub fn new() -> Solver {
|
pub fn new() -> Result<Solver, String> {
|
||||||
let wl = WordList::from(["test", "yooo"].iter());
|
let valid_words =
|
||||||
Solver(solver::Solver::from_single_word_list(wl))
|
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 {
|
pub fn stats(&self) -> String {
|
||||||
|
|||||||
+20
-24
@@ -2,7 +2,7 @@ use rand::{rng, seq::IteratorRandom};
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{hash_set, HashSet},
|
collections::{hash_set, HashSet},
|
||||||
fs::{self},
|
fs::{self},
|
||||||
io::{self, BufRead},
|
io::{self, BufRead, Read},
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,27 +16,12 @@ pub struct WordList {
|
|||||||
words: HashSet<String>,
|
words: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I, S> From<I> for WordList
|
impl<R: Read> TryFrom<io::BufReader<R>> for WordList {
|
||||||
where
|
type Error = String;
|
||||||
I: Iterator<Item = S>,
|
fn try_from(value: io::BufReader<R>) -> Result<WordList, String> {
|
||||||
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())?;
|
|
||||||
let mut len = None;
|
let mut len = None;
|
||||||
let words: Result<HashSet<String>, String> = io::BufReader::new(file)
|
|
||||||
|
let words: Result<HashSet<String>, String> = value
|
||||||
.lines()
|
.lines()
|
||||||
.map_while(Result::ok)
|
.map_while(Result::ok)
|
||||||
.map(|s| s.trim_ascii().to_ascii_uppercase())
|
.map(|s| s.trim_ascii().to_ascii_uppercase())
|
||||||
@@ -63,6 +48,17 @@ impl WordList {
|
|||||||
|
|
||||||
Ok(WordList { words: words? })
|
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 {
|
pub fn apply_guess(&self, pat: &Pattern) -> WordList {
|
||||||
WordList {
|
WordList {
|
||||||
@@ -82,13 +78,13 @@ impl WordList {
|
|||||||
.count()
|
.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 e = 0.0;
|
||||||
|
|
||||||
let mut observed_patterns = HashSet::new();
|
let mut observed_patterns = HashSet::new();
|
||||||
|
|
||||||
for w in self.words.iter() {
|
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) {
|
if observed_patterns.contains(&pat) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -100,7 +96,7 @@ impl WordList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e
|
Ok(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn word_probability(&self, word: &str) -> f64 {
|
pub fn word_probability(&self, word: &str) -> f64 {
|
||||||
|
|||||||
Reference in New Issue
Block a user