too much generics, idk

This commit is contained in:
Christoph J. Scherr 2024-03-22 14:12:49 +01:00
parent 4367e7fc58
commit ce9686f7bf
Signed by: cscherrNT
GPG Key ID: 8E2B45BC51A27EA7
13 changed files with 237 additions and 60 deletions

View File

@ -16,7 +16,7 @@ default-run = "wordlec"
default = ["game", "bench", "tui", "solve", "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"]
solve = [] solve = ["game"]
tui = ["cli"] tui = ["cli"]
cli = ["dep:clap"] cli = ["dep:clap"]
bench = [] bench = []
@ -45,4 +45,4 @@ required-features = ["tui", "game"]
[[bin]] [[bin]]
name = "wordlesolve" name = "wordlesolve"
path = "src/bin/solve/simple.rs" path = "src/bin/solve/simple.rs"
required-features = ["game", "solve", "cli"] required-features = ["solve", "cli"]

View File

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

View File

@ -1,3 +1,78 @@
fn main() { #![warn(clippy::all)]
unimplemented!(); // #![warn(missing_docs)]
#![warn(missing_debug_implementations)]
use std::io::Write;
use clap::Parser;
use libpt::log::*;
use wordle_analyzer::error::GameError;
use wordle_analyzer::game::response::GuessResponse;
use wordle_analyzer::game::Game;
use wordle_analyzer::solve::{stupid, BuiltinSolvers, Solver};
use wordle_analyzer::wlist::builtin::BuiltinWList;
use wordle_analyzer::wlist::word::Word;
use wordle_analyzer::{self, game};
#[derive(Parser, Clone, Debug)]
#[command(version, about, long_about, author)]
struct Cli {
/// precompute all possibilities for better performance at runtime
#[arg(short, long)]
precompute: bool,
/// how long should the word be?
#[arg(short, long, default_value_t = wordle_analyzer::DEFAULT_WORD_LENGTH)]
length: usize,
/// how many times can we guess?
#[arg(short, long, default_value_t = wordle_analyzer::DEFAULT_MAX_STEPS)]
max_steps: usize,
/// more verbose logs
#[arg(short, long)]
verbose: bool,
/// which solver to use
#[arg(short, long, default_value_t = BuiltinSolvers::default())]
solver: BuiltinSolvers,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
if cli.verbose {
Logger::build_mini(Some(Level::TRACE))?;
} else {
Logger::build_mini(Some(Level::INFO))?;
}
debug!("dumping CLI: {:#?}", cli);
let wl = BuiltinWList::default();
let builder = game::Game::builder(&wl)
.length(cli.length)
.max_steps(cli.max_steps)
.precompute(cli.precompute);
let solver = match cli.solver {
BuiltinSolvers::Naive => {
stupid::StupidSolver::build(&wl)?
},
_ => todo!()
};
let mut game = builder.build()?;
debug!("{game:#?}");
let mut response: GuessResponse;
let mut guess: Word;
loop {
response = solver.play(&mut game)?;
println!("{response}");
if response.finished() {
break;
}
}
if response.won() {
println!("You win! You took {} guesses.", game.step() - 1);
} else {
println!("You lose! The solution was {:?}.", game.solution());
}
Ok(())
} }

View File

@ -15,6 +15,9 @@ pub enum Error {
#[from] #[from]
source: anyhow::Error, source: anyhow::Error,
}, },
// for `FromStr` of `BuiltinSolver`
#[error("Unknown builtin solver")]
UnknownBuiltinSolver
} }
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]

View File

