riichi_elements/meld/
kakan.rs

1use core::fmt::{Display, Formatter};
2
3use crate::{
4    player::*,
5    tile::Tile,
6    tile_set::*,
7    utils::{pack4, unpack4},
8};
9
10use super::{
11    packed::{normalize_kakan, PackedMeld, PackedMeldKind},
12    utils::count_for_kan,
13    Pon,
14};
15
16/// A Kan formed by an existing [`Pon`] + the 1 last identical tile from closed hand (加槓 / 小明槓).
17/// This can be formed when the owner of the [`Pon`] is in action.
18#[derive(Copy, Clone, Debug, Eq, PartialEq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20#[non_exhaustive]
21pub struct Kakan {
22    /// The original Pon.
23    #[cfg_attr(feature = "serde", serde(flatten))]
24    pub pon: Pon,
25
26    /// The extra tile from the player's closed hand.
27    pub added: Tile,
28}
29
30impl Kakan {
31    pub const fn num(self) -> u8 {
32        self.added.normal_num()
33    }
34    pub const fn suit(self) -> u8 {
35        self.added.suit()
36    }
37
38    /// Constructs from an existing Pon and the (last) added tile.
39    pub fn from_pon_added(pon: Pon, added: Tile) -> Option<Self> {
40        if added.to_normal() != pon.called.to_normal() {
41            return None;
42        }
43        Some(Kakan { pon, added })
44    }
45
46    /// Constructs from an existing Pon and the closed hand.
47    /// If the closed hand does not have the last remaining tile, returns `None`.
48    pub fn from_pon_hand(pon: Pon, hand: &TileSet37) -> Option<Self> {
49        let normal = pon.called.to_normal();
50        let (num_normal, num_red) = count_for_kan(hand, normal);
51        match (num_normal, num_red) {
52            (1, 0) => Some(Kakan { pon, added: normal }),
53            (0, 1) => Some(Kakan { pon, added: normal.to_red() }),
54            _ => None,
55        }
56    }
57
58    /// Removes the added tile from the hand (where this was constructed from).
59    pub fn consume_from_hand(self, hand: &mut TileSet37) {
60        hand[self.added] -= 1;
61    }
62}
63
64impl Display for Kakan {
65    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
66        let (n0, n1, nc, na, suit) = (
67            self.pon.own[0].num(),
68            self.pon.own[1].num(),
69            self.pon.called.num(),
70            self.added.num(),
71            self.added.suit_char(),
72        );
73        match self.pon.dir.to_u8() {
74            1 => write!(f, "{}{}K({}/{}){}", n0, n1, na, nc, suit),
75            2 => write!(f, "{}K({}/{}){}{}", n0, na, nc, n1, suit),
76            3 => write!(f, "K({}/{}){}{}{}", na, nc, n0, n1, suit),
77            _ => Err(core::fmt::Error::default()),
78        }
79    }
80}
81
82impl TryFrom<PackedMeld> for Kakan {
83    type Error = ();
84
85    fn try_from(raw: PackedMeld) -> Result<Self, Self::Error> {
86        if raw.kind() != PackedMeldKind::Kakan as u8 {
87            return Err(());
88        }
89        let t = raw.get_tile().ok_or(())?;
90        let (mut own0, mut own1, mut called, mut added) = (t, t, t, t);
91        let (r0, r1, r2, r3) = unpack4(normalize_kakan(raw.red()));
92        if r0 { own0 = own0.to_red(); }
93        if r1 { own1 = own1.to_red(); }
94        if r2 { called = called.to_red(); }
95        if r3 { added = added.to_red(); }
96        let pon = Pon::from_tiles_dir(own0, own1, called, Player::new(raw.dir()))
97            .ok_or(())?;
98        Kakan::from_pon_added(pon, added).ok_or(())
99    }
100}
101
102impl From<Kakan> for PackedMeld {
103    fn from(kakan: Kakan) -> Self {
104        let [own0, own1] = kakan.pon.own;
105        PackedMeld::new()
106            .with_tile(own0.normal_encoding())
107            .with_dir(kakan.pon.dir.to_u8())
108            .with_red(pack4(
109                own0.is_red(),
110                own1.is_red(),
111                kakan.pon.called.is_red(),
112                kakan.added.is_red(),
113            ))
114            .with_kind(PackedMeldKind::Kakan as u8)
115    }
116}