generated from PlexSheep/rs-base
feat(naive): start moving to state machine for solver internals
cargo devel CI / cargo CI (push) Has been cancelled
Details
cargo devel CI / cargo CI (push) Has been cancelled
Details
This commit is contained in:
parent
714aac0d86
commit
3cd20d775e
|
@ -3,12 +3,15 @@ use std::collections::HashMap;
|
||||||
use libpt::log::{debug, info, trace};
|
use libpt::log::{debug, info, trace};
|
||||||
|
|
||||||
use crate::error::{SolverError, WResult};
|
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::word::{Word, WordData};
|
||||||
use crate::wlist::WordList;
|
use crate::wlist::WordList;
|
||||||
|
|
||||||
use super::{AnyBuiltinSolver, Solver, Status};
|
use super::{AnyBuiltinSolver, Solver, Status};
|
||||||
|
|
||||||
|
mod states;
|
||||||
|
use states::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NaiveSolver<'wl, WL> {
|
pub struct NaiveSolver<'wl, WL> {
|
||||||
wl: &'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> {
|
fn guess_for(&self, game: &crate::game::Game<WL>) -> WResult<Word> {
|
||||||
let mut pattern: String = ".".repeat(game.length());
|
let mut pattern: String = ".".repeat(game.length());
|
||||||
// indexes we tried for that char and the number of occurences
|
// 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();
|
let responses = game.responses().iter().enumerate();
|
||||||
for (_idx, response) in responses {
|
for (_idx, response) in responses {
|
||||||
let evaluation: &Evaluation = response.evaluation();
|
let evaluation: &Evaluation = response.evaluation();
|
||||||
|
@ -40,25 +43,23 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
|
||||||
Status::Matched => {
|
Status::Matched => {
|
||||||
pattern.replace_range(idx..idx + 1, &p.0.to_string());
|
pattern.replace_range(idx..idx + 1, &p.0.to_string());
|
||||||
|
|
||||||
other_chars.entry(p.0).or_default();
|
state.char_map_mut().entry(p.0).or_default().found_at(idx);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
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
|
// get all words that have the correct chars on the same positions
|
||||||
let mut matches: Vec<WordData> = game.wordlist().get_words_matching(&pattern)?;
|
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()
|
.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))
|
||||||
// only words that do contain the chars we know exist
|
.filter(|solution_candidate| {
|
||||||
.filter(|p| {
|
for (idx, c) in solution_candidate.0.char_indices() {
|
||||||
for other in other_chars.iter() {
|
let cinfo = state.char_map_mut().entry(c).or_default();
|
||||||
if p.0.contains(*other.0) {
|
// bad word if it uses a char thats not in the solution
|
||||||
let mut already_tried: Vec<(_, _)> = Vec::new();
|
if !cinfo.part_of_solution()
|
||||||
for spot in &other.1 .0 {
|
|| cinfo.has_been_tried(idx)
|
||||||
already_tried.push((spot, *other.0));
|
|| cinfo.occurences_of_char_possible(&solution_candidate.0, c)
|
||||||
}
|
{
|
||||||
|
|
||||||
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 {
|
|
||||||
return false;
|
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> {
|
impl<'wl, WL: WordList> From<NaiveSolver<'wl, WL>> for AnyBuiltinSolver<'wl, WL> {
|
||||||
fn from(value: NaiveSolver<'wl, WL>) -> Self {
|
fn from(value: NaiveSolver<'wl, WL>) -> Self {
|
||||||
Self::Naive(value)
|
Self::Naive(value)
|
||||||
|
|
|
@ -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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue