Skip to main content

riichi_decomp_table/
c_table.rs

1//! Complete hand decomposition
2
3use nanovec::{NanoArray, NanoArrayBit, NanoDeque, NanoStackBit};
4use rustc_hash::FxHashMap as HashMap;
5
6use crate::utils::*;
7use details::*;
8
9pub type CTable = HashMap<u32, CAlts>;
10pub type CTableStatic = phf::Map<u32, u64>;
11
12/// Since the CTable is statically determinable, we can check its number of keys to make sure that
13/// we have generated the correct table.
14pub const C_TABLE_NUM_KEYS: usize = 21743;
15
16/// Each entry of the C-Table is 1..=4 "alternatives", each consists of 1..=4 [`CGroups`].
17///
18/// Each alternative is stored as the bitwise inverse of the bit-packed groups array.
19/// Since `0xFFFF` is not a valid alternative, we can avoid storing its inverse, which is zero.
20/// This allows us to use [`NanoStackBit`] to encode the number of alternatives implicitly.
21pub type CAlts = NanoStackBit<u64, u16, 16>;
22
23/// Each group is in 0..16 (prefix of suited HandGroup encoding in the main package).
24/// The number of groups is implicit by the hand (len == num_tiles / 3).
25pub type CGroups = NanoArrayBit<u16, u8, 4>;
26
27pub fn make_c_table() -> CTable {
28    let mut table = CTable::with_capacity_and_hasher(
29        C_TABLE_NUM_KEYS, Default::default());
30    let jantou_only_alts = CAlts::new().with(!0);
31    table.insert(0, jantou_only_alts);
32    dfs_koutsu(&mut table, 0, 0, 0, CGroups::new());
33    dfs_shuntsu(&mut table, 0, 0, 0, CGroups::new());
34    for j in 0..=8 {
35        let j_key = 2 << ((j as u32) * 3);
36        table.insert(j_key, jantou_only_alts);
37        dfs_koutsu(&mut table, 0, 0, j_key, CGroups::new());
38        dfs_shuntsu(&mut table, 0, 0, j_key, CGroups::new());
39    }
40    table
41}
42
43fn dfs_koutsu(table: &mut CTable, i: u8, pos0: u8, key: u32, groups: CGroups) {
44    for pos in pos0..=8 {
45        let new_key = key + k_key(pos);
46        if key_is_overflow(new_key) { continue; }
47        let new_groups = groups.with(i as usize, k_to_ks(pos));
48
49        table.entry(new_key).or_insert(CAlts::new()).push(!new_groups.packed());
50
51        if i < 3 {
52            dfs_koutsu(table, i + 1, pos + 1, new_key, new_groups);
53            dfs_shuntsu(table, i + 1, 0, new_key, new_groups);
54        }
55    }
56}
57
58fn dfs_shuntsu(table: &mut CTable, i: u8, pos0: u8, key: u32, groups: CGroups) {
59    for pos in pos0..=6 {
60        let new_key = key + s_key(pos);
61        if key_is_overflow(new_key) { continue; }
62        let new_groups = groups.with(i as usize, s_to_ks(pos));
63
64        table.entry(new_key).or_default().push(!new_groups.packed());
65
66        if i < 3 {
67            dfs_shuntsu(table, i + 1, pos, new_key, new_groups);
68        }
69    }
70}
71
72#[derive(Copy, Clone, Debug, Eq, PartialEq)]
73pub struct CompleteGrouping {
74    pub groups: NanoDeque<CGroups>,
75    raw_pair: u8,
76}
77
78impl CompleteGrouping {
79    pub fn has_shuntsu(self) -> bool {
80        any_group_is_shuntsu(self.groups.packed())
81    }
82    pub fn pair(self) -> Option<u8> {
83        (self.raw_pair != u8::MAX).then_some(self.raw_pair)
84    }
85}
86
87pub fn c_entry_iter(key: u32, alts_packed: u64) -> impl Iterator<Item = CompleteGrouping> {
88    c_entry_iter_alts(key, CAlts::from_packed(alts_packed))
89}
90
91pub fn c_entry_iter_alts(key: u32, alts: CAlts) -> impl Iterator<Item = CompleteGrouping> {
92    let n = key_sum(key);
93    let num_groups = n / 3;
94    let has_pair = n % 3 == 2;
95    alts.map(move |inv_groups_packed| {
96        let groups = CGroups::from_packed(!inv_groups_packed);
97        let raw_pair = if has_pair {
98            recover_known_pair(key, groups, num_groups as usize)
99        } else {
100            u8::MAX
101        };
102        CompleteGrouping {
103            groups: NanoDeque::<CGroups>::from_packed_len(
104                !inv_groups_packed, num_groups as u8),
105            raw_pair,
106        }
107    })
108
109}
110
111pub(crate) mod details {
112    use nanovec::NanoArray;
113    use super::{CGroups};
114
115    /// Encodes a Koutsu(0..=8) into a Group(0..=0xF)
116    pub const fn k_to_ks(i: u8) -> u8 { if i == 8 { 0xF } else { i * 2 } }
117    /// Encodes a Shuntsu(0..=6) into a Group(1..=0xD)
118    pub const fn s_to_ks(i: u8) -> u8 { i * 2 + 1 }
119
120    /// Packed hand of a Koutsu at `i`.
121    pub const fn k_key(i: u8) -> u32 { 3 << ((i as u32) * 3) }
122    /// Packed hand of a Shuntsu at `i`.
123    pub const fn s_key(i: u8) -> u32 { 0o111 << ((i as u32) * 3) }
124    /// Packed hand of an encoded Group (either Koutsu or Shuntsu).
125    pub const fn ks_key(ks: u8) -> u32 {
126        const TABLE: [u32; 16] = [
127            // 111, 123, 222, 234,
128            k_key(0), s_key(0), k_key(1), s_key(1),
129            // 333, 345, 444, 456,
130            k_key(2), s_key(2), k_key(3), s_key(3),
131            // 555, 567, 666, 678,
132            k_key(4), s_key(4), k_key(5), s_key(5),
133            // 777, 789, 888, 999,
134            k_key(6), s_key(6), k_key(7), k_key(8),
135        ];
136        TABLE[ks as usize]
137    }
138
139    pub fn any_group_is_shuntsu(packed_groups: u16) -> bool {
140        let mask = !((((packed_groups >> 1) & 0x7777) + 0x1111) >> 3) & 0x1111 & packed_groups;
141        mask != 0
142    }
143
144    pub fn groups_to_key(mid: CGroups, num_groups: usize) -> u32 {
145        (0..num_groups).map(|i| mid.get(i)).map(ks_key).sum()
146    }
147
148    pub fn recover_known_pair(key: u32, groups: CGroups, num_groups: usize) -> u8 {
149        let pair_key = key - groups_to_key(groups, num_groups);
150        (pair_key.trailing_zeros() / 3) as u8
151    }
152}