use super::model::{KeyboardHints, LetterState, LetterStatus};
pub fn check(wordle: String, guess: String) -> Vec<LetterStatus> {
let mut output: Vec<LetterStatus> = Vec::with_capacity(5);
if wordle.len() != 5 && guess.len() != 5 {
panic!("5 letter wordle and guess word is needed. Aborting");
}
let wordle_letters: Vec<char> = wordle.chars().collect();
let guess_letters: Vec<char> = guess.chars().collect();
for position in 0..5 {
let wordle_letter = wordle_letters[position];
let guess_letter = guess_letters[position];
if wordle_letter == guess_letter {
output.push(LetterStatus {
letter: guess_letter,
status: LetterState::Correct,
})
} else {
if !wordle_letters.contains(&guess_letter) {
output.push(LetterStatus {
letter: guess_letter,
status: LetterState::NotPresent,
})
} else {
let wordle_indices = get_all_letter_indices(guess_letter, wordle.clone());
let guess_indices = get_all_letter_indices(guess_letter, guess.clone());
let matches = intersection_match(wordle_indices.clone(), guess_indices.clone());
let coloring_chances = wordle_indices.len();
let future_start = guess_indices.partition_point(|&i| i == position);
let future_occurences = guess_indices[future_start..].iter();
let future_chances = future_occurences.len();
let match_completed = coloring_chances == matches.len();
let should_leave = match_completed || future_chances >= coloring_chances;
if !should_leave {
let is_incorrect = !wordle_indices.contains(&position);
if is_incorrect {
output.push(LetterStatus {
letter: guess_letter,
status: LetterState::Incorrect,
});
} else {
output.push(LetterStatus {
letter: guess_letter,
status: LetterState::NotPresent,
});
}
} else {
output.push(LetterStatus {
letter: guess_letter,
status: LetterState::NotPresent,
});
}
}
}
}
output
}
pub fn is_correct_guess(input: Vec<LetterStatus>) -> bool {
input.iter().all(|x| x.status == LetterState::Correct)
}
pub fn update_keyboard_hints<'a>(
hints: &'a mut KeyboardHints,
statuses: Vec<LetterStatus>,
) -> &'a mut KeyboardHints {
for status in statuses {
if !hints.contains_key(&status.letter) {
hints.insert(status.letter.clone(), status.status.clone());
} else {
match status.status {
LetterState::Incorrect => {
if let Some(current_status) = hints.get(&status.letter) {
if *current_status != LetterState::Correct {
hints.insert(status.letter.clone(), status.status.clone());
}
}
}
LetterState::Unknown | LetterState::NotPresent => {
if let Some(current_status) = hints.get(&status.letter) {
if *current_status != LetterState::Correct
&& *current_status != LetterState::Incorrect
{
hints.insert(status.letter.clone(), status.status.clone());
}
}
}
LetterState::Correct => {
hints.insert(status.letter.clone(), LetterState::Correct);
}
}
}
}
hints
}
fn get_all_letter_indices(letter: char, word: String) -> Vec<usize> {
let mut output: Vec<usize> = Vec::new();
word.chars()
.enumerate()
.into_iter()
.for_each(|(index, word_letter)| {
if word_letter == letter {
output.push(index);
}
});
output
}
fn intersection_match(wordle: Vec<usize>, guess: Vec<usize>) -> Vec<usize> {
let mut output: Vec<usize> = Vec::new();
wordle.into_iter().for_each(|i| {
if guess.contains(&i) {
output.push(i.clone());
}
});
output
}
#[cfg(test)]
mod tests {
use crate::wordle::model::{LetterState, LetterStatus};
use crate::wordle::utils::*;
use std::collections::HashMap;
#[test]
fn test_intersection_match() {
let output = intersection_match(vec![0, 2], vec![2]);
assert_eq!(output, vec![2]);
let output = intersection_match(vec![0, 2], vec![3]);
assert_eq!(output, vec![]);
let output = intersection_match(vec![1], vec![1, 3]);
assert_eq!(output, vec![1]);
}
#[test]
fn test_letter_indices() {
let output = get_all_letter_indices('e', "ennui".into());
assert_eq!(output, vec![0]);
let output = get_all_letter_indices('e', "where".into());
assert_eq!(output, vec![2, 4]);
let output = get_all_letter_indices('e', "drove".into());
assert_eq!(output, vec![4]);
let output = get_all_letter_indices('e', "evoke".into());
assert_eq!(output, vec![0, 4]);
}
#[test]
fn test_status_random() {
let output = check("ennui".into(), "where".into());
let expected: Vec<LetterStatus> = vec![
LetterStatus {
letter: 'w',
status: LetterState::NotPresent,
},
LetterStatus {
letter: 'h',
status: LetterState::NotPresent,
},
LetterStatus {
letter: 'e',
status: LetterState::NotPresent,
},
LetterStatus {
letter: 'r',
status: LetterState::NotPresent,
},
LetterStatus {
letter: 'e',
status: LetterState::Incorrect,
},
];
assert_eq!(output, expected);
let output = check("drove".into(), "evoke".into());
let expected: Vec<LetterStatus> = vec![
LetterStatus {
letter: 'e',
status: LetterState::NotPresent,
},
LetterStatus {
letter: 'v',
status: LetterState::Incorrect,
},
LetterStatus {
letter: 'o',
status: LetterState::Correct,
},
LetterStatus {
letter: 'k',
status: LetterState::NotPresent,
},
LetterStatus {
letter: 'e',
status: LetterState::Correct,
},
];
assert_eq!(output, expected);
let output = check("milky".into(), "livid".into());
let expected: Vec<LetterStatus> = vec![
LetterStatus {
letter: 'l',
status: LetterState::Incorrect,
},
LetterStatus {
letter: 'i',
status: LetterState::Correct,
},
LetterStatus {
letter: 'v',
status: LetterState::NotPresent,
},
LetterStatus {
letter: 'i',
status: LetterState::NotPresent,
},
LetterStatus {
letter: 'd',
status: LetterState::NotPresent,
},
];
assert_eq!(output, expected);
}
#[test]
fn test_letter_status() {
let all_correct_input: Vec<LetterStatus> = vec![
LetterStatus {
letter: 'p',
status: LetterState::Correct,
},
LetterStatus {
letter: 'i',
status: LetterState::Correct,
},
LetterStatus {
letter: 'o',
status: LetterState::Correct,
},
LetterStatus {
letter: 'u',
status: LetterState::Correct,
},
LetterStatus {
letter: 's',
status: LetterState::Correct,
},
];
assert!(is_correct_guess(all_correct_input));
let not_correct_input: Vec<LetterStatus> = vec![
LetterStatus {
letter: 'x',
status: LetterState::Unknown,
},
LetterStatus {
letter: 'i',
status: LetterState::Correct,
},
LetterStatus {
letter: 'o',
status: LetterState::Correct,
},
LetterStatus {
letter: 'u',
status: LetterState::Correct,
},
LetterStatus {
letter: 's',
status: LetterState::Correct,
},
];
assert_eq!(is_correct_guess(not_correct_input), false);
}
#[test]
fn test_keyboard_hints() {
let wordle = String::from("pious");
let mut hints: KeyboardHints = HashMap::new();
let statuses: Vec<LetterStatus> = check(wordle.clone(), "piano".into());
update_keyboard_hints(&mut hints, statuses);
assert_eq!(*hints.get(&'p').unwrap(), LetterState::Correct);
assert_eq!(*hints.get(&'i').unwrap(), LetterState::Correct);
assert_eq!(*hints.get(&'a').unwrap(), LetterState::NotPresent);
assert_eq!(*hints.get(&'n').unwrap(), LetterState::NotPresent);
assert_eq!(*hints.get(&'o').unwrap(), LetterState::Incorrect);
assert_eq!(hints.get(&'x'), None);
let statuses: Vec<LetterStatus> = check(wordle.clone(), "smile".into());
update_keyboard_hints(&mut hints, statuses);
assert_eq!(*hints.get(&'i').unwrap(), LetterState::Correct);
let wordle = "below";
hints.clear();
let statuses: Vec<LetterStatus> = check(wordle.into(), "hello".into());
update_keyboard_hints(&mut hints, statuses);
assert_eq!(*hints.get(&'l').unwrap(), LetterState::Correct);
}
}