From 6f72fda4ff8122e2b2248ffc076b795e5e127e48 Mon Sep 17 00:00:00 2001 From: "Christoph J. Scherr" Date: Wed, 31 Jul 2024 10:16:51 +0200 Subject: [PATCH] feat(naive): don't reuse bad chars #12 --- src/bin/solve/simple.rs | 10 +++--- src/error.rs | 6 ++-- src/solve/naive/mod.rs | 78 ++++++++++++++++++++++++++--------------- src/wlist/mod.rs | 2 +- 4 files changed, 58 insertions(+), 38 deletions(-) diff --git a/src/bin/solve/simple.rs b/src/bin/solve/simple.rs index dc6cbd4..2f4c060 100644 --- a/src/bin/solve/simple.rs +++ b/src/bin/solve/simple.rs @@ -172,7 +172,7 @@ fn help_guess_interactive(cli: Cli) -> anyhow::Result<()> { eprintln!("{}", style(best_guess.unwrap_err()).red().bold()); continue; } - debug!("game state: {game:?}"); + trace!("game state: {game:?}"); println!("best guess: {}", best_guess.unwrap()); } ReplCommand::Guess { @@ -188,7 +188,7 @@ fn help_guess_interactive(cli: Cli) -> anyhow::Result<()> { continue; } println!("{}", guess.unwrap()); - debug!("game state: {game:#?}"); + trace!("game state: {game:#?}"); } ReplCommand::New => game = builder.build()?, ReplCommand::Undo { n } => game.undo(n)?, @@ -218,7 +218,7 @@ fn play_native_non_interactive(cli: Cli) -> anyhow::Result<()> { "eng" => BuiltinWList::english(cli.length), _ => BuiltinWList::load(&cli.wordlist, cli.length)?, }; - debug!("wordlist: {wl}"); + trace!("wordlist: {wl}"); let mut builder = game::Game::builder(&wl) .length(cli.length) .max_steps(cli.max_steps) @@ -238,13 +238,13 @@ fn play_native_non_interactive(cli: Cli) -> anyhow::Result<()> { let solver = cli.solver.to_solver(&wl); let mut game = builder.build()?; - debug!("{game:#?}"); + trace!("{game:#?}"); let mut response: GuessResponse; let mut _guess: Word; loop { response = solver.make_a_move(&mut game)?; - debug!("game state: {game:#?}"); + trace!("game state: {game:#?}"); println!("{}. guess: {response}", game.step() - 1); if response.finished() { diff --git a/src/error.rs b/src/error.rs index a0ddbc8..ea0cb5d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,7 @@ use thiserror::Error; use crate::bench::report::Report; -use crate::wlist::word::Word; +use crate::wlist::word::{Word, WordData}; pub type WResult = std::result::Result; pub type GameResult = std::result::Result; @@ -62,8 +62,8 @@ pub enum BenchError { #[derive(Debug, Clone, Error)] pub enum SolverError { - #[error("Wordlist has no matches for the gamestate")] - NoMatches, + #[error("Wordlist has no matches for the gamestate (solution: {0:?})")] + NoMatches(Option), #[error("Unknown builtin solver")] UnknownBuiltinSolver, } diff --git a/src/solve/naive/mod.rs b/src/solve/naive/mod.rs index 960bb3c..09fc63c 100644 --- a/src/solve/naive/mod.rs +++ b/src/solve/naive/mod.rs @@ -1,6 +1,10 @@ -use libpt::log::{info, trace}; +use core::panic; +use std::collections::HashMap; + +use libpt::log::{debug, info, trace}; use crate::error::{SolverError, WResult}; +use crate::game::response; use crate::wlist::word::{Word, WordData}; use crate::wlist::WordList; @@ -26,51 +30,67 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> { /// * Discard all words that don't have the chars that we know from the last guess are in the /// word, but don't know the position of. fn guess_for(&self, game: &crate::game::Game) -> WResult { - // HACK: hardcoded length let mut pattern: String = ".".repeat(game.length()); let mut other_chars: Vec = Vec::new(); - let response = game.last_response(); - trace!( - "guessing best guess for last response: {response:#?}\n{:#?}", - response.map(|a| a.evaluation()) - ); - if response.is_some() { - for (idx, p) in response - .unwrap() - .evaluation() - .clone() - .into_iter() - .enumerate() - { - if p.1 == Status::Matched { - pattern.replace_range(idx..idx + 1, &p.0.to_string()); - } else if p.1 == Status::Exists { - other_chars.push(p.0) + // a hash map telling how many of the characters may be in a correct word (+1) + // if the value for a char is 2 => it may be in the solution 1 time + // if the value for a char is 1 => it may not be in the solution + let mut wrong_chars: Vec = Vec::new(); + let responses = game.responses().iter().enumerate(); + for (_idx, response) in responses { + for (idx, p) in response.evaluation().clone().into_iter().enumerate() { + match p.1 { + Status::Matched => { + pattern.replace_range(idx..idx + 1, &p.0.to_string()); + } + Status::Exists => other_chars.push(p.0), + Status::None => wrong_chars.push(p.0), } } } - trace!("other chars: {:?}", other_chars); - let mut matches: Vec = game.wordlist().get_words_matching(pattern)?; + debug!("other chars: {:?}", other_chars); + debug!("wrong chars: {:?}", wrong_chars); + let mut matches: Vec = game.wordlist().get_words_matching(&pattern)?; if matches.is_empty() { - return Err(SolverError::NoMatches.into()); + return Err(SolverError::NoMatches(game.solution().cloned()).into()); } matches = matches .iter() // only words that have not been guessed yet .filter(|p| !game.made_guesses().contains(&&p.0)) - // only words that contain the letters we found earlier (that were not matched) + // only words that do contain the chars we know exist .filter(|p| { - // TODO: don't repeat unmatched contained chars on the same position twice #2 - let mut fits = true; - for c in other_chars.iter() { - fits &= p.0.contains(*c); + for other in &other_chars { + if p.0.contains(*other) { + // TODO: account for chars that occur multiple times + continue; + } else { + return false; + } } - fits + true + }) + // only words that do not contain the letters we know are wrong + .filter(|p| { + for wrong in &wrong_chars { + if p.0.contains(*wrong) { + let mut tmp = 0; + let in_other = other_chars.iter().filter(|v| **v == *wrong).count() + + pattern.chars().filter(|v| *v == *wrong).count(); + // TODO: account for chars that occur multiple times + if in_other > tmp { + tmp += 1; + continue; + } + return false; + } + } + true }) .map(|v| v.to_owned()) .collect(); if matches.is_empty() { - return Err(SolverError::NoMatches.into()); + return Err(SolverError::NoMatches(game.solution().cloned()).into()); } Ok(matches[0].0.to_owned()) } diff --git a/src/wlist/mod.rs b/src/wlist/mod.rs index e529758..57b2f8c 100644 --- a/src/wlist/mod.rs +++ b/src/wlist/mod.rs @@ -93,7 +93,7 @@ pub trait WordList: Clone + std::fmt::Debug + Default + Sync + Display { } buf } - fn get_words_matching(&self, pattern: String) -> WResult> { + fn get_words_matching(&self, pattern: &str) -> WResult> { let pattern = Regex::new(&pattern).map_err(WordlistError::from)?; let hay = self.raw_wordlist(); let keys = pattern.captures_iter(&hay);