reuse wordlist

This commit is contained in:
Christoph J. Scherr 2024-03-22 13:00:49 +01:00
parent df15d117ef
commit 02d67cdd0c
Signed by: cscherrNT
GPG Key ID: 8E2B45BC51A27EA7
11 changed files with 80 additions and 32 deletions

View File

@ -13,10 +13,10 @@ keywords = ["wordle", "benchmark"]
default-run = "wordlec" default-run = "wordlec"
[features] [features]
default = ["game", "bench", "tui", "solvers", "builtin_wlist", "serde"] default = ["game", "bench", "tui", "solve", "builtin_wlist", "serde"]
builtin_wlist = ["dep:serde_json", "serde"] builtin_wlist = ["dep:serde_json", "serde"]
game = ["builtin_wlist"] game = ["builtin_wlist"]
solvers = [] solve = []
tui = ["cli"] tui = ["cli"]
cli = ["dep:clap"] cli = ["dep:clap"]
bench = [] bench = []
@ -41,3 +41,8 @@ required-features = ["game", "cli"]
name = "wordlet" name = "wordlet"
path = "src/bin/game/tui.rs" path = "src/bin/game/tui.rs"
required-features = ["tui", "game"] required-features = ["tui", "game"]
[[bin]]
name = "wordlesolve"
path = "src/bin/solve/simple.rs"
required-features = ["game", "solve", "cli"]

View File

@ -39,11 +39,13 @@ fn main() -> anyhow::Result<()> {
} }
debug!("dumping CLI: {:#?}", cli); debug!("dumping CLI: {:#?}", cli);
let mut game = game::Game::<BuiltinWList>::builder() let wl = BuiltinWList::default();
let builder = game::Game::builder()
.length(cli.length) .length(cli.length)
.max_steps(cli.max_steps) .max_steps(cli.max_steps)
.precompute(cli.precompute) .precompute(cli.precompute)
.build()?; .wordlist(wl);
let mut game = builder.build()?;
debug!("{game:#?}"); debug!("{game:#?}");

3
src/bin/solve/simple.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
unimplemented!();
}

View File

@ -1,6 +1,6 @@
use thiserror::Error; use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>; pub type WResult<T> = std::result::Result<T, Error>;
pub type GameResult<T> = std::result::Result<T, GameError>; pub type GameResult<T> = std::result::Result<T, GameError>;
#[derive(Debug, Error)] #[derive(Debug, Error)]

View File