@ -7,6 +7,8 @@ use libpt::log::debug;
pub mod response; pub mod response;
use response::GuessResponse; use response::GuessResponse;
pub mod summary;
use self::response::Status; use self::response::Status;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -26,8 +28,8 @@ where
impl<'wl, WL: WordList> Game<'wl, WL> { impl<'wl, WL: WordList> Game<'wl, WL> {
/// get a new [`GameBuilder`] /// get a new [`GameBuilder`]
pub fn builder() -> GameBuilder<WL> { pub fn builder(wl: &'wl WL) -> GameBuilder<'wl, WL> {
GameBuilder::default() GameBuilder::new(wl)
} }
/// Create a [Game] of wordle /// Create a [Game] of wordle
/// ///
@ -97,7 +99,7 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
evaluation.push((c, status)); evaluation.push((c, status));
} }
let mut response = GuessResponse::new(guess, evaluation, self.step, self.max_steps); let mut response = GuessResponse::new(guess, evaluation, &self);
self.finished = response.finished(); self.finished = response.finished();
Ok(response) Ok(response)
} }
@ -113,6 +115,14 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
pub fn step(&self) -> usize { pub fn step(&self) -> usize {
self.step self.step
} }
pub fn finished(&self) -> bool {
self.finished
}
pub fn max_steps(&self) -> usize {
self.max_steps
}
} }
/// Build and Configure a [`Game`] /// Build and Configure a [`Game`]
@ -126,9 +136,11 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
/// ///
/// ``` /// ```
/// # use wordle_analyzer::game::*; /// # use wordle_analyzer::game::*;
/// # use wordle_analyzer::wlist::builtin::BuiltinWList;
/// # use anyhow::Result; /// # use anyhow::Result;
/// # fn main() -> Result<()> { /// # fn main() -> Result<()> {
/// let game: Game = GameBuilder::default() /// let wl = BuiltinWList::default();
/// let game: Game<_> = GameBuilder::new(&wl)
/// .build()?; /// .build()?;
/// # Ok(()) /// # Ok(())
/// # } /// # }
@ -136,9 +148,11 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
/// ///
/// ``` /// ```
/// # use wordle_analyzer::game::*; /// # use wordle_analyzer::game::*;
/// # use wordle_analyzer::wlist::builtin::BuiltinWList;
/// # use anyhow::Result; /// # use anyhow::Result;
/// # fn main() -> Result<()> { /// # fn main() -> Result<()> {
/// let game: Game = Game::builder() /// let wl = BuiltinWList::default();
/// let game: Game<_> = Game::builder(&wl)
/// .length(5) /// .length(5)
/// .precompute(false) /// .precompute(false)
/// .max_steps(6) /// .max_steps(6)
@ -148,19 +162,31 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
/// ``` /// ```
/// ///
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GameBuilder<WL: WordList> { pub struct GameBuilder<'wl, WL: WordList> {
length: usize, length: usize,
precompute: bool, precompute: bool,
max_steps: usize, max_steps: usize,
wordlist: WL, wordlist: &'wl WL,
} }
impl<WL: WordList> GameBuilder<WL> { impl<'wl, WL: WordList> GameBuilder<'wl, WL> {
/// make a new [GameBuilder]
///
/// We need a [WordList], so provide one here.
pub fn new(wl: &'wl WL) -> Self {
Self {
length: super::DEFAULT_WORD_LENGTH,
precompute: false,
max_steps: super::DEFAULT_MAX_STEPS,
wordlist: 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, WL>> {
debug!("{:#?}", self); debug!("{:#?}", self);
let game: Game<WL> = let game: Game<WL> =
Game::build(self.length, self.precompute, self.max_steps, &self.wordlist)?; Game::build(self.length, self.precompute, self.max_steps, self.wordlist)?;
Ok(game) Ok(game)
} }
@ -194,19 +220,8 @@ impl<WL: WordList> GameBuilder<WL> {
/// ///
/// The builder can be used multiple times. Each [`Game`] will have a immutable reference to /// The builder can be used multiple times. Each [`Game`] will have a immutable reference to
/// `wl`. /// `wl`.
pub fn wordlist(mut self, wl: WL) -> Self { pub fn wordlist(mut self, wl: &'wl WL) -> Self {
self.wordlist = wl; self.wordlist = wl;
self self
} }
} }
impl<WL: WordList> Default for GameBuilder<WL> {
fn default() -> Self {
Self {
length: super::DEFAULT_WORD_LENGTH,
precompute: false,
max_steps: super::DEFAULT_MAX_STEPS,
wordlist: WL::default(),
}
}
}

View File

@ -1,18 +1,20 @@
use crate::wlist::word::Word; use crate::wlist::word::{Word, WordData};
use crate::wlist::WordList;
use anyhow::Ok; use anyhow::Ok;
use colored::{ColoredString, Colorize}; use colored::{ColoredString, Colorize};
use libpt::log::debug; use libpt::log::debug;
use std::fmt::Display; use std::fmt::Display;
use super::Game;
pub type Evaluation = Vec<(char, Status)>; pub type Evaluation = Vec<(char, Status)>;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq)]
pub struct GuessResponse { pub struct GuessResponse {
guess: Word, guess: Word,
evaluation: Evaluation, evaluation: Evaluation,
step: usize,
finish: bool, finish: bool,
win: bool, solution: WordData,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@ -23,29 +25,21 @@ pub enum Status {
} }
impl GuessResponse { impl GuessResponse {
pub(crate) fn new( pub(crate) fn new<WL: WordList>(guess: Word, status: Vec<(char, Status)>, game: &Game<WL>) -> Self {
guess: Word, let finish: bool = if game.step() > game.max_steps() {
status: Vec<(char, Status)>,
step: usize,
max_step: usize,
) -> Self {
let mut win = false;
let mut finish: bool = if step > max_step {
true true
} else { } else {
let mut matched = true; let mut matched = true;
for p in &status { for p in &status {
matched &= p.1 == Status::Matched; matched &= p.1 == Status::Matched;
} }
win = matched; matched
win
}; };
Self { Self {
guess, guess,
evaluation: status, evaluation: status,
step,
finish, finish,
win, solution: game.solution().clone(),
} }
} }
@ -54,7 +48,15 @@ impl GuessResponse {
} }
pub fn won(&self) -> bool { pub fn won(&self) -> bool {
self.win self.guess == self.solution.0
}
pub fn solution(&self) -> Option<WordData> {
if self.won() {
Some(self.solution.clone())
} else {
None
}
} }
} }

