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"
[features]
default = ["game", "bench", "tui", "solvers", "builtin_wlist", "serde"]
default = ["game", "bench", "tui", "solve", "builtin_wlist", "serde"]
builtin_wlist = ["dep:serde_json", "serde"]
game = ["builtin_wlist"]
solvers = []
solve = []
tui = ["cli"]
cli = ["dep:clap"]
bench = []
@ -41,3 +41,8 @@ required-features = ["game", "cli"]
name = "wordlet"
path = "src/bin/game/tui.rs"
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);
let mut game = game::Game::<BuiltinWList>::builder()
let wl = BuiltinWList::default();
let builder = game::Game::builder()
.length(cli.length)
.max_steps(cli.max_steps)
.precompute(cli.precompute)
.build()?;
.wordlist(wl);
let mut game = builder.build()?;
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;
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>;
#[derive(Debug, Error)]

View File

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

View File

@ -4,10 +4,12 @@ use colored::{ColoredString, Colorize};
use libpt::log::debug;
use std::fmt::Display;
pub type Evaluation = Vec<(char, Status)>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GuessResponse {
guess: Word,
evaluation: Vec<(char, Status)>,
evaluation: Evaluation,
step: usize,
finish: 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 {
&self.words
}
fn get_word(&self, word: &Word) -> Option<super::WordData> {
self.words.get(&word)
}
}
impl Default for BuiltinWList {

View File

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

View File

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