riichi_elements/tile_set/
tile_set_34.rs

1use core::fmt::{Display, Formatter};
2use core::ops::{Index, IndexMut};
3
4use super::TileSet37;
5
6use derive_more::{
7    Constructor, From, Into, IntoIterator, Index, IndexMut,
8};
9
10use crate::tile::Tile;
11
12/// Histogram for all 34 kinds of normal tiles (red 5's are treated as normal 5's).
13/// Can be directly indexed with [`Tile`].
14#[derive(Clone, Debug, Eq, PartialEq, Constructor, From, Into, IntoIterator, Index, IndexMut)]
15pub struct TileSet34(pub [u8; 34]);
16
17impl Index<Tile> for TileSet34 {
18    type Output = u8;
19    fn index(&self, tile: Tile) -> &Self::Output {
20        &self.0[tile.normal_encoding() as usize]  // NOTE: different
21    }
22}
23
24impl IndexMut<Tile> for TileSet34 {
25    fn index_mut(&mut self, tile: Tile) -> &mut Self::Output {
26        &mut self.0[tile.normal_encoding() as usize]  // NOTE: different
27    }
28}
29
30impl Default for TileSet34 {
31    fn default() -> Self { TileSet34([0u8; 34]) }
32}
33
34impl Display for TileSet34 {
35    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
36        for xs in self.0.chunks(9) {
37            for x in xs {
38                write!(f, "{}", x)?;
39            }
40            write!(f, ",")?;
41        }
42        Ok(())
43    }
44}
45
46// Conversion is one-way from 37 to 34 (count of red is lost).
47impl From<&TileSet37> for TileSet34 {
48    fn from(original: &TileSet37) -> Self {
49        let mut result: [u8; 34] = (original[..34]).try_into().unwrap();
50        result[4] += original[34];
51        result[13] += original[35];
52        result[22] += original[36];
53        Self(result)
54    }
55}
56
57impl FromIterator<Tile> for TileSet34 {
58    fn from_iter<T: IntoIterator<Item=Tile>>(tiles: T) -> Self {
59        let mut ts = Self::default();
60        for tile in tiles {
61            ts[tile.to_normal()] += 1;
62        }
63        ts
64    }
65}
66
67impl TileSet34 {
68    /// An empty tile set.
69    pub const fn empty_set() -> Self { TileSet34([0; 34]) }
70
71    /// The complete set of tiles in a game, without differentiating red 5's from normal 5's.
72    /// The total number of tiles is 136 (34 x 4).
73    pub const fn complete_set() -> Self { TileSet34([4; 34]) }
74
75    /// Reconstructs the histogram from its [packed](Self::packed_34) representation.
76    pub fn from_packed(packed: [u32; 4]) -> Self {
77        let mut ts34 = Self::default();
78        let mut i = 0;
79        for s in 0..3 {
80            let mut m = packed[s];
81            for _ in 0..9 {
82                ts34[i] = (m & 0o7) as u8;
83                i += 1;
84                m >>= 3;
85            }
86        }
87        let mut m = packed[3];
88        for _ in 0..7 {
89            ts34[i] = (m & 0o7) as u8;
90            i += 1;
91            m >>= 3;
92        }
93        ts34
94    }
95
96    /// Compresses the histogram so that each element takes 3 bits (valid range `0..=4`).
97    /// This results in 4 x 27-bit integers, one for each suit.
98    ///
99    /// Conveniently, this is 1 digit per element in octal.
100    pub fn packed_34(&self) -> [u32; 4] {
101        let mut packed = [0u32; 4];
102        let h = &self.0;
103        for i in (0..34).rev() {
104            let s = i / 9;
105            packed[s] = (packed[s] << 3) | (h[i] as u32);
106        }
107        packed
108    }
109
110    /// Iterates through all tiles in this tile set, in encoding order.
111    pub fn iter_tiles(&self) -> impl Iterator<Item=Tile> {
112        self.0.into_iter().enumerate().flat_map(|(encoding, count)|
113            itertools::repeat_n(
114                Tile::from_encoding(encoding as u8).unwrap(),
115                count as usize))
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use crate::tile::*;
123
124    #[test]
125    fn ts34_treats_red_as_normal() {
126        let mut h = TileSet34::default();
127        h[t!("5m")] = 1;
128        h[t!("0p")] = 2;
129        h[t!("6s")] = 3;
130        assert_eq!(h, [
131            0, 0, 0, 0, 1, 0, 0, 0, 0,
132            0, 0, 0, 0, 2, 0, 0, 0, 0,
133            0, 0, 0, 0, 0, 3, 0, 0, 0,
134            0, 0, 0, 0, 0, 0, 0,
135        ].into());
136    }
137
138    #[test]
139    fn ts34_packs_correctly() {
140        let h = TileSet34::from_iter(tiles_from_str("147m258p369s77z"));
141        assert_eq!(h.packed_34(), [
142            0o001001001,
143            0o010010010,
144            0o100100100,
145            0o2000000,
146        ]);
147        assert_eq!(TileSet34::from_packed(h.packed_34()), h);
148    }
149}