riichi_elements/meld/
chii.rs

1use core::fmt::{Display, Formatter};
2
3use crate::{
4    tile::Tile,
5    tile_set::*,
6    utils::{sort2, sort3},
7};
8
9use super::packed::{PackedMeld, PackedMeldKind};
10
11/// An open group of 3 consecutive tiles (チー / 明順).
12/// The called tile may only come from the previous player's discard.
13#[derive(Copy, Clone, Debug, Eq, PartialEq)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[non_exhaustive]
16pub struct Chii {
17    /// The calling player's own 2 tiles.
18    pub own: [Tile; 2],
19
20    /// The called tile.
21    pub called: Tile,
22
23    /// The smallest tile (ignoring red) in the group.
24    pub min: Tile,
25}
26
27impl Chii {
28    pub const fn dir(self) -> u8 { self.called.normal_num() - self.min.num() }
29    pub const fn suit(self) -> u8 { self.called.suit() }
30
31    /// Construct from own tiles and the called tile.
32    pub fn from_tiles(own0: Tile, own1: Tile, called: Tile) -> Option<Self> {
33        let suit = called.suit();
34        if own0.suit() != suit || own1.suit() != suit { return None; }
35        let (own0, own1) = sort2(own0, own1);
36        let (a, b, c) = sort3(
37            own0.to_normal(),
38            own1.to_normal(),
39            called.to_normal());
40        if !(b == a.succ().unwrap() && c == b.succ().unwrap()) { return None; }
41        Some(Chii { own: [own0, own1], called, min: a })
42    }
43
44    /// Checks whether own tiles exist in player's closed hand.
45    pub fn is_in_hand(self, hand: &TileSet37) -> bool {
46        hand[self.own[0]] >= 1 && hand[self.own[1]] >= 1
47    }
48
49    /// Removes own tiles from player's closed hand (assuming they exist).
50    pub fn consume_from_hand(self, hand: &mut TileSet37) {
51        hand[self.own[0]] -= 1;
52        hand[self.own[1]] -= 1;
53    }
54}
55
56impl Display for Chii {
57    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
58        write!(f, "C{}{}{}{}",
59               self.called.num(),
60               self.own[0].num(),
61               self.own[1].num(),
62               self.called.suit_char())
63    }
64}
65
66impl TryFrom<PackedMeld> for Chii {
67    type Error = ();
68
69    fn try_from(raw: PackedMeld) -> Result<Self, Self::Error> {
70        if raw.kind() != PackedMeldKind::Chii as u8 { return Err(()); }
71        let mut a = raw.get_tile().ok_or(())?;
72        let mut b = a.succ().unwrap();
73        let mut c = b.succ().unwrap();
74        if raw.red() > 0 {
75            a = a.to_red();
76            b = b.to_red();
77            c = c.to_red();
78        }
79        match raw.dir() {
80            0 => Chii::from_tiles(b, c, a),
81            1 => Chii::from_tiles(a, c, b),
82            2 => Chii::from_tiles(a, b, c),
83            _ => return Err(()),
84        }.ok_or(())
85    }
86}
87
88impl From<Chii> for PackedMeld {
89    fn from(chii: Chii) -> Self {
90        let [own0, own1] = chii.own;
91        let red = own0.is_red() || own1.is_red() || chii.called.is_red();
92        PackedMeld::new()
93            .with_tile(chii.min.encoding())
94            .with_dir(chii.dir())
95            .with_red(red as u8)
96            .with_kind(PackedMeldKind::Chii as u8)
97    }
98}