Skip to main content

rustoku_lib/core/techniques/
flags.rs

1use bitflags::bitflags;
2use serde::{Deserialize, Serialize};
3
4bitflags! {
5    /// Bitflags indicating which human techniques are active/enabled.
6    ///
7    /// The flags are organized into bytes for better organization:
8    /// - Easy techniques from bits 0-7
9    /// - Medium techniques from bits 8-15
10    /// - Hard techniques from bits 16-23
11    /// - Expert techniques from bits 24-31
12    ///
13    /// Composite groups (EASY, MEDIUM, HARD, EXPERT) are here for convenience.
14    #[repr(transparent)]
15    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
16    pub struct TechniqueFlags: u32 {
17        /// Apply the naked singles technique.
18        const NAKED_SINGLES = 1 << 0;
19        /// Apply the hidden singles technique.
20        const HIDDEN_SINGLES = 1 << 1;
21
22        /// Apply the naked pairs technique.
23        const NAKED_PAIRS = 1 << 8;
24        /// Apply the hidden pairs technique.
25        const HIDDEN_PAIRS = 1 << 9;
26        /// Apply the locked candidates technique.
27        const LOCKED_CANDIDATES = 1 << 10;
28        /// Apply the naked triples technique.
29        const NAKED_TRIPLES = 1 << 11;
30        /// Apply the hidden triples technique.
31        const HIDDEN_TRIPLES = 1 << 12;
32
33        /// Apply the X-Wing technique.
34        const X_WING = 1 << 16;
35        /// Apply the naked quads technique.
36        const NAKED_QUADS = 1 << 17;
37        /// Apply the hidden quads technique.
38        const HIDDEN_QUADS = 1 << 18;
39        /// Apply the Swordfish technique.
40        const SWORDFISH = 1 << 19;
41        /// Apply the Jellyfish technique.
42        const JELLYFISH = 1 << 20;
43        /// Apply the Skyscraper technique.
44        const SKYSCRAPER = 1 << 21;
45
46        /// Apply the W-Wing technique.
47        const W_WING = 1 << 24;
48        /// Apply the XY-Wing technique.
49        const XY_WING = 1 << 25;
50        /// Apply the XYZ-Wing technique.
51        const XYZ_WING = 1 << 26;
52        /// Apply the Alternating Inference Chain (AIC) technique.
53        const ALTERNATING_INFERENCE_CHAIN = 1 << 27;
54
55        /// Alias for X_WING.
56        #[deprecated(note = "use X_WING instead")]
57        const XWING = Self::X_WING.bits();
58
59        /// Apply easy techniques
60        const EASY = 0x0000_00FF;
61        /// Apply medium techniques
62        const MEDIUM = 0x0000_FF00;
63        /// Apply hard techniques
64        const HARD = 0x00FF_0000;
65        /// Apply expert techniques
66        const EXPERT = 0xFF00_0000;
67    }
68}
69
70#[derive(
71    Debug,
72    Clone,
73    Copy,
74    PartialEq,
75    Eq,
76    PartialOrd,
77    Ord,
78    Hash,
79    clap::ValueEnum,
80    Serialize,
81    Deserialize,
82)]
83pub enum Difficulty {
84    Easy,
85    Medium,
86    Hard,
87    Expert,
88}
89
90impl std::fmt::Display for Difficulty {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        write!(f, "{}", self.difficulty_name())
93    }
94}
95
96impl From<TechniqueFlags> for Difficulty {
97    fn from(flags: TechniqueFlags) -> Self {
98        if !(flags & TechniqueFlags::EXPERT).is_empty() {
99            Difficulty::Expert
100        } else if !(flags & TechniqueFlags::HARD).is_empty() {
101            Difficulty::Hard
102        } else if !(flags & TechniqueFlags::MEDIUM).is_empty() {
103            Difficulty::Medium
104        } else {
105            Difficulty::Easy
106        }
107    }
108}
109
110impl TechniqueFlags {
111    /// Returns the highest difficulty level associated with the current flags.
112    pub fn difficulty(&self) -> Difficulty {
113        Difficulty::from(*self)
114    }
115
116    /// Returns the name of the highest difficulty level.
117    pub fn difficulty_name(&self) -> &'static str {
118        self.difficulty().difficulty_name()
119    }
120}
121
122impl Difficulty {
123    pub fn difficulty_name(&self) -> &'static str {
124        match self {
125            Difficulty::Easy => "Easy",
126            Difficulty::Medium => "Medium",
127            Difficulty::Hard => "Hard",
128            Difficulty::Expert => "Expert",
129        }
130    }
131}
132
133impl std::str::FromStr for Difficulty {
134    type Err = ();
135
136    fn from_str(s: &str) -> Result<Self, Self::Err> {
137        match s.to_lowercase().as_str() {
138            "easy" => Ok(Difficulty::Easy),
139            "medium" => Ok(Difficulty::Medium),
140            "hard" => Ok(Difficulty::Hard),
141            "expert" => Ok(Difficulty::Expert),
142            _ => Err(()),
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_difficulty_display() {
153        assert_eq!(format!("{}", Difficulty::Easy), "Easy");
154        assert_eq!(format!("{}", Difficulty::Medium), "Medium");
155        assert_eq!(format!("{}", Difficulty::Hard), "Hard");
156        assert_eq!(format!("{}", Difficulty::Expert), "Expert");
157    }
158
159    #[test]
160    fn test_difficulty_name() {
161        assert_eq!(Difficulty::Easy.difficulty_name(), "Easy");
162        assert_eq!(Difficulty::Medium.difficulty_name(), "Medium");
163        assert_eq!(Difficulty::Hard.difficulty_name(), "Hard");
164        assert_eq!(Difficulty::Expert.difficulty_name(), "Expert");
165    }
166
167    #[test]
168    fn test_technique_flags_difficulty() {
169        // Single flags
170        assert_eq!(TechniqueFlags::NAKED_SINGLES.difficulty(), Difficulty::Easy);
171        assert_eq!(TechniqueFlags::NAKED_PAIRS.difficulty(), Difficulty::Medium);
172        assert_eq!(TechniqueFlags::X_WING.difficulty(), Difficulty::Hard);
173        assert_eq!(TechniqueFlags::W_WING.difficulty(), Difficulty::Expert);
174
175        // Combined flags (should return highest difficulty)
176        let combined = TechniqueFlags::NAKED_SINGLES | TechniqueFlags::X_WING;
177        assert_eq!(combined.difficulty(), Difficulty::Hard);
178
179        let all = TechniqueFlags::all();
180        assert_eq!(all.difficulty(), Difficulty::Expert);
181    }
182
183    #[test]
184    fn test_from_technique_flags_for_difficulty() {
185        assert_eq!(
186            Difficulty::from(TechniqueFlags::HIDDEN_SINGLES),
187            Difficulty::Easy
188        );
189        assert_eq!(
190            Difficulty::from(TechniqueFlags::LOCKED_CANDIDATES),
191            Difficulty::Medium
192        );
193        assert_eq!(
194            Difficulty::from(TechniqueFlags::JELLYFISH),
195            Difficulty::Hard
196        );
197        assert_eq!(
198            Difficulty::from(TechniqueFlags::XYZ_WING),
199            Difficulty::Expert
200        );
201    }
202}