feat(naive): don't reuse bad chars #12
cargo devel CI / cargo CI (push) Failing after 1m53s Details

This commit is contained in:
Christoph J. Scherr 2024-07-31 10:16:51 +02:00
parent 1b354299d4
commit 6f72fda4ff
4 changed files with 58 additions and 38 deletions

View File

@ -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() {

View File

@ -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<T> = std::result::Result<T, Error>;
pub type GameResult<T> = std::result::Result<T, GameError>;
@ -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<WordData>),
#[error("Unknown builtin solver")]
UnknownBuiltinSolver,
}

View File

@ -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<WL>) -> WResult<Word> {
// HACK: hardcoded length
let mut pattern: String = ".".repeat(game.length());
let mut other_chars: Vec<char> = 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 {
// 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<char> = 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());
} else if p.1 == Status::Exists {
other_chars.push(p.0)
}
Status::Exists => other_chars.push(p.0),
Status::None => wrong_chars.push(p.0),
}
}
}
trace!("other chars: {:?}", other_chars);
let mut matches: Vec<WordData> = game.wordlist().get_words_matching(pattern)?;
debug!("other chars: {:?}", other_chars);
debug!("wrong chars: {:?}", wrong_chars);
let mut matches: Vec<WordData> = 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())
}

View File

@ -93,7 +93,7 @@ pub trait WordList: Clone + std::fmt::Debug + Default + Sync + Display {
}
buf
}
fn get_words_matching(&self, pattern: String) -> WResult<Vec<WordData>> {
fn get_words_matching(&self, pattern: &str) -> WResult<Vec<WordData>> {
let pattern = Regex::new(&pattern).map_err(WordlistError::from)?;
let hay = self.raw_wordlist();
let keys = pattern.captures_iter(&hay);