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"
[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"]

View File

@ -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<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 _guess: Word;
loop {
response = solver.play(&mut game)?;
response = solver.make_a_move(&mut game)?;
println!("{}. guess: {response}", game.step() - 1);
if response.finished() {

View File

@ -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(),
}
}

View File

@ -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<Self>;
fn guess_for(&self, game: &Game<'wl, WL>) -> Word;
fn play(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse> {
Ok(game.guess(self.guess_for(&game))?)
fn make_a_move(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse> {
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;
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<Game<'wl, WL>>) -> WResult<Summary<'wl, WL>> {
for game in games.iter_mut() {
self.play(game)?;
}
Ok(Summary::from(games))
fn solve(&self, game: &Game<'wl, WL>) -> WResult<Option<WordData>> {
let mut game = game.clone();
Ok(self.play(&mut game)?.solution())
}
fn boxed(self) -> Box<Self> {
Box::new(self)

View File

@ -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::*;