cl_wordle/
lib.rs

1use std::{fmt::Display, ops::Deref};
2
3pub mod words;
4pub mod state;
5pub mod game;
6pub mod iter;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
9/// Represents a match for a given letter against the solution
10pub enum Match {
11    /// Letter is in the correct position
12    Exact,
13    /// Letter is in the solution but not in the correct positon
14    Close,
15    /// Letter is not in the solution
16    Wrong,
17}
18
19/// Represents the outcome for a single guess
20#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
21pub struct Matches(pub [Match; 5]);
22
23impl Matches {
24    pub fn win(self) -> bool {
25        self.0 == [Match::Exact; 5]
26    }
27}
28
29impl Display for Match {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            Match::Exact => write!(f, "🟩"),
33            Match::Close => write!(f, "🟨"),
34            Match::Wrong => write!(f, "⬛"),
35        }
36    }
37}
38
39impl Display for Matches {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        for m in self.0 {
42            write!(f, "{}", m)?;
43        }
44        write!(f, "")
45    }
46}
47
48impl Deref for Matches {
49    type Target = [Match; 5];
50
51    fn deref(&self) -> &Self::Target {
52        &self.0
53    }
54}
55
56pub fn diff(input: &str, solution: &str) -> Matches {
57    debug_assert!(
58        input.is_ascii(),
59        "input guess should only be 5 ascii letters"
60    );
61    debug_assert_eq!(input.len(), 5, "input guess should only be 5 ascii letters");
62    debug_assert!(solution.is_ascii());
63    debug_assert_eq!(solution.len(), 5);
64
65    let input = input.as_bytes();
66    let mut solution = solution.as_bytes().to_owned();
67
68    let mut diff = [Match::Wrong; 5];
69
70    // find exact matches first
71    for (i, &b) in input.iter().enumerate() {
72        if solution[i] == b {
73            solution[i] = 0; // letters only match once
74            diff[i] = Match::Exact;
75        }
76    }
77
78    // now, find amber matches
79    for (i, &b) in input.iter().enumerate() {
80        if diff[i] != Match::Wrong {
81            continue;
82        }
83        if let Some(j) = solution.iter().position(|&x| x == b) {
84            solution[j] = 0; // letters only match once
85            diff[i] = Match::Close;
86        }
87    }
88
89    Matches(diff)
90}
91
92#[cfg(test)]
93mod tests {
94    use super::{
95        diff,
96        Match::{self, *},
97    };
98    use test_case::test_case;
99
100    #[test_case("class", "crest", [Exact, Wrong, Wrong, Exact, Wrong]; "double letter, one exact, one wrong")]
101    #[test_case("stars", "crest", [Close, Close, Wrong, Close, Wrong]; "double letter, one close, one wrong")]
102    #[test_case("kills", "skill", [Close, Close, Close, Exact, Close]; "double letter, one exact, one close")]
103    fn test_diff(input: &str, solution: &str, matches: [Match; 5]) {
104        assert_eq!(diff(input, solution).0, matches);
105    }
106}