riichi_decomp_table/
w_table.rs

1use nanovec::NanoStackRadix;
2use rustc_hash::FxHashMap as HashMap;
3
4use crate::utils::*;
5use details::*;
6
7/*
8#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
9pub struct WEntry {
10    pub big: u64,
11    pub tenpai_type: u8,
12    pub tenpai_num: u8,
13    pub anchor_num: u8,
14    pub has_pair: bool,
15}
16// pub type WTable = HashMap<u32, Vec<WEntry>>;
17 */
18
19pub type WTable = HashMap<u32, WAlts>;
20pub type WTableStatic = phf::Map<u32, u64>;
21
22pub type WAlts = NanoStackRadix<u64, 55>;
23
24/// Since the WTable is statically determinable, we can check its number of keys to make sure that
25/// we have generated the correct table.
26pub const W_TABLE_NUM_KEYS: usize = 66913;
27
28pub fn make_w_table(c_table: &super::c_table::CTable) -> WTable {
29    let mut w_table = WTable::with_capacity_and_hasher(
30        W_TABLE_NUM_KEYS, Default::default());
31    for key in c_table.keys() {
32        make_waiting_for_c_entry(&mut w_table, *key);
33    }
34    w_table
35}
36
37fn make_waiting_for_c_entry(w_table: &mut WTable, key: u32) {
38    let num_tiles = key_sum(key);
39    let num_complete_groups = num_tiles / 3;
40    let has_pair = (num_tiles % 3) == 2;
41
42    let mut push = |new_key, pos: i8, waiting_kind: WaitingKind, _has_pair: bool| {
43        w_table.entry(new_key).or_default().push(pack_alt(waiting_kind, pos) as u64)
44    };
45
46    if !has_pair {
47        for pos in 0..=8 {
48            if let Some(new_key) = check_pattern(key, 0o1, pos, 0) {
49                push(new_key, pos, WaitingKind::Tanki, true);
50            }
51        }
52    }
53    if num_complete_groups <= 3 {
54        // only 3 or less mentsu in complete part
55        // try add mentsu-based tenpai pattern
56        for pos in 0..=8 {
57            if let Some(new_key) = check_pattern(key, 0o2, pos, 0) {
58                push(new_key, pos, WaitingKind::Shanpon, has_pair);
59            }
60        }
61        for pos in 0..=6 {
62            if let Some(new_key) = check_pattern(key, 0o101, pos, 1) {
63                push(new_key, pos, WaitingKind::Kanchan, has_pair);
64            }
65        }
66        for pos in 0..=7 {
67            let key_low = check_pattern(key, 0o11, pos, -1);
68            let key_high = check_pattern(key, 0o11, pos, 2);
69            if key_low.is_some() && key_high.is_some() {
70                push(key_low.unwrap(), pos, WaitingKind::RyanmenBoth, has_pair);
71            } else if let Some(key) = key_low {
72                push(key, pos, WaitingKind::RyanmenLow, has_pair);
73            } else if let Some(key) = key_high {
74                push(key, pos, WaitingKind::RyanmenHigh, has_pair);
75            }
76        }
77    }
78}
79
80#[derive(Copy, Clone, Debug, Eq, PartialEq)]
81pub struct WaitingPattern {
82    pub complete_key: u32,
83    pub waiting_kind: WaitingKind,
84    pub pattern_pos: u8,
85}
86
87#[derive(
88    Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd,
89    strum::FromRepr, strum::AsRefStr, strum::EnumString,
90)]
91#[repr(u8)]
92pub enum WaitingKind {
93    #[default]
94    Tanki = 0,   // e.g. 1222333444555z wait 1z
95    Shanpon,     // e.g. 4477s wait 4s/7s
96    Kanchan,     // e.g. 13m wait 2m
97    RyanmenHigh, // e.g. 12m wait 3m, 12333345m77z wait 6m (because 3m is used up)
98    RyanmenLow,  // e.g. 89m wait 7m, ditto
99    RyanmenBoth, // e.g. 34m wait 2m/5m
100}
101
102impl WaitingKind {
103    pub const fn is_shuntsu(self) -> bool {
104        use WaitingKind::*;
105        matches!(self, Kanchan | RyanmenHigh | RyanmenLow | RyanmenBoth)
106    }
107
108    pub const fn pattern(self) -> u32 {
109        use WaitingKind::*;
110        match self {
111            Tanki => 0o1,
112            Shanpon => 0o2,
113            Kanchan => 0o101,
114            RyanmenHigh | RyanmenLow | RyanmenBoth => 0o11,
115        }
116    }
117
118    pub const fn pattern_at(self, pos: u8) -> u32 {
119        self.pattern() << ((pos as u32) * 3)
120    }
121}
122
123pub fn w_entry_iter(key: u32, packed_alts: u64) -> impl Iterator<Item = WaitingPattern> {
124    w_entry_iter_alts(key, WAlts::from_packed(packed_alts))
125}
126
127pub fn w_entry_iter_alts(key: u32, alts: WAlts) -> impl Iterator<Item = WaitingPattern> {
128    alts
129        .map(|packed| unpack_alt(packed as u8))
130        .map(move |(waiting_kind, pos)| {
131            let complete_key = key - waiting_kind.pattern_at(pos);
132            WaitingPattern{
133                complete_key,
134                waiting_kind,
135                pattern_pos: pos,
136            }
137        })
138}
139
140pub(crate) mod details {
141    use crate::utils::*;
142    use super::*;
143
144    /// Attempts to place a waiting pattern at the position, and see if we are not attempting to use
145    /// more than 4 tiles of each kind, including the waiting tile. If this attempt is valid,
146    /// return `Some(new_key)`; otherwise `None`.
147    pub fn check_pattern(key: u32, pattern: u8, pos: i8, waiting_offset: i8) -> Option<u32> {
148        let new_key = key + ((pattern as u32) << ((pos as u32) * 3));
149        let waiting_pos = pos + waiting_offset;
150        if (0..=8).contains(&waiting_pos) &&
151            !key_is_overflow(new_key) &&
152            ((new_key >> ((waiting_pos as u32) * 3)) & 0o7) < 4 {
153            Some(new_key)
154        } else {
155            None
156        }
157    }
158
159    /// Encode (waiting_kind, pos) first into a "waiting alt" (1..55)
160    ///
161    /// Explained:
162    /// - `waiting kind`: 0..6
163    /// - `pos`: 0..9
164    /// Total combinations: 6*9 = 54
165    pub fn pack_alt(waiting_kind: WaitingKind, pos: i8) -> u8 {
166        9 * (waiting_kind as u8) + (pos as u8) + 1
167    }
168
169    /// Decode (waiting_kind, pos) from a packed "waiting alt" (1..55)
170    ///
171    /// See [`pack_alt`].
172    pub fn unpack_alt(packed: u8) -> (WaitingKind, u8) {
173        let x = packed - 1;
174        (WaitingKind::from_repr(x / 9).unwrap(), x % 9)
175    }
176}