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;
13const 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
31pub const RANKS: &[u64; 13] = &[
33 8192, 32769, 69632, 237568, 593920, 1531909, 3563520, 4300819, 4685870, 4690024, 4767972,
34 4780561, 4801683,
35];
36pub 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;
44const 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
75struct 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 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 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 self.populate(3u64 << (4 * r), 3, &mut hand_value, rc, 0, r, 0, false);
140 }
141
142 hand_value = STRAIGHT;
143 self.populate(0x1000000001111u64, 5, &mut hand_value, rc, rc, rc, 3, false);
145 for r in 4..rc {
146 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 hand_value = FULL_HOUSE;
164 for r1 in 0..rc {
165 for r2 in 0..rc {
166 if r2 != r1 {
167 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 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 hand_value = STRAIGHT_FLUSH;
190 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 if (n_cards <= 5) && (n_cards >= MIN_CARDS) {
219 *hand_value += 1;
220 }
221
222 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 for r in 0..end_rank {
239 let new_ranks = ranks + (1u64 << (4 * r));
240 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 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 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 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 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 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 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 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}