From d9ffa03e88e48c52bca33da549930997ef66ecab Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 26 Mar 2024 00:16:26 +0100 Subject: [PATCH] bench work --- Cargo.toml | 17 ++++++---- src/bench/mod.rs | 32 +++++++++++++++++-- src/bin/bench/cli.rs | 71 +++++++++++++++++++++++++++++++++++++++++ src/bin/solve/simple.rs | 2 +- src/game/response.rs | 4 +++ src/solve/mod.rs | 22 +++++++------ src/wlist/mod.rs | 2 +- 7 files changed, 130 insertions(+), 20 deletions(-) create mode 100644 src/bin/bench/cli.rs diff --git a/Cargo.toml b/Cargo.toml index 71fdfb9..b9e47b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,9 @@ keywords = ["wordle", "benchmark"] default-run = "wordlec" [features] -default = ["game", "bench", "tui", "solve", "builtin_wlist", "serde"] -builtin_wlist = ["dep:serde_json", "serde"] -game = ["builtin_wlist"] +default = ["game", "bench", "tui", "solve", "builtin", "serde"] +builtin = ["dep:serde_json", "serde"] +game = [] solve = ["game"] tui = ["cli"] cli = ["dep:clap"] @@ -36,14 +36,19 @@ thiserror = "1.0.58" [[bin]] name = "wordlec" path = "src/bin/game/cli.rs" -required-features = ["game", "cli"] +required-features = ["game", "cli", "builtin"] [[bin]] name = "wordlet" path = "src/bin/game/tui.rs" -required-features = ["tui", "game"] +required-features = ["tui", "game", "builtin"] [[bin]] name = "wordlesolve" 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"] diff --git a/src/bench/mod.rs b/src/bench/mod.rs index d568c69..1e72ee8 100644 --- a/src/bench/mod.rs +++ b/src/bench/mod.rs @@ -1,5 +1,33 @@ 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; + fn play(&self) -> WResult; + 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)) + } } diff --git a/src/bin/bench/cli.rs b/src/bin/bench/cli.rs new file mode 100644 index 0000000..bec7407 --- /dev/null +++ b/src/bin/bench/cli.rs @@ -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(()) +} diff --git a/src/bin/solve/simple.rs b/src/bin/solve/simple.rs index aa7f996..bec7407 100644 --- a/src/bin/solve/simple.rs +++ b/src/bin/solve/simple.rs @@ -54,7 +54,7 @@ fn main() -> anyhow::Result<()> { let mut response: GuessResponse; let mut _guess: Word; loop { - response = solver.play(&mut game)?; + response = solver.make_a_move(&mut game)?; println!("{}. guess: {response}", game.step() - 1); if response.finished() { diff --git a/src/game/response.rs b/src/game/response.rs index db01b68..dda8c61 100644 --- a/src/game/response.rs +++ b/src/game/response.rs @@ -15,6 +15,8 @@ pub struct GuessResponse { evaluation: Evaluation, finish: bool, solution: WordData, + step: usize, + max_steps: usize, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -40,6 +42,8 @@ impl GuessResponse { evaluation: status, finish, solution: game.solution().clone(), + step: game.step(), + max_steps: game.max_steps(), } } diff --git a/src/solve/mod.rs b/src/solve/mod.rs index b84e9b3..7e9b305 100644 --- a/src/solve/mod.rs +++ b/src/solve/mod.rs @@ -9,32 +9,34 @@ use crate::{ }, }; +#[cfg(feature = "builtin")] pub mod naive; +#[cfg(feature = "builtin")] pub use naive::NaiveSolver; +#[cfg(feature = "builtin")] pub mod stupid; +#[cfg(feature = "builtin")] pub use stupid::StupidSolver; pub trait Solver<'wl, WL: WordList>: Clone + std::fmt::Debug + Sized { fn build(wordlist: &'wl WL) -> WResult; fn guess_for(&self, game: &Game<'wl, WL>) -> Word; - fn play(&self, game: &mut Game<'wl, WL>) -> WResult { - Ok(game.guess(self.guess_for(&game))?) + fn make_a_move(&self, game: &mut Game<'wl, WL>) -> WResult { + Ok(game.guess(self.guess_for(game))?) } - fn solve(&self, game: &mut Game<'wl, WL>) -> WResult> { + fn play(&self, game: &mut Game<'wl, WL>) -> WResult { let mut resp: GuessResponse; loop { - resp = self.play(game)?; + resp = self.make_a_move(game)?; if game.finished() { break; } } - Ok(resp.solution()) + Ok(resp) } - fn play_n(&self, games: &'wl mut Vec>) -> WResult> { - for game in games.iter_mut() { - self.play(game)?; - } - Ok(Summary::from(games)) + fn solve(&self, game: &Game<'wl, WL>) -> WResult> { + let mut game = game.clone(); + Ok(self.play(&mut game)?.solution()) } fn boxed(self) -> Box { Box::new(self) diff --git a/src/wlist/mod.rs b/src/wlist/mod.rs index b2122bb..ec7db95 100644 --- a/src/wlist/mod.rs +++ b/src/wlist/mod.rs @@ -6,7 +6,7 @@ use regex::Regex; use std::collections::HashMap; use std::ops::RangeBounds; -#[cfg(feature = "builtin_wlist")] +#[cfg(feature = "builtin")] pub mod builtin; pub mod word; use word::*;