use std::io::{self, stdin, stdout, Write};
use itertools::join;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use std::collections::BTreeSet;
mod word;
pub use word::*;
mod clue;
pub use clue::*;
mod play;
pub use play::play_wordle;
pub const N: usize = 5;
fn word_vec_from_str(s: &str) -> Vec<Word> {
s.lines().filter_map(|l| l.try_into().ok()).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!("{}", join(seq, ", "));
}
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]);
for (pos, (gc, ac)) in g
.iter_mut()
.zip(a.iter_mut())
.enumerate()
.filter(|(_, (gc, ac))| *gc == *ac)
{
clue[pos] = Green;
*gc = 0;
*ac = 0;
}
for (pos, gc) in g.iter().enumerate().filter(|&(_, gc)| *gc != 0) {
if let Some(ac) = a.iter_mut().find(|ac| *gc == **ac) {
clue[pos] = Yellow;
*ac = 0;
}
}
clue
}
pub fn find_best_guesses(answers: &[Word], guesses: &[Word]) -> Vec<Word> {
let mut guess_scores: Vec<(Word, isize)> = guesses
.par_iter()
.cloned()
.map(|g| {
(
g,
-(answers
.par_iter()
.map(|a| compute_clue(a, &g))
.collect::<BTreeSet<Clue>>()
.len() as isize),
)
})
.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")
}