riichi_decomp_table/
c_table.rs1use 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
12pub const C_TABLE_NUM_KEYS: usize = 21743;
15
16pub type CAlts = NanoStackBit<u64, u16, 16>;
22
23pub 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 pub const fn k_to_ks(i: u8) -> u8 { if i == 8 { 0xF } else { i * 2 } }
117 pub const fn s_to_ks(i: u8) -> u8 { i * 2 + 1 }
119
120 pub const fn k_key(i: u8) -> u32 { 3 << ((i as u32) * 3) }
122 pub const fn s_key(i: u8) -> u32 { 0o111 << ((i as u32) * 3) }
124 pub const fn ks_key(ks: u8) -> u32 {
126 const TABLE: [u32; 16] = [
127 k_key(0), s_key(0), k_key(1), s_key(1),
129 k_key(2), s_key(2), k_key(3), s_key(3),
131 k_key(4), s_key(4), k_key(5), s_key(5),
133 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}