use std::io::{self, stdin, stdout, Write};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
mod word;
pub use word::*;
mod clue;
pub use clue::*;
pub const N: usize = 5;
fn word_vec_from_str(s: &str) -> Vec<Word> {
s.lines()
.filter_map(|l| {
let w: Word = l.try_into().ok()?;
Some(w)
})
.collect()
}
pub fn load_words() -> (Vec<Word>, Vec<Word>) {
let mut answers = word_vec_from_str(include_str!("answers.txt"));
let mut guesses = word_vec_from_str(include_str!("guesses.txt"));
guesses.extend(answers.iter().cloned());
answers.sort();
guesses.sort();
(answers, guesses)
}
pub fn get_guess() -> io::Result<Word> {
print!("guess: ");
stdout().flush()?;
let mut line = String::new();
stdin().read_line(&mut line)?;
line.trim()
.try_into()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}
pub fn get_clue() -> io::Result<Clue> {
print!("clue: ");
stdout().flush()?;
let mut line = String::new();
stdin().read_line(&mut line)?;
line.trim()
.chars()
.filter_map(|c| ClueColor::try_from(c).ok())
.collect::<Vec<ClueColor>>()
.as_slice()
.try_into()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}
pub fn print_sequence(seq: &Vec<Word>) {
println!(
"{}",
seq.iter()
.map(String::from)
.collect::<Vec<String>>()
.join(", ")
);
}
pub fn is_candidate(clue: &Clue, guess: &Word, word: &Word) -> bool {
let mut w: Word = word.clone();
for (i, _) in clue.iter().enumerate().filter(|&(_, &c)| c == Green) {
if guess[i] != w[i] {
return false;
}
w[i] = 0;
}
for (i, _) in clue.iter().enumerate().filter(|&(_, &c)| c == Yellow) {
match w.iter().position(|&chr| chr == guess[i]) {
Some(j) => {
if j == i {
return false;
} else {
w[j] = 0;
}
}
None => return false,
}
}
for (i, _) in clue.iter().enumerate().filter(|&(_, &c)| c == Black) {
if w.iter().position(|&chr| chr == guess[i]).is_some() {
return false;
}
}
true
}
pub fn compute_clue(answer: &Word, guess: &Word) -> Clue {
let mut a = answer.clone();
let mut g = guess.clone();
let mut clue = Clue::from([Black; N]);
g.iter_mut()
.zip(a.iter_mut())
.enumerate()
.for_each(|(pos, (ac, gc))| {
if *ac == *gc {
clue[pos] = Green;
*ac = 0;
*gc = 0;
}
});
g.iter_mut().enumerate().for_each(|(pos, gc)| {
if *gc != 0 {
a.iter_mut().find(|c| **c == *gc).map(|ac| {
clue[pos] = Yellow;
*gc = 0;
*ac = 0;
});
}
});
clue
}
pub fn find_best_guesses(answers: &Vec<Word>, guesses: &Vec<Word>) -> Vec<Word> {
let mut guess_scores: Vec<(Word, usize)> = guesses
.par_iter()
.cloned()
.map(|g| {
(
g,
answers
.par_iter()
.map(|a| {
let clue = compute_clue(a, &g);
answers
.par_iter()
.filter(|w| is_candidate(&clue, &g, w))
.count()
})
.max()
.expect("no answers left"),
)
})
.collect();
guess_scores.sort_by_key(|&(_, s)| s);
if let Some((_, best_score)) = guess_scores.first() {
let best_score = best_score.clone();
guess_scores.retain(|(_, s)| *s == best_score);
}
let mut best_guesses: Vec<Word> = guess_scores.iter().map(|&(w, _)| w).collect();
if best_guesses.iter().any(|g| answers.contains(g)) {
best_guesses.retain(|g| answers.contains(g));
}
best_guesses
}
pub fn clue_grid(sequence: &Vec<Word>, answer: &Word) -> String {
sequence
.iter()
.map(|w| compute_clue(answer, w))
.map(|c| {
let s: String = (&c).into();
s
})
.collect::<Vec<String>>()
.join("\n")
}