riichi_elements/meld/
pon.rs

1use core::fmt::{Display, Formatter};
2
3use crate::{
4    player::*,
5    tile::Tile,
6    tile_set::*,
7    utils::{pack4, sort2, unpack4},
8};
9use super::packed::{normalize_pon, PackedMeld, PackedMeldKind};
10
11/// An open group of 3 identical (ignoring red) tiles (ポン / 明刻).
12/// May be called from any other 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 Pon {
17    /// The calling player's own 2 tiles.
18    pub own: [Tile; 2],
19
20    /// The called tile.
21    pub called: Tile,
22
23    /// (discarding player - self) mod 4
24    pub dir: Player,
25}
26
27impl Pon {
28    pub const fn num(self) -> u8 { self.called.normal_num() }
29    pub const fn suit(self) -> u8 { self.called.suit() }
30
31    /// Constructs from own tiles, called tile, and the relative pos of the discarding player.
32    pub fn from_tiles_dir(own0: Tile, own1: Tile, called: Tile, dir: Player) -> Option<Self> {
33        if own0.to_normal() != called.to_normal() ||
34            own1.to_normal() != called.to_normal() ||
35            dir.to_u8() == 0 { return None; }
36        let (own0, own1) = sort2(own0, own1);
37        Some(Pon { own: [own0, own1], called, dir })
38    }
39
40    /// Checks whether own tiles exist in player's closed hand.
41    pub fn is_in_hand(self, hand: &TileSet37) -> bool {
42        if self.own[0] != self.own[1] { hand[self.own[0]] >= 1 && hand[self.own[1]] >= 1 }
43        else { hand[self.own[0]] >= 2 }
44    }
45
46    /// Removes own tiles from player's closed hand (assuming they exist).
47    pub fn consume_from_hand(self, hand: &mut TileSet37) {
48        hand[self.own[0]] -= 1;
49        hand[self.own[1]] -= 1;
50    }
51}
52
53impl Display for Pon {
54    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
55        let (n0, n1, nc, s) = (
56            self.own[0].num(),
57            self.own[1].num(),
58            self.called.num(),
59            self.called.suit_char(),
60        );
61        match self.dir.to_u8() {
62            1 => write!(f, "{}{}P{}{}", n0, n1, nc, s),
63            2 => write!(f, "{}P{}{}{}", n0, nc, n1, s),
64            3 => write!(f, "P{}{}{}{}", nc, n0, n1, s),
65            _ => Err(core::fmt::Error::default()),
66        }
67    }
68}
69
70impl TryFrom<PackedMeld> for Pon {
71    type Error = ();
72
73    fn try_from(raw: PackedMeld) -> Result<Self, Self::Error> {
74        if raw.kind() != PackedMeldKind::Pon as u8 { return Err(()); }
75        let t = raw.get_tile().ok_or(())?;
76        let (mut own0, mut own1, mut called) = (t, t, t);
77        let (r0, r1, r2, _) = unpack4(normalize_pon(raw.red()));
78        if r0 { own0 = own0.to_red(); }
79        if r1 { own1 = own1.to_red(); }
80        if r2 { called = called.to_red(); }
81        Pon::from_tiles_dir(own0, own1, called, Player::new(raw.dir())).ok_or(())
82    }
83}
84
85impl From<Pon> for PackedMeld {
86    fn from(pon: Pon) -> Self {
87        let [own0, own1] = pon.own;
88        PackedMeld::new()
89            .with_tile(own0.normal_encoding())
90            .with_dir(pon.dir.to_u8())
91            .with_red(pack4(own0.is_red(),
92                            own1.is_red(),
93                            pon.called.is_red(),
94                            false))
95            .with_kind(PackedMeldKind::Pon as u8)
96    }
97}