refactor: improve error handling
cargo devel CI / cargo CI (push) Failing after 1m41s Details

This commit is contained in:
Christoph J. Scherr 2024-07-25 13:24:56 +02:00
parent 500e99d8ee
commit 713a661cc5
4 changed files with 59 additions and 10 deletions

View File

@ -8,12 +8,13 @@ use libpt::cli::{repl::Repl, strum};
use libpt::log::*; use libpt::log::*;
use strum::EnumIter; use strum::EnumIter;
use wordle_analyzer::error::Error;
use wordle_analyzer::game::evaluation::Evaluation; use wordle_analyzer::game::evaluation::Evaluation;
use wordle_analyzer::game::response::GuessResponse; use wordle_analyzer::game::response::GuessResponse;
use wordle_analyzer::solve::{BuiltinSolverNames, Solver}; use wordle_analyzer::solve::{BuiltinSolverNames, Solver};
use wordle_analyzer::wlist::builtin::BuiltinWList; use wordle_analyzer::wlist::builtin::BuiltinWList;
use wordle_analyzer::wlist::word::Word; use wordle_analyzer::wlist::word::{Word, WordData};
use wordle_analyzer::wlist::WordList; use wordle_analyzer::wlist::WordList;
use wordle_analyzer::{self, game}; use wordle_analyzer::{self, game};
@ -33,12 +34,22 @@ struct Cli {
#[command(flatten)] #[command(flatten)]
verbose: libpt::cli::args::VerbosityLevel, verbose: libpt::cli::args::VerbosityLevel,
/// which solver to use /// which solver to use
#[arg(short, long, default_value_t = BuiltinSolverNames::default())] #[arg(long, default_value_t = BuiltinSolverNames::default())]
solver: BuiltinSolverNames, solver: BuiltinSolverNames,
/// set if the solver should play a full native game without interaction /// set if the solver should play a full native game without interaction
#[arg(short, long)] #[arg(short, long)]
non_interactive: bool, non_interactive: bool,
// FIXME: line breaks don't work correctly in the cli help
//
/// Solution for the game
///
/// This will only be used when non-interactive is used. You can use this option to see how the
/// selected solver behaves when trying to guess a specific solution, which can help reproduce
/// behavior.
#[arg(short, long)]
solution: Option<Word>,
} }
#[derive(Subcommand, Debug, EnumIter, Clone)] #[derive(Subcommand, Debug, EnumIter, Clone)]
@ -184,10 +195,22 @@ fn wlcommand_handler(_cli: &Cli, cmd: &WlCommand, wl: &impl WordList) -> anyhow:
fn play_native_non_interactive(cli: Cli) -> anyhow::Result<()> { fn play_native_non_interactive(cli: Cli) -> anyhow::Result<()> {
let wl = BuiltinWList::default(); let wl = BuiltinWList::default();
let builder = game::Game::builder(&wl) let mut 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);
if cli.solution.is_some() {
let solw: Word = cli.solution.unwrap();
let sol = wl.get_word(&solw);
if sol.is_none() {
eprintln!("the requested solution \"{solw}\" is not in the wordlist");
return Err(Error::GameError {
source: wordle_analyzer::error::GameError::WordNotInWordlist(solw),
}
.into());
}
builder = builder.solution(sol);
}
let solver = cli.solver.to_solver(&wl); let solver = cli.solver.to_solver(&wl);
let mut game = builder.build()?; let mut game = builder.build()?;

View File

@ -49,7 +49,7 @@ pub enum GameError {
GuessHasWrongLength(usize), GuessHasWrongLength(usize),
#[error("The game is finished but a guess is being made")] #[error("The game is finished but a guess is being made")]
TryingToPlayAFinishedGame, TryingToPlayAFinishedGame,
#[error("Tried to guess a word that is not in the wordlist ({0})")] #[error("Tried to guess or use a word that is not in the wordlist ({0})")]
WordNotInWordlist(Word), WordNotInWordlist(Word),
} }

