use std::error::Error;
use std::fmt::{Display, Formatter};
use std::ops::RangeInclusive;
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum Suite {
Manzu,
Pinzu,
Souzu,
Honor,
Any,
}
impl Display for Suite {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let name = match self {
Suite::Manzu => "Manzu",
Suite::Pinzu => "Pinzu",
Suite::Souzu => "Souzu",
Suite::Honor => "Honor",
Suite::Any => "Any",
};
write!(f, "{}", name)
}
}
#[derive(Copy, Clone, Default, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct TileValue(pub u8);
impl Display for TileValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl From<TileValue> for usize {
fn from(value: TileValue) -> Self {
value.0 as usize
}
}
const TILE_NUMERALS: [&str; 10] = [
"Akadora", "Ii", "Ryan", "San", "Suu", "Uu", "Rou", "Chii", "Paa", "Kyuu",
];
const HONOR_NAMES: [&str; 7] = ["Ton", "Nan", "Shaa", "Pei", "Haku", "Hatsu", "Chun"];
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct Tile {
pub suite: Suite,
pub value: TileValue,
}
#[derive(Debug, Copy, Clone)]
pub struct InvalidTileError {
pub suite: Suite,
pub value: TileValue,
}
impl Error for InvalidTileError {}
impl InvalidTileError {
#[inline]
pub fn new(suite: Suite, value: TileValue) -> Self {
Self { suite, value }
}
}
impl Display for InvalidTileError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid value: {} for suite: {}", self.value, self.suite)
}
}
impl Tile {
pub fn new(suite: Suite, value: TileValue) -> Result<Tile, InvalidTileError> {
let range: RangeInclusive<usize> = match suite {
Suite::Manzu | Suite::Pinzu | Suite::Souzu => 0..=9,
Suite::Honor => 1..=7,
Suite::Any => 0..=0,
};
if range.contains(&usize::from(value)) {
Ok(Self { suite, value })
} else {
Err(InvalidTileError::new(suite, value))
}
}
pub fn name(&self) -> String {
match self.suite {
Suite::Manzu => format!("{} man", TILE_NUMERALS[usize::from(self.value)]),
Suite::Pinzu => format!("{} pin", TILE_NUMERALS[usize::from(self.value)]),
Suite::Souzu => format!("{} sou", TILE_NUMERALS[usize::from(self.value)]),
Suite::Honor => HONOR_NAMES[usize::from(self.value) - 1].to_owned(),
Suite::Any => "Any".to_owned(),
}
}
}
impl Display for Tile {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.name())
}
}
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum TilePlacement {
Normal,
Rotated,
RotatedAndShifted,
}
impl TilePlacement {
#[inline]
pub fn next(&self) -> TilePlacement {
match self {
TilePlacement::Normal => TilePlacement::Rotated,
TilePlacement::Rotated => TilePlacement::RotatedAndShifted,
TilePlacement::RotatedAndShifted => TilePlacement::Normal,
}
}
}
impl Display for TilePlacement {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TilePlacement::Normal => write!(f, "normal"),
TilePlacement::Rotated => write!(f, "rotated"),
TilePlacement::RotatedAndShifted => write!(f, "rotated and shifted"),
}
}
}
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct HandTile {
pub tile: Tile,
pub placement: TilePlacement,
}
impl HandTile {
#[inline]
pub fn new(tile: Tile, placement: TilePlacement) -> Self {
Self { tile, placement }
}
}
impl Display for HandTile {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.placement, self.tile)
}
}
pub type HandGroup = Vec<HandTile>;
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Hand {
groups: Vec<HandGroup>,
}
impl Hand {
#[inline]
pub fn new(groups: Vec<HandGroup>) -> Self {
Self { groups }
}
#[inline]
pub fn groups(&self) -> &Vec<HandGroup> {
&self.groups
}
#[inline]
pub fn hand_tiles(&self) -> impl Iterator<Item = HandTile> + '_ {
self.groups.iter().flatten().copied()
}
#[inline]
pub fn tiles(&self) -> impl Iterator<Item = Tile> + '_ {
self.groups.iter().flatten().map(|x| x.tile)
}
}
#[cfg(test)]
mod tests {
use crate::tiles::ALL_TILES;
use crate::{Suite, Tile, TileValue};
#[test]
fn should_return_valid_suite_names() {
let suites = [
Suite::Manzu,
Suite::Pinzu,
Suite::Souzu,
Suite::Honor,
Suite::Any,
];
let names = suites.map(|suite| format!("{}", suite));
let expected = ["Manzu", "Pinzu", "Souzu", "Honor", "Any"].map(|x| x.to_owned());
assert_eq!(names, expected);
}
#[test]
fn should_create_valid_tiles() {
assert!(Tile::new(Suite::Manzu, TileValue(1)).is_ok());
assert!(Tile::new(Suite::Manzu, TileValue(0)).is_ok());
assert!(Tile::new(Suite::Manzu, TileValue(2)).is_ok());
assert!(Tile::new(Suite::Manzu, TileValue(9)).is_ok());
assert!(Tile::new(Suite::Pinzu, TileValue(0)).is_ok());
assert!(Tile::new(Suite::Pinzu, TileValue(9)).is_ok());
assert!(Tile::new(Suite::Souzu, TileValue(0)).is_ok());
assert!(Tile::new(Suite::Souzu, TileValue(9)).is_ok());
assert!(Tile::new(Suite::Honor, TileValue(1)).is_ok());
assert!(Tile::new(Suite::Honor, TileValue(2)).is_ok());
assert!(Tile::new(Suite::Honor, TileValue(7)).is_ok());
assert!(Tile::new(Suite::Any, TileValue(0)).is_ok());
}
#[test]
fn should_return_error_on_invalid_tiles() {
assert!(Tile::new(Suite::Manzu, TileValue(10)).is_err());
assert!(Tile::new(Suite::Pinzu, TileValue(10)).is_err());
assert!(Tile::new(Suite::Souzu, TileValue(10)).is_err());
assert!(Tile::new(Suite::Honor, TileValue(0)).is_err());
assert!(Tile::new(Suite::Honor, TileValue(8)).is_err());
assert!(Tile::new(Suite::Any, TileValue(1)).is_err());
assert!(Tile::new(Suite::Any, TileValue(5)).is_err());
}
#[test]
fn should_return_valid_tile_names() {
let names = ALL_TILES.map(|tile| format!("{}", tile));
let expected = [
"Akadora man",
"Ii man",
"Ryan man",
"San man",
"Suu man",
"Uu man",
"Rou man",
"Chii man",
"Paa man",
"Kyuu man",
"Akadora pin",
"Ii pin",
"Ryan pin",
"San pin",
"Suu pin",
"Uu pin",
"Rou pin",
"Chii pin",
"Paa pin",
"Kyuu pin",
"Akadora sou",
"Ii sou",
"Ryan sou",
"San sou",
"Suu sou",
"Uu sou",
"Rou sou",
"Chii sou",
"Paa sou",
"Kyuu sou",
"Ton",
"Nan",
"Shaa",
"Pei",
"Haku",
"Hatsu",
"Chun",
"Any",
]
.map(|x| x.to_owned());
assert_eq!(names, expected);
}
}