gen_eval_table/
lib.rs

1#![allow(clippy::too_many_arguments)]
2
3extern crate read_write;
4
5use read_write::VecIO;
6
7use std::env;
8use std::fs::File;
9use std::io::Result;
10use std::path::Path;
11
12const HAND_CATEGORY_OFFSET: u16 = 0x1000;
13// const HAND_CATEGORY_SHIFT: u8 = 12;
14const RANK_COUNT: u8 = 13;
15const MIN_CARDS: u8 = 2;
16const MAX_CARDS: u8 = 7;
17
18const PERF_HASH_ROW_SHIFT: usize = 12;
19const PERF_HASH_COLUMN_MASK: usize = (1 << PERF_HASH_ROW_SHIFT) - 1;
20
21const HIGH_CARD: u16 = HAND_CATEGORY_OFFSET;
22const PAIR: u16 = 2 * HAND_CATEGORY_OFFSET;
23const TWO_PAIR: u16 = 3 * HAND_CATEGORY_OFFSET;
24const THREE_OF_A_KIND: u16 = 4 * HAND_CATEGORY_OFFSET;
25const STRAIGHT: u16 = 5 * HAND_CATEGORY_OFFSET;
26const FLUSH: u16 = 6 * HAND_CATEGORY_OFFSET;
27const FULL_HOUSE: u16 = 7 * HAND_CATEGORY_OFFSET;
28const FOUR_OF_A_KIND: u16 = 8 * HAND_CATEGORY_OFFSET;
29const STRAIGHT_FLUSH: u16 = 9 * HAND_CATEGORY_OFFSET;
30
31/// Tables of unique primes for hashing hands
32pub const RANKS: &[u64; 13] = &[
33    8192, 32769, 69632, 237568, 593920, 1531909, 3563520, 4300819, 4685870, 4690024, 4767972,
34    4780561, 4801683,
35];
36/// Table of power of 2 flush ranks
37pub const FLUSH_RANKS: &[u64; 13] = &[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096];
38
39const PERF_HASH_FILENAME: &str = "h_eval_offsets.dat";
40const RANK_TABLE_FILENAME: &str = "h_eval_rank_table.dat";
41const FLUSH_TABLE_FILENAME: &str = "h_eval_flush_table.dat";
42
43const FLUSH_TABLE_SIZE: usize = 8192;
44// const RANK_TABLE_SIZE: usize = 86362;
45
46const MAX_KEY: usize = (4 * RANKS[12] + 3 * RANKS[11]) as usize;
47
48fn get_biggest_straight(ranks: u64) -> u8 {
49    let rank_mask: u64 =
50        (0x1111111111111 & ranks) | (0x2222222222222 & ranks) >> 1 | (0x4444444444444 & ranks) >> 2;
51    for i in (0..9).rev() {
52        if ((rank_mask >> (4 * i)) & 0x11111u64) == 0x11111u64 {
53            return i + 4;
54        }
55    }
56    if (rank_mask & 0x1000000001111) == 0x1000000001111 {
57        return 3;
58    }
59    0
60}
61
62fn get_key(ranks: u64, flush: bool) -> usize {
63    let mut key: u64 = 0;
64    for r in 0..RANK_COUNT {
65        key += ((ranks >> (r * 4)) & 0xf)
66            * (if flush {
67                FLUSH_RANKS[usize::from(r)]
68            } else {
69                RANKS[usize::from(r)]
70            });
71    }
72    key as usize
73}
74
75/// return true if asset file at path exists
76// fn hash_file_exists(filename: &str) -> bool {
77//     let out_dir = env::var("OUT_DIR").expect("CARGO_TARGET_DIR env var for perfect hash file not set");
78//     // let fullpath = Path::new(&out_dir).join(ASSET_FOLDER).join(filename);
79//     let fullpath = Path::new(&out_dir).join(filename);
80//     if File::open(fullpath).is_ok() {
81//         return true;
82//     }
83//     false
84// }
85
86struct EvalTableGenerator {
87    rank_table: Vec<u16>,
88    flush_table: Vec<u16>,
89    orig_lookup: Vec<u16>,
90    perf_hash_offsets: Vec<u32>,
91}
92
93impl EvalTableGenerator {
94    fn new() -> Self {
95        Self {
96            rank_table: vec![0u16; MAX_KEY + 1],
97            flush_table: vec![0; FLUSH_TABLE_SIZE],
98            orig_lookup: vec![0u16; MAX_KEY + 1],
99            perf_hash_offsets: vec![0u32; 1000000],
100        }
101    }
102    fn start(&mut self) {
103        self.generate_tables();
104        self.calc_perfect_hash_offsets();
105        self.write_files().unwrap();
106    }
107    fn generate_tables(&mut self) {
108        let rc = RANK_COUNT;
109        let mut hand_value = HIGH_CARD;
110        self.populate(0, 0, &mut hand_value, rc, 0, 0, 0, false);
111
112        hand_value = PAIR;
113        for r in 0..rc {
114            // 2u64 << 4 * rank, means pair for each rank
115            self.populate(2u64 << (4 * r), 2, &mut hand_value, rc, 0, 0, 0, false);
116        }
117
118        hand_value = TWO_PAIR;
119        for r1 in 0..rc {
120            for r2 in 0..r1 {
121                // (2u64 << 4 * r1) + (2u64 << 4 * r2)
122                // each two pair combination
123                self.populate(
124                    (2u64 << (4 * r1)) + (2u64 << (4 * r2)),
125                    4,
126                    &mut hand_value,
127                    rc,
128                    r2,
129                    0,
130                    0,
131                    false,
132                );
133            }
134        }
135
136        hand_value = THREE_OF_A_KIND;
137        for r in 0..rc {
138            // each three of a kind combo
139            self.populate(3u64 << (4 * r), 3, &mut hand_value, rc, 0, r, 0, false);
140        }
141
142        hand_value = STRAIGHT;
143        // A-5
144        self.populate(0x1000000001111u64, 5, &mut hand_value, rc, rc, rc, 3, false);
145        for r in 4..rc {
146            // every other straight
147            self.populate(
148                0x11111u64 << (4 * (r - 4)),
149                5,
150                &mut hand_value,
151                rc,
152                rc,
153                rc,
154                r,
155                false,
156            );
157        }
158
159        hand_value = FLUSH;
160        self.populate(0, 0, &mut hand_value, rc, 0, 0, 0, true);
161
162        // println!("ADDING FULL HOUSES");
163        hand_value = FULL_HOUSE;
164        for r1 in 0..rc {
165            for r2 in 0..rc {
166                if r2 != r1 {
167                    // r1's full of r2
168                    self.populate(
169                        (3u64 << (4 * r1)) + (2u64 << (4 * r2)),
170                        5,
171                        &mut hand_value,
172                        rc,
173                        r2,
174                        r1,
175                        rc,
176                        false,
177                    );
178                }
179            }
180        }
181
182        // println!("ADDING FOUR OF A KINDS");
183        hand_value = FOUR_OF_A_KIND;
184        for r in 0..rc {
185            self.populate(4u64 << (4 * r), 4, &mut hand_value, rc, rc, rc, rc, false);
186        }
187
188        // println!("ADDING STRAIGHT flush_table");
189        hand_value = STRAIGHT_FLUSH;
190        // A-5
191        self.populate(0x1000000001111u64, 5, &mut hand_value, rc, 0, 0, 3, true);
192        for r in 4..rc {
193            self.populate(
194                0x11111u64 << (4 * (r - 4)),
195                5,
196                &mut hand_value,
197                rc,
198                0,
199                0,
200                r,
201                true,
202            );
203        }
204    }
205
206    fn populate(
207        &mut self,
208        ranks: u64,
209        n_cards: u8,
210        hand_value: &mut u16,
211        end_rank: u8,
212        max_pair: u8,
213        max_trips: u8,
214        max_straight: u8,
215        flush: bool,
216    ) {
217        // only increment counter for 0-5 card combos
218        if (n_cards <= 5) && (n_cards >= MIN_CARDS) {
219            *hand_value += 1;
220        }
221
222        // write hand value to lookup table
223        if (n_cards >= MIN_CARDS) || (flush && n_cards >= 5) {
224            let key = get_key(ranks, flush);
225
226            if flush {
227                self.flush_table[key] = *hand_value;
228            } else {
229                self.orig_lookup[key] = *hand_value;
230            }
231
232            if n_cards == MAX_CARDS {
233                return;
234            }
235        }
236
237        // iterate next card rank
238        for r in 0..end_rank {
239            let new_ranks = ranks + (1u64 << (4 * r));
240            // check that hand doesn't improve
241            let rank_count = (new_ranks >> (r * 4)) & 0xf;
242
243            if (rank_count == 2) && (r >= max_pair) {
244                continue;
245            }
246            if (rank_count == 3) && (r >= max_trips) {
247                continue;
248            }
249            if rank_count >= 4 {
250                // cant be more than 1 pair of quads for each rank
251                continue;
252            }
253            if get_biggest_straight(new_ranks) > max_straight {
254                continue;
255            }
256
257            self.populate(
258                new_ranks,
259                n_cards + 1,
260                hand_value,
261                r + 1,
262                max_pair,
263                max_trips,
264                max_straight,
265                flush,
266            );
267        }
268    }
269    fn calc_perfect_hash_offsets(&mut self) {
270        let mut rows: Vec<(usize, Vec<usize>)> = Vec::new();
271        for i in 0..(MAX_KEY + 1) {
272            if self.orig_lookup[i] != 0 {
273                let row_idx = i >> PERF_HASH_ROW_SHIFT;
274                if row_idx >= rows.len() {
275                    rows.resize(row_idx + 1, (0, Vec::new()));
276                }
277                rows[row_idx].1.push(i);
278            }
279        }
280
281        for (i, row) in rows.iter_mut().enumerate() {
282            row.0 = i;
283        }
284        rows.sort_by(|a, b| b.1.len().cmp(&a.1.len()));
285
286        let mut max_idx = 0usize;
287        for row in &rows {
288            // for i in 0..rows.len() {
289            let mut offset = 0usize;
290            loop {
291                let mut ok = true;
292                for x in &row.1 {
293                    let val = self.rank_table[(*x & PERF_HASH_COLUMN_MASK) + offset];
294                    if val != 0 && val != self.orig_lookup[*x] {
295                        ok = false;
296                        break;
297                    }
298                }
299                if ok {
300                    break;
301                }
302                offset += 1;
303            }
304
305            self.perf_hash_offsets[row.0] =
306                (offset as i32 - (row.0 << PERF_HASH_ROW_SHIFT) as i32) as u32;
307
308            for key in &row.1 {
309                let new_idx = (*key & PERF_HASH_COLUMN_MASK) + offset;
310                max_idx = if new_idx > max_idx { new_idx } else { max_idx };
311                self.rank_table[new_idx] = self.orig_lookup[*key];
312            }
313        }
314        // free_memory
315        self.perf_hash_offsets.resize(rows.len(), 0);
316        self.rank_table.resize(max_idx + 1, 0);
317        self.orig_lookup = Vec::with_capacity(0);
318    }
319    fn write_files(&mut self) -> Result<()> {
320        // create folder
321        let out_dir = env::var("OUT_DIR").expect("OUT_DIR env var for perfect hash file not set");
322        let dir = Path::new(&out_dir);
323        // let dir = Path::new();
324        // std::fs::create_dir(dir.clone())?;
325        // write offsets
326        let hash_offsets_path = dir.join(PERF_HASH_FILENAME);
327        let mut hash_offsets_file = File::create(hash_offsets_path)?;
328        hash_offsets_file.write_slice_to_file::<u32>(&self.perf_hash_offsets.as_slice())?;
329        // write rank table
330        let rank_table_path = dir.join(RANK_TABLE_FILENAME);
331        let mut rank_table_file = File::create(rank_table_path)?;
332        rank_table_file.write_slice_to_file::<u16>(&self.rank_table.as_slice())?;
333        // write flush table
334        let flush_table_path = dir.join(FLUSH_TABLE_FILENAME);
335        let mut flush_table_file = File::create(flush_table_path)?;
336        flush_table_file.write_slice_to_file::<u16>(&self.flush_table.as_slice())?;
337
338        Ok(())
339    }
340}
341
342pub fn gen_eval_table() {
343    let out_dir = env::var("OUT_DIR").expect("OUT_DIR env var for perfect hash file not set");
344    let fullpath = Path::new(&out_dir);
345
346    if fullpath.join(PERF_HASH_FILENAME).exists()
347    && fullpath.join(RANK_TABLE_FILENAME).exists()
348    && fullpath.join(FLUSH_TABLE_FILENAME).exists() {
349        return;
350    }
351
352    let mut generator = EvalTableGenerator::new();
353    generator.start();
354}