bench work

This commit is contained in:
Christoph J. Scherr 2024-03-26 00:16:26 +01:00
parent 98cb455a3c
commit d9ffa03e88
Signed by: PlexSheep
GPG Key ID: 7CDD0B14851A08EF
7 changed files with 130 additions and 20 deletions

View File

@ -13,9 +13,9 @@ keywords = ["wordle", "benchmark"]
default-run = "wordlec" default-run = "wordlec"
[features] [features]
default = ["game", "bench", "tui", "solve", "builtin_wlist", "serde"] default = ["game", "bench", "tui", "solve", "builtin", "serde"]
builtin_wlist = ["dep:serde_json", "serde"] builtin = ["dep:serde_json", "serde"]
game = ["builtin_wlist"] game = []
solve = ["game"] solve = ["game"]
tui = ["cli"] tui = ["cli"]
cli = ["dep:clap"] cli = ["dep:clap"]
@ -36,14 +36,19 @@ thiserror = "1.0.58"
[[bin]] [[bin]]
name = "wordlec" name = "wordlec"
path = "src/bin/game/cli.rs" path = "src/bin/game/cli.rs"
required-features = ["game", "cli"] required-features = ["game", "cli", "builtin"]
[[bin]] [[bin]]
name = "wordlet" name = "wordlet"
path = "src/bin/game/tui.rs" path = "src/bin/game/tui.rs"
required-features = ["tui", "game"] required-features = ["tui", "game", "builtin"]
[[bin]] [[bin]]
name = "wordlesolve" name = "wordlesolve"
path = "src/bin/solve/simple.rs" path = "src/bin/solve/simple.rs"
required-features = ["solve", "cli"] required-features = ["solve", "cli", "builtin"]
[[bin]]
name = "wordlebench"
path = "src/bin/bench/cli.rs"
required-features = ["solve", "cli", "bench", "builtin"]

View File

@ -1,5 +1,33 @@
use std::fmt::Debug; use std::fmt::Debug;
pub trait Benchmark: Clone + Sized + Debug { use libpt::log::debug;
use crate::error::WResult;
use crate::wlist::WordList;
pub trait Benchmark<'wl, WL: WordList>: Clone + Sized + Debug {
fn build(wordlist: &'wl WL) -> WResult<Self>;
fn play(&self) -> WResult<usize>;
fn bench(&self, n: usize) -> WResult<(usize, f64)> {
// PERF: it would be better to make this multithreaded
let mut absolute: usize = 0;
let part = n / 20;
let start = std::time::Instant::now();
for i in 0..n {
// TODO: limit execution time for the following, perhaps async
absolute += self.play()?;
if i % part == part - 1 {
debug!(
"{} / {n}\t ratio: {} \t elapsed: {:?}",
i + 1,
absolute as f64 / n as f64,
start.elapsed()
);
}
}
Ok((absolute, absolute as f64 / n as f64))
}
} }

71
src/bin/bench/cli.rs Normal file
View File

@ -0,0 +1,71 @@
#![warn(clippy::all)]
// #![warn(missing_docs)]
#![warn(missing_debug_implementations)]
use clap::Parser;
use libpt::log::*;
use wordle_analyzer::game::response::GuessResponse;
use wordle_analyzer::solve::{BuiltinSolverNames, 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 = BuiltinSolverNames::default())]
solver: BuiltinSolverNames,
}
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 = cli.solver.to_solver(&wl);
let mut game = builder.build()?;
debug!("{game:#?}");
let mut response: GuessResponse;
let mut _guess: Word;
loop {
response = solver.make_a_move(&mut game)?;
println!("{}. guess: {response}", game.step() - 1);
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

@ -54,7 +54,7 @@ fn main() -> anyhow::Result<()> {
let mut response: GuessResponse; let mut response: GuessResponse;
let mut _guess: Word; let mut _guess: Word;
loop { loop {
response = solver.play(&mut game)?; response = solver.make_a_move(&mut game)?;
println!("{}. guess: {response}", game.step() - 1); println!("{}. guess: {response}", game.step() - 1);
if response.finished() { if response.finished() {

View File

@ -15,6 +15,8 @@ pub struct GuessResponse {
evaluation: Evaluation, evaluation: Evaluation,
finish: bool, finish: bool,
solution: WordData, solution: WordData,
step: usize,
max_steps: usize,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@ -40,6 +42,8 @@ impl GuessResponse {
evaluation: status, evaluation: status,
finish, finish,
solution: game.solution().clone(), solution: game.solution().clone(),
step: game.step(),
max_steps: game.max_steps(),
} }
} }

View File

@ -9,32 +9,34 @@ use crate::{
}, },
}; };
#[cfg(feature = "builtin")]
pub mod naive; pub mod naive;
#[cfg(feature = "builtin")]
pub use naive::NaiveSolver; pub use naive::NaiveSolver;
#[cfg(feature = "builtin")]
pub mod stupid; pub mod stupid;
#[cfg(feature = "builtin")]
pub use stupid::StupidSolver; pub use stupid::StupidSolver;
pub trait Solver<'wl, WL: WordList>: Clone + std::fmt::Debug + Sized { pub trait Solver<'wl, WL: WordList>: Clone + std::fmt::Debug + Sized {
fn build(wordlist: &'wl WL) -> WResult<Self>; fn build(wordlist: &'wl WL) -> WResult<Self>;
fn guess_for(&self, game: &Game<'wl, WL>) -> Word; fn guess_for(&self, game: &Game<'wl, WL>) -> Word;
fn play(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse> { fn make_a_move(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse> {
Ok(game.guess(self.guess_for(&game))?) Ok(game.guess(self.guess_for(game))?)
} }
fn solve(&self, game: &mut Game<'wl, WL>) -> WResult<Option<WordData>> { fn play(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse> {
let mut resp: GuessResponse; let mut resp: GuessResponse;
loop { loop {
resp = self.play(game)?; resp = self.make_a_move(game)?;
if game.finished() { if game.finished() {
break; break;
} }
} }
Ok(resp.solution()) Ok(resp)
} }
fn play_n(&self, games: &'wl mut Vec<Game<'wl, WL>>) -> WResult<Summary<'wl, WL>> { fn solve(&self, game: &Game<'wl, WL>) -> WResult<Option<WordData>> {
for game in games.iter_mut() { let mut game = game.clone();
self.play(game)?; Ok(self.play(&mut game)?.solution())
}
Ok(Summary::from(games))
} }
fn boxed(self) -> Box<Self> { fn boxed(self) -> Box<Self> {
Box::new(self) Box::new(self)

View File

@ -6,7 +6,7 @@ use regex::Regex;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::RangeBounds; use std::ops::RangeBounds;
#[cfg(feature = "builtin_wlist")] #[cfg(feature = "builtin")]
pub mod builtin; pub mod builtin;
pub mod word; pub mod word;
use word::*; use word::*;