19
src/game/summary.rs Normal file
View File

@ -0,0 +1,19 @@
use crate::wlist::WordList;
use super::Game;
pub struct Summary<'wl, WL: WordList> {
data: &'wl Vec<Game<'wl, WL>>,
}
impl<'wl, WL: WordList> From<&'wl Vec<Game<'wl, WL>>> for Summary<'wl, WL> {
fn from(value: &'wl Vec<Game<'wl, WL>>) -> Self {
Summary { data: value }
}
}
impl<'wl, WL: WordList> From<&'wl mut Vec<Game<'wl, WL>>> for Summary<'wl, WL> {
fn from(value: &'wl mut Vec<Game<'wl, WL>>) -> Self {
Summary { data: value }
}
}

View File

@ -12,6 +12,6 @@ pub mod bench;
pub mod error; pub mod error;
#[cfg(feature = "game")] #[cfg(feature = "game")]
pub mod game; pub mod game;
#[cfg(feature = "solvers")] #[cfg(feature = "solve")]
pub mod solvers; pub mod solve;
pub mod wlist; pub mod wlist;

56
src/solve/mod.rs Normal file
View File

@ -0,0 +1,56 @@
use std::{fmt::Display, str::FromStr};
use crate::{
error::{Error, WResult},
game::{response::*, summary::Summary, Game},
wlist::{word::WordData, WordList},
};
pub mod naive;
pub mod stupid;
pub trait Solver<'wl, WL: WordList>: Clone + std::fmt::Debug {
fn build(wordlist: &'wl WL) -> WResult<Self>;
fn play(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse>;
fn solve(&self, game: &mut Game<'wl, WL>) -> WResult<Option<WordData>> {
let mut resp: GuessResponse;
loop {
resp = self.play(game)?;
if game.finished() {
break;
}
}
Ok(resp.solution())
}
fn play_n(&self, games: &'wl mut Vec<Game<'wl, WL>>) -> WResult<Summary<'wl, WL>> {
for game in games.iter_mut() {
self.play(game)?;
}
Ok(Summary::from(games))
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BuiltinSolvers {
#[default]
Naive,
Stupid,
}
impl FromStr for BuiltinSolvers {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"naive" => Ok(Self::Naive),
"stupid" => Ok(Self::Stupid),
_ => Err(Self::Err::UnknownBuiltinSolver),
}
}
}
impl Display for BuiltinSolvers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}

20
src/solve/stupid/mod.rs Normal file
View File

@ -0,0 +1,20 @@
use crate::wlist::WordList;
use super::Solver;
#[derive(Debug, Clone)]
pub struct StupidSolver<'wl, WL> {
wl: &'wl WL,
}
impl<'wl, WL: WordList> Solver<'wl, WL> for StupidSolver<'wl, WL> {
fn build(wordlist: &'wl WL) -> crate::error::WResult<Self> {
Ok(Self { wl: wordlist })
}
fn play(
&self,
game: &mut crate::game::Game<'wl, WL>,
) -> crate::error::WResult<super::GuessResponse> {
todo!()
}
}

View File

@ -1,12 +0,0 @@
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>;
}