View File

@ -214,13 +214,14 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
/// # } /// # }
/// ``` /// ```
/// ///
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq)]
pub struct GameBuilder<'wl, WL: WordList> { pub struct GameBuilder<'wl, WL: WordList> {
length: usize, length: usize,
precompute: bool, precompute: bool,
max_steps: usize, max_steps: usize,
wordlist: &'wl WL, wordlist: &'wl WL,
generate_solution: bool, generate_solution: bool,
solution: Option<WordData>,
} }
impl<'wl, WL: WordList> GameBuilder<'wl, WL> { impl<'wl, WL: WordList> GameBuilder<'wl, WL> {
@ -234,19 +235,23 @@ impl<'wl, WL: WordList> GameBuilder<'wl, WL> {
max_steps: super::DEFAULT_MAX_STEPS, max_steps: super::DEFAULT_MAX_STEPS,
wordlist: wl, wordlist: wl,
generate_solution, generate_solution,
solution: None,
} }
} }
/// build a [`Game`] with the stored configuration /// build a [`Game`] with the stored configuration
pub fn build(&'wl self) -> GameResult<Game<'wl, WL>> { pub fn build(&'wl self) -> GameResult<Game<'wl, WL>> {
trace!("{:#?}", self); trace!("{:#?}", self);
let game: Game<WL> = Game::build( let mut game: Game<WL> = Game::build(
self.length, self.length,
self.precompute, self.precompute,
self.max_steps, self.max_steps,
self.wordlist, self.wordlist,
self.generate_solution, self.generate_solution,
)?; )?;
if self.solution.is_some() {
game.set_solution(self.solution.clone())
}
Ok(game) Ok(game)
} }
@ -284,6 +289,22 @@ impl<'wl, WL: WordList> GameBuilder<'wl, WL> {
self.wordlist = wl; self.wordlist = wl;
self self
} }
/// Set the solution for the games built by the builder
///
/// If this is [Some], then the solution generated by
/// [generate_solution](Self::generate_solution) will be overwritten (if it
/// is true).
///
/// If [generate_solution](Self::generate_solution) is false and this method is not used, the
/// game will not have a predetermined solution and will not be able to generate evaluations
/// for guesses, so these will need to be added manually by the user. The intention is that
/// this can be used for use cases where the user plays wordle not within wordle-analyzer but
/// in another program (like their browser). It can also be used to test solvers.
pub fn solution(mut self, solution: Option<WordData>) -> Self {
self.solution = solution;
self
}
} }
impl<'wl, WL: WordList> Display for Game<'wl, WL> { impl<'wl, WL: WordList> Display for Game<'wl, WL> {

View File

@ -21,6 +21,10 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
let mut pattern: String = String::from("....."); let mut pattern: String = String::from(".....");
let mut other_chars: Vec<char> = Vec::new(); let mut other_chars: Vec<char> = Vec::new();
let response = game.last_response(); let response = game.last_response();
trace!(
"guessing best guess for last response: {response:#?}\n{:#?}",
response.map(|a| a.evaluation())
);
if response.is_some() { if response.is_some() {
for (idx, p) in response for (idx, p) in response
.unwrap() .unwrap()
@ -37,10 +41,11 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
} }
} }
trace!("other chars: {:?}", other_chars); trace!("other chars: {:?}", other_chars);
let matches: Vec<WordData> = game let mut matches: Vec<WordData> = game.wordlist().get_words_matching(pattern)?;
.wordlist() if matches.is_empty() {
.get_words_matching(pattern) return Err(SolverError::NoMatches.into());
.expect("the solution does not exist in the wordlist") }
matches = matches
.iter() .iter()
// only words that have not been guessed yet // only words that have not been guessed yet
.filter(|p| !game.made_guesses().contains(&&p.0)) .filter(|p| !game.made_guesses().contains(&&p.0))