feat(naive): start moving to state machine for solver internals
cargo devel CI / cargo CI (push) Has been cancelled Details

This commit is contained in:
Christoph J. Scherr 2024-08-02 15:11:46 +02:00
parent 714aac0d86
commit 3cd20d775e
2 changed files with 142 additions and 35 deletions

View File

@ -3,12 +3,15 @@ use std::collections::HashMap;
use libpt::log::{debug, info, trace};
use crate::error::{SolverError, WResult};
use crate::game::evaluation::Evaluation;
use crate::game::evaluation::{Evaluation, EvaluationUnit};
use crate::wlist::word::{Word, WordData};
use crate::wlist::WordList;
use super::{AnyBuiltinSolver, Solver, Status};
mod states;
use states::*;
#[derive(Debug, Clone)]
pub struct NaiveSolver<'wl, WL> {
wl: &'wl WL,
@ -31,7 +34,7 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
fn guess_for(&self, game: &crate::game::Game<WL>) -> WResult<Word> {
let mut pattern: String = ".".repeat(game.length());
// indexes we tried for that char and the number of occurences
let mut other_chars: HashMap<char, (Vec<usize>, usize)> = HashMap::new();
let mut state: SolverState = SolverState::new();
let responses = game.responses().iter().enumerate();
for (_idx, response) in responses {
let evaluation: &Evaluation = response.evaluation();
@ -40,25 +43,23 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
Status::Matched => {
pattern.replace_range(idx..idx + 1, &p.0.to_string());
other_chars.entry(p.0).or_default();
let v = other_chars.get_mut(&p.0).unwrap();
v.1 += 1;
}
Status::Exists => {
other_chars.entry(p.0).or_default();
let v = other_chars.get_mut(&p.0).unwrap();
v.0.push(idx);
// TODO: count how many times the char occurs
v.1 += 1;
}
Status::None => {
other_chars.entry(p.0).or_default();
state.char_map_mut().entry(p.0).or_default().found_at(idx);
}
Status::Exists => state
.char_map_mut()
.entry(p.0)
.or_default()
.at_least_n_occurences(
response.guess().chars().filter(|c| *c == p.0).count(),
),
Status::None => state
.char_map_mut()
.entry(p.0)
.or_default()
.not_in_solution(),
}
}
}
debug!("other chars: {:?}", other_chars);
// get all words that have the correct chars on the same positions
let mut matches: Vec<WordData> = game.wordlist().get_words_matching(&pattern)?;
@ -69,24 +70,14 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
.iter()
// only words that have not been guessed yet
.filter(|p| !game.made_guesses().contains(&&p.0))
// only words that do contain the chars we know exist
.filter(|p| {
for other in other_chars.iter() {
if p.0.contains(*other.0) {
let mut already_tried: Vec<(_, _)> = Vec::new();
for spot in &other.1 .0 {
already_tried.push((spot, *other.0));
}
if p.0.chars().filter(|c| *c == *other.0).count() > other.1 .1 {
return false; // the char occurs too often in that word
}
for c in p.0.char_indices() {
if c.1 == *other.0 && other.1 .0.contains(&c.0) {
return false;
}
}
} else if other.1 .1 != 0 {
.filter(|solution_candidate| {
for (idx, c) in solution_candidate.0.char_indices() {
let cinfo = state.char_map_mut().entry(c).or_default();
// bad word if it uses a char thats not in the solution
if !cinfo.part_of_solution()
|| cinfo.has_been_tried(idx)
|| cinfo.occurences_of_char_possible(&solution_candidate.0, c)
{
return false;
}
}
@ -101,6 +92,12 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
}
}
impl<'wl, WL: WordList> NaiveSolver<'wl, WL> {
fn get_highest_possible_abs_freq(indexes: &[usize], game: &crate::game::Game<WL>) -> usize {
game.length() - indexes.len()
}
}
impl<'wl, WL: WordList> From<NaiveSolver<'wl, WL>> for AnyBuiltinSolver<'wl, WL> {
fn from(value: NaiveSolver<'wl, WL>) -> Self {
Self::Naive(value)

110
src/solve/naive/states.rs Normal file
View File

@ -0,0 +1,110 @@
use std::collections::{HashMap, HashSet};
use std::ops::{Range, RangeBounds};
use crate::error::WResult;
pub(crate) type CharMap = HashMap<char, CharInfo>;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub(crate) struct CharInfo {
confirmed_indexes: HashSet<usize>,
tried_indexes: HashSet<usize>,
occurences_amount: Range<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SolverState {
char_map: CharMap,
}
impl SolverState {
pub fn new() -> Self {
Self {
char_map: HashMap::new(),
}
}
pub fn char_map(&self) -> &CharMap {
&self.char_map
}
pub fn char_map_mut(&mut self) -> &mut CharMap {
&mut self.char_map
}
}
impl CharInfo {
pub fn new() -> Self {
Self::default()
}
pub fn found_at(&mut self, idx: usize) {
self.tried_indexes.insert(idx);
self.confirmed_indexes.insert(idx);
if self.occurences_amount.start < 1 {
self.occurences_amount.start = 1;
}
}
/// tried to guess a char we know exists at this position, but it was incorrect
pub fn tried_but_failed(&mut self, idx: usize) {
self.tried_indexes.insert(idx);
}
/// char is not in the solution
pub fn not_in_solution(&mut self) {
self.occurences_amount.start = 0;
self.occurences_amount.end = 0;
}
pub fn has_been_tried(&self, idx: usize) -> bool {
self.tried_indexes.contains(&idx)
}
#[must_use]
pub fn part_of_solution(&self) -> bool {
self.occurences_amount.end > 0
}
pub fn at_least_n_occurences(&mut self, n: usize) {
self.occurences_amount.start = n;
}
pub(crate) fn confirmed_indexes(&self) -> &HashSet<usize> {
&self.confirmed_indexes
}
pub(crate) fn confirmed_indexes_mut(&mut self) -> &mut HashSet<usize> {
&mut self.confirmed_indexes
}
pub(crate) fn tried_indexes(&self) -> &HashSet<usize> {
&self.tried_indexes
}
pub(crate) fn tried_indexes_mut(&mut self) -> &mut HashSet<usize> {
&mut self.tried_indexes
}
pub(crate) fn occurences_amount(&self) -> &Range<usize> {
&self.occurences_amount
}
pub(crate) fn occurences_amount_mut(&mut self) -> &mut Range<usize> {
&mut self.occurences_amount
}
pub(crate) fn occurences_of_char_possible(
&self,
solution_candidate: &str,
character: char,
) -> bool {
self.occurences_amount.contains(
&solution_candidate
.chars()
.filter(|c| *c == character)
.count(),
)
}
}