@ -1,15 +1,16 @@
use crate::error::*; use crate::error::*;
use crate::wlist::word::{Solution, Word}; use crate::wlist::word::{WordData, Word};
use crate::wlist::WordList; use crate::wlist::WordList;
pub mod response;
use libpt::log::debug; use libpt::log::debug;
pub mod response;
use response::GuessResponse; use response::GuessResponse;
use self::response::Status; use self::response::Status;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Game<WL> pub struct Game<'wl, WL>
where where
WL: WordList, WL: WordList,
{ {
@ -17,13 +18,13 @@ where
precompute: bool, precompute: bool,
max_steps: usize, max_steps: usize,
step: usize, step: usize,
solution: Solution, solution: WordData,
wordlist: WL, wordlist: &'wl WL,
finished: bool, finished: bool,
// TODO: keep track of the letters the user has tried // TODO: keep track of the letters the user has tried
} }
impl<WL: WordList> Game<WL> { impl<'wl, WL: WordList> Game<'wl, WL> {
/// get a new [`GameBuilder`] /// get a new [`GameBuilder`]
pub fn builder() -> GameBuilder<WL> { pub fn builder() -> GameBuilder<WL> {
GameBuilder::default() GameBuilder::default()
@ -44,30 +45,33 @@ impl<WL: WordList> Game<WL> {
length: usize, length: usize,
precompute: bool, precompute: bool,
max_steps: usize, max_steps: usize,
wlist: WL, wlist: &'wl WL,
) -> GameResult<Self> { ) -> GameResult<Self> {
// TODO: check if the length is in the range bounds of the wordlist // TODO: check if the length is in the range bounds of the wordlist
let solution = wlist.rand_solution(); let solution = wlist.rand_solution();
let game = Game { let game: Game<'wl, WL> = Game {
length, length,
precompute, precompute,
max_steps, max_steps,
step: 1, step: 1,
solution, solution,
wordlist: wlist, wordlist: &wlist,
finished: false, finished: false,
}; };
Ok(game) Ok(game)
} }
pub fn reset(mut self) -> Self { /// Make a new guess
self.solution = self.wordlist.rand_solution(); ///
self.step = 1; /// The word will be evaluated against the [solution](Game::solution) of the [Game].
self.finished = false; /// A [GuessResponse] will be formulated, showing us which letters are correctly placed, in the
self /// solution, or just wrong.
} ///
/// # Errors
///
/// This function will return an error if the length of the [Word] is wrong It will also error
/// if the game is finished.
pub fn guess(&mut self, guess: Word) -> GameResult<GuessResponse> { pub fn guess(&mut self, guess: Word) -> GameResult<GuessResponse> {
if guess.len() != self.length { if guess.len() != self.length {
return Err(GameError::GuessHasWrongLength); return Err(GameError::GuessHasWrongLength);
@ -102,7 +106,7 @@ impl<WL: WordList> Game<WL> {
self.length self.length
} }
pub fn solution(&self) -> &Solution { pub fn solution(&self) -> &WordData {
&self.solution &self.solution
} }
@ -153,10 +157,10 @@ pub struct GameBuilder<WL: WordList> {
impl<WL: WordList> GameBuilder<WL> { impl<WL: WordList> GameBuilder<WL> {
/// build a [`Game`] with the stored configuration /// build a [`Game`] with the stored configuration
pub fn build(self) -> GameResult<Game<WL>> { pub fn build(&self) -> GameResult<Game<WL>> {
debug!("{:#?}", self); debug!("{:#?}", self);
let game: Game<WL> = let game: Game<WL> =
Game::build(self.length, self.precompute, self.max_steps, WL::default())?; Game::build(self.length, self.precompute, self.max_steps, &self.wordlist)?;
Ok(game) Ok(game)
} }
@ -185,6 +189,15 @@ impl<WL: WordList> GameBuilder<WL> {
debug!("max steps: {:#?}", self.max_steps); debug!("max steps: {:#?}", self.max_steps);
self self
} }
/// Set the wordlist for the builder
///
/// The builder can be used multiple times. Each [`Game`] will have a immutable reference to
/// `wl`.
pub fn wordlist(mut self, wl: WL) -> Self {
self.wordlist = wl;
self
}
} }
impl<WL: WordList> Default for GameBuilder<WL> { impl<WL: WordList> Default for GameBuilder<WL> {

View File

@ -4,10 +4,12 @@ use colored::{ColoredString, Colorize};
use libpt::log::debug; use libpt::log::debug;
use std::fmt::Display; use std::fmt::Display;
pub type Evaluation = Vec<(char, Status)>;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct GuessResponse { pub struct GuessResponse {
guess: Word, guess: Word,
evaluation: Vec<(char, Status)>, evaluation: Evaluation,
step: usize, step: usize,
finish: bool, finish: bool,
win: bool, win: bool,

View File

@ -1 +1,12 @@
use crate::{
error::WResult,
game::{response::*, Game},
wlist::{word::WordData, WordList},
};
pub trait Solver<'wl, WL: WordList>: Clone + Default {
fn build(wordlist: WL) -> WResult<Self>;
fn build_game(&self) -> Game<'wl, WL>;
fn play(game: &mut Game<'wl, WL>) -> Game<'wl, WL>;
fn solve(game: &mut Game<'wl, WL>) -> WResult<WordData>;
}

View File

View File

@ -18,6 +18,9 @@ impl super::WordList for BuiltinWList {
fn wordmap(&self) -> &super::WordMap { fn wordmap(&self) -> &super::WordMap {
&self.words &self.words
} }
fn get_word(&self, word: &Word) -> Option<super::WordData> {
self.words.get(&word)
}
} }
impl Default for BuiltinWList { impl Default for BuiltinWList {

View File

@ -8,15 +8,17 @@ pub mod builtin;
pub mod word; pub mod word;
use word::*; use word::*;
use crate::error::WResult;
pub type AnyWordlist = Box<dyn WordList>; pub type AnyWordlist = Box<dyn WordList>;
pub trait WordList: Clone + std::fmt::Debug + Default { pub trait WordList: Clone + std::fmt::Debug + Default {
fn solutions(&self) -> ManySolutions { fn solutions(&self) -> ManyWordDatas {
let wmap = self.wordmap(); let wmap = self.wordmap();
let threshold = wmap.threshold(); let threshold = wmap.threshold();
wmap.iter().filter(|i| *i.1 > threshold).collect() wmap.iter().filter(|i| *i.1 > threshold).collect()
} }
fn rand_solution(&self) -> Solution { fn rand_solution(&self) -> WordData {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let sol = *self.solutions().iter().choose(&mut rng).unwrap(); let sol = *self.solutions().iter().choose(&mut rng).unwrap();
(sol.0.to_owned(), sol.1.to_owned()) (sol.0.to_owned(), sol.1.to_owned())
@ -39,4 +41,5 @@ pub trait WordList: Clone + std::fmt::Debug + Default {
} }
WordMap::new(hm) WordMap::new(hm)
} }
fn get_word(&self, word: &Word) -> Option<WordData>;
} }

View File

@ -1,7 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{write, Display}; use std::fmt::write;
use std::iter::Sum;
use std::ops::RangeFull;
use libpt::log::debug; use libpt::log::debug;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
@ -11,8 +9,9 @@ pub type Frequency = f64;
// PERF: Hash for String is probably a bottleneck // PERF: Hash for String is probably a bottleneck
pub type Word = String; pub type Word = String;
pub type ManySolutions<'a> = Vec<(&'a Word, &'a Frequency)>; pub type WordData = (Word, Frequency);
pub type Solution = (Word, Frequency); pub type ManyWords<'a> = Vec<&'a Word>;
pub type ManyWordDatas<'a> = Vec<(&'a Word, &'a Frequency)>;
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -35,6 +34,7 @@ impl WordMap {
self.inner.iter() self.inner.iter()
} }
pub fn freq_range(&self) -> std::ops::Range<Frequency> { pub fn freq_range(&self) -> std::ops::Range<Frequency> {
// TODO: calculate this instead of estimating like this
return 0.1e-10..1e-6; return 0.1e-10..1e-6;
let lowest: Frequency = todo!(); let lowest: Frequency = todo!();
let highest: Frequency = todo!(); let highest: Frequency = todo!();
@ -64,6 +64,12 @@ impl WordMap {
pub fn inner(&self) -> &HashMap<Word, Frequency> { pub fn inner(&self) -> &HashMap<Word, Frequency> {
&self.inner &self.inner
} }
pub fn get(&self, word: &Word) -> Option<WordData> {
match self.inner.get(word) {
Some(f) => Some((word.clone(), *f)),
None => None,
}
}
} }
impl std::fmt::Debug for WordMap { impl std::fmt::Debug for WordMap {