use std::collections::HashMap; use std::convert::TryInto; use crate::guess::*; pub trait Constraints { fn push(&mut self, result: GuessResult); fn check(&self, word: Word) -> bool; } #[derive(Clone)] pub struct LetterCount { letter_minimums: HashMap, letter_maximums: HashMap, empty: bool, } impl LetterCount { pub fn new() -> Self { LetterCount { letter_minimums: HashMap::new(), letter_maximums: HashMap::new(), empty: true, } } } impl Constraints for LetterCount { fn push(&mut self, result: GuessResult) { self.empty = false; let mut letter_matches = HashMap::new(); for a in 0..N { letter_matches .entry(result.word[a]) .or_insert(Vec::new()) .push(result.matches[a]); } for (letter, matches) in letter_matches { let nowheres = matches.iter().filter(|&m| *m == LetterMatch::NOWHERE).count(); let somewheres = matches.len() - nowheres; self.letter_minimums.insert( letter, std::cmp::max( *self.letter_minimums.get(&letter).unwrap_or(&0), somewheres, ), ); if nowheres > 0 { self.letter_maximums.insert( letter, std::cmp::min( *self.letter_maximums.get(&letter).unwrap_or(&N), somewheres, ), ); } } } fn check(&self, word: Word) -> bool { if self.empty { return true; } let mut letter_counts = HashMap::new(); for a in 0..N { *letter_counts.entry(word[a]).or_insert(0) += 1; } for (letter, minimum) in &self.letter_minimums { let letter_count = letter_counts.get(&letter).unwrap_or(&0); if letter_count < minimum { return false; } } for (letter, maximum) in &self.letter_maximums { let letter_count = letter_counts.get(&letter).unwrap_or(&0); if letter_count > maximum { return false; } } true } } #[derive(Clone)] pub struct LetterPosition { heres: [Option; N], elsewheres: [Vec; N], empty: bool, } impl LetterPosition { pub fn new() -> Self { Self { heres: [None; N], elsewheres: vec![Vec::new(); N].try_into().expect("failed to initialize [Vec; N]"), empty: true, } } } impl Constraints for LetterPosition { fn push(&mut self, result: GuessResult) { self.empty = false; for a in 0..N { match result.matches[a] { LetterMatch::HERE => self.heres[a] = Some(result.word[a]), LetterMatch::ELSEWHERE => { self.elsewheres[a].push(result.word[a]); for b in 0..N { if a != b && result.word[b] == result.word[a] && result.matches[b] == LetterMatch::NOWHERE { self.elsewheres[b].push(result.word[a]); } } }, LetterMatch::NOWHERE => (), } } } fn check(&self, word: Word) -> bool { if self.empty { return true; } for a in 0..N { if let Some(here) = self.heres[a] { if word[a] != here { return false; } } if self.elsewheres[a].contains(&word[a]) { return false; } } true } } #[derive(Clone)] pub struct Total { count: LetterCount, position: LetterPosition, } impl Total { pub fn new() -> Self { Self { count: LetterCount::new(), position: LetterPosition::new(), } } } impl Constraints for Total { fn push(&mut self, result: GuessResult) { self.count.push(result); self.position.push(result); } fn check(&self, word: Word) -> bool { self.count.check(word) && self.position.check(word) } } #[cfg(test)] mod tests { use super::*; #[test] fn it_constrains_nowheres() { let mut constraints = Total::new(); constraints.push(GuessResult { word: word!("loose"), matches: [LetterMatch::NOWHERE; 5], }); assert_eq!(false, constraints.check(word!("blink"))); // no L assert_eq!(false, constraints.check(word!("ghost"))); // no O assert_eq!(true, constraints.check(word!("chain"))); } #[test] fn it_constraints_elsewheres() { let mut constraints = Total::new(); constraints.push(GuessResult { word: word!("loose"), matches: [LetterMatch::NOWHERE, LetterMatch::ELSEWHERE, LetterMatch::NOWHERE, LetterMatch::NOWHERE, LetterMatch::NOWHERE], }); assert_eq!(false, constraints.check(word!("brood"))); // too many O's assert_eq!(false, constraints.check(word!("going"))); // O can't go here assert_eq!(false, constraints.check(word!("abort"))); // O can't go here assert_eq!(true, constraints.check(word!("favor"))); } #[test] fn it_constrains_heres() { let mut constraints = Total::new(); constraints.push(GuessResult { word: word!("loose"), matches: [LetterMatch::NOWHERE, LetterMatch::HERE, LetterMatch::HERE, LetterMatch::HERE, LetterMatch::HERE], }); assert_eq!(false, constraints.check(word!("house"))); // no U here assert_eq!(false, constraints.check(word!("loose"))); // no L here assert_eq!(true, constraints.check(word!("goose"))); } }