use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Suit {
Man, Pin, Sou, }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Honor {
East,
South,
West,
North,
White,
Green,
Red,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Tile {
Suited { suit: Suit, value: u8 }, Honor(Honor),
}
impl Tile {
pub fn suited(suit: Suit, value: u8) -> Self {
Tile::Suited { suit, value }
}
pub fn honor(honor: Honor) -> Self {
Tile::Honor(honor)
}
pub fn is_simple(&self) -> bool {
match self {
Tile::Suited { value, .. } => *value >= 2 && *value <= 8,
Tile::Honor(_) => false,
}
}
pub fn is_terminal_or_honor(&self) -> bool {
match self {
Tile::Suited { value, .. } => *value == 1 || *value == 9,
Tile::Honor(_) => true,
}
}
pub fn is_terminal(&self) -> bool {
match self {
Tile::Suited { value, .. } => *value == 1 || *value == 9,
Tile::Honor(_) => false,
}
}
pub fn is_honor(&self) -> bool {
matches!(self, Tile::Honor(_))
}
pub fn is_dragon(&self) -> bool {
matches!(self, Tile::Honor(Honor::White | Honor::Green | Honor::Red))
}
pub fn is_wind(&self) -> bool {
matches!(
self,
Tile::Honor(Honor::East | Honor::South | Honor::West | Honor::North)
)
}
pub fn is_green(&self) -> bool {
match self {
Tile::Suited {
suit: Suit::Sou,
value,
} => matches!(value, 2 | 3 | 4 | 6 | 8),
Tile::Honor(Honor::Green) => true,
_ => false,
}
}
pub fn suit(&self) -> Option<Suit> {
match self {
Tile::Suited { suit, .. } => Some(*suit),
Tile::Honor(_) => None,
}
}
pub fn value(&self) -> Option<u8> {
match self {
Tile::Suited { value, .. } => Some(*value),
Tile::Honor(_) => None,
}
}
}
pub const KOKUSHI_TILES: [Tile; 13] = [
Tile::Suited {
suit: Suit::Man,
value: 1,
},
Tile::Suited {
suit: Suit::Man,
value: 9,
},
Tile::Suited {
suit: Suit::Pin,
value: 1,
},
Tile::Suited {
suit: Suit::Pin,
value: 9,
},
Tile::Suited {
suit: Suit::Sou,
value: 1,
},
Tile::Suited {
suit: Suit::Sou,
value: 9,
},
Tile::Honor(Honor::East),
Tile::Honor(Honor::South),
Tile::Honor(Honor::West),
Tile::Honor(Honor::North),
Tile::Honor(Honor::White),
Tile::Honor(Honor::Green),
Tile::Honor(Honor::Red),
];
impl TryFrom<&str> for Tile {
type Error = String;
fn try_from(input: &str) -> Result<Self, Self::Error> {
let chars: Vec<char> = input.chars().collect();
if chars.len() != 2 {
return Err(format!("Invalid tile format: {}", input));
}
let value_char = chars[0];
let type_char = chars[1];
match type_char {
'm' | 'p' | 's' => {
let suit = match type_char {
'm' => Suit::Man,
'p' => Suit::Pin,
_ => Suit::Sou,
};
let val = value_char.to_digit(10).ok_or("Not a digit")? as u8;
if !(1..=9).contains(&val) {
return Err("Suited tiles must be 1-9".to_string());
}
Ok(Tile::Suited { suit, value: val })
}
'z' => {
let val_digit = value_char.to_digit(10).ok_or("Not a digit")? as u8;
let honor_variant = match val_digit {
1 => Honor::East,
2 => Honor::South,
3 => Honor::West,
4 => Honor::North,
5 => Honor::White,
6 => Honor::Green,
7 => Honor::Red,
_ => return Err("Out of bounds for Honors".into()),
};
Ok(Tile::Honor(honor_variant))
}
_ => Err(format!("Unknown suit: {}", type_char)),
}
}
}
impl fmt::Display for Tile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Tile::Suited { suit, value } => {
let s = match suit {
Suit::Man => 'm',
Suit::Pin => 'p',
Suit::Sou => 's',
};
write!(f, "{}{}", value, s)
}
Tile::Honor(h) => {
let v = match h {
Honor::East => 1,
Honor::South => 2,
Honor::West => 3,
Honor::North => 4,
Honor::White => 5,
Honor::Green => 6,
Honor::Red => 7,
};
write!(f, "{}z", v)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_tiles() {
let five_man = Tile::suited(Suit::Man, 5);
let red_dragon = Tile::honor(Honor::Red);
println!("{:?}", five_man);
println!("{:?}", red_dragon);
assert_eq!(five_man, Tile::suited(Suit::Man, 5));
}
#[test]
fn tile_properties() {
assert!(Tile::suited(Suit::Pin, 5).is_simple());
assert!(!Tile::suited(Suit::Pin, 1).is_simple());
assert!(!Tile::suited(Suit::Pin, 9).is_simple());
assert!(!Tile::honor(Honor::East).is_simple());
assert!(Tile::suited(Suit::Sou, 1).is_terminal_or_honor());
assert!(Tile::suited(Suit::Sou, 9).is_terminal_or_honor());
assert!(Tile::honor(Honor::White).is_terminal_or_honor());
assert!(!Tile::suited(Suit::Man, 5).is_terminal_or_honor());
}
}