use bitflags::bitflags;
use serde::{Deserialize, Serialize};
bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TechniqueFlags: u32 {
const NAKED_SINGLES = 1 << 0;
const HIDDEN_SINGLES = 1 << 1;
const NAKED_PAIRS = 1 << 8;
const HIDDEN_PAIRS = 1 << 9;
const LOCKED_CANDIDATES = 1 << 10;
const NAKED_TRIPLES = 1 << 11;
const HIDDEN_TRIPLES = 1 << 12;
const X_WING = 1 << 16;
const NAKED_QUADS = 1 << 17;
const HIDDEN_QUADS = 1 << 18;
const SWORDFISH = 1 << 19;
const JELLYFISH = 1 << 20;
const SKYSCRAPER = 1 << 21;
const W_WING = 1 << 24;
const XY_WING = 1 << 25;
const XYZ_WING = 1 << 26;
const ALTERNATING_INFERENCE_CHAIN = 1 << 27;
#[deprecated(note = "use X_WING instead")]
const XWING = Self::X_WING.bits();
const EASY = 0x0000_00FF;
const MEDIUM = 0x0000_FF00;
const HARD = 0x00FF_0000;
const EXPERT = 0xFF00_0000;
}
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
clap::ValueEnum,
Serialize,
Deserialize,
)]
pub enum Difficulty {
Easy,
Medium,
Hard,
Expert,
}
impl std::fmt::Display for Difficulty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.difficulty_name())
}
}
impl From<TechniqueFlags> for Difficulty {
fn from(flags: TechniqueFlags) -> Self {
if !(flags & TechniqueFlags::EXPERT).is_empty() {
Difficulty::Expert
} else if !(flags & TechniqueFlags::HARD).is_empty() {
Difficulty::Hard
} else if !(flags & TechniqueFlags::MEDIUM).is_empty() {
Difficulty::Medium
} else {
Difficulty::Easy
}
}
}
impl TechniqueFlags {
pub fn difficulty(&self) -> Difficulty {
Difficulty::from(*self)
}
pub fn difficulty_name(&self) -> &'static str {
self.difficulty().difficulty_name()
}
}
impl Difficulty {
pub fn difficulty_name(&self) -> &'static str {
match self {
Difficulty::Easy => "Easy",
Difficulty::Medium => "Medium",
Difficulty::Hard => "Hard",
Difficulty::Expert => "Expert",
}
}
}
impl std::str::FromStr for Difficulty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"easy" => Ok(Difficulty::Easy),
"medium" => Ok(Difficulty::Medium),
"hard" => Ok(Difficulty::Hard),
"expert" => Ok(Difficulty::Expert),
_ => Err(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_difficulty_display() {
assert_eq!(format!("{}", Difficulty::Easy), "Easy");
assert_eq!(format!("{}", Difficulty::Medium), "Medium");
assert_eq!(format!("{}", Difficulty::Hard), "Hard");
assert_eq!(format!("{}", Difficulty::Expert), "Expert");
}
#[test]
fn test_difficulty_name() {
assert_eq!(Difficulty::Easy.difficulty_name(), "Easy");
assert_eq!(Difficulty::Medium.difficulty_name(), "Medium");
assert_eq!(Difficulty::Hard.difficulty_name(), "Hard");
assert_eq!(Difficulty::Expert.difficulty_name(), "Expert");
}
#[test]
fn test_technique_flags_difficulty() {
assert_eq!(TechniqueFlags::NAKED_SINGLES.difficulty(), Difficulty::Easy);
assert_eq!(TechniqueFlags::NAKED_PAIRS.difficulty(), Difficulty::Medium);
assert_eq!(TechniqueFlags::X_WING.difficulty(), Difficulty::Hard);
assert_eq!(TechniqueFlags::W_WING.difficulty(), Difficulty::Expert);
let combined = TechniqueFlags::NAKED_SINGLES | TechniqueFlags::X_WING;
assert_eq!(combined.difficulty(), Difficulty::Hard);
let all = TechniqueFlags::all();
assert_eq!(all.difficulty(), Difficulty::Expert);
}
#[test]
fn test_from_technique_flags_for_difficulty() {
assert_eq!(
Difficulty::from(TechniqueFlags::HIDDEN_SINGLES),
Difficulty::Easy
);
assert_eq!(
Difficulty::from(TechniqueFlags::LOCKED_CANDIDATES),
Difficulty::Medium
);
assert_eq!(
Difficulty::from(TechniqueFlags::JELLYFISH),
Difficulty::Hard
);
assert_eq!(
Difficulty::from(TechniqueFlags::XYZ_WING),
Difficulty::Expert
);
}
}