#![allow(clippy::too_many_arguments)]
extern crate read_write;
use read_write::VecIO;
use std::env;
use std::fs::File;
use std::io::Result;
use std::path::Path;
const HAND_CATEGORY_OFFSET: u16 = 0x1000;
const RANK_COUNT: u8 = 13;
const MIN_CARDS: u8 = 2;
const MAX_CARDS: u8 = 7;
const PERF_HASH_ROW_SHIFT: usize = 12;
const PERF_HASH_COLUMN_MASK: usize = (1 << PERF_HASH_ROW_SHIFT) - 1;
const HIGH_CARD: u16 = HAND_CATEGORY_OFFSET;
const PAIR: u16 = 2 * HAND_CATEGORY_OFFSET;
const TWO_PAIR: u16 = 3 * HAND_CATEGORY_OFFSET;
const THREE_OF_A_KIND: u16 = 4 * HAND_CATEGORY_OFFSET;
const STRAIGHT: u16 = 5 * HAND_CATEGORY_OFFSET;
const FLUSH: u16 = 6 * HAND_CATEGORY_OFFSET;
const FULL_HOUSE: u16 = 7 * HAND_CATEGORY_OFFSET;
const FOUR_OF_A_KIND: u16 = 8 * HAND_CATEGORY_OFFSET;
const STRAIGHT_FLUSH: u16 = 9 * HAND_CATEGORY_OFFSET;
pub const RANKS: &[u64; 13] = &[
8192, 32769, 69632, 237568, 593920, 1531909, 3563520, 4300819, 4685870, 4690024, 4767972,
4780561, 4801683,
];
pub const FLUSH_RANKS: &[u64; 13] = &[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096];
const PERF_HASH_FILENAME: &str = "h_eval_offsets.dat";
const RANK_TABLE_FILENAME: &str = "h_eval_rank_table.dat";
const FLUSH_TABLE_FILENAME: &str = "h_eval_flush_table.dat";
const FLUSH_TABLE_SIZE: usize = 8192;
const MAX_KEY: usize = (4 * RANKS[12] + 3 * RANKS[11]) as usize;
fn get_biggest_straight(ranks: u64) -> u8 {
let rank_mask: u64 =
(0x1111111111111 & ranks) | (0x2222222222222 & ranks) >> 1 | (0x4444444444444 & ranks) >> 2;
for i in (0..9).rev() {
if ((rank_mask >> (4 * i)) & 0x11111u64) == 0x11111u64 {
return i + 4;
}
}
if (rank_mask & 0x1000000001111) == 0x1000000001111 {
return 3;
}
0
}
fn get_key(ranks: u64, flush: bool) -> usize {
let mut key: u64 = 0;
for r in 0..RANK_COUNT {
key += ((ranks >> (r * 4)) & 0xf)
* (if flush {
FLUSH_RANKS[usize::from(r)]
} else {
RANKS[usize::from(r)]
});
}
key as usize
}
struct EvalTableGenerator {
rank_table: Vec<u16>,
flush_table: Vec<u16>,
orig_lookup: Vec<u16>,
perf_hash_offsets: Vec<u32>,
}
impl EvalTableGenerator {
fn new() -> Self {
Self {
rank_table: vec![0u16; MAX_KEY + 1],
flush_table: vec![0; FLUSH_TABLE_SIZE],
orig_lookup: vec![0u16; MAX_KEY + 1],
perf_hash_offsets: vec![0u32; 1000000],
}
}
fn start(&mut self) {
self.generate_tables();
self.calc_perfect_hash_offsets();
self.write_files().unwrap();
}
fn generate_tables(&mut self) {
let rc = RANK_COUNT;
let mut hand_value = HIGH_CARD;
self.populate(0, 0, &mut hand_value, rc, 0, 0, 0, false);
hand_value = PAIR;
for r in 0..rc {
self.populate(2u64 << (4 * r), 2, &mut hand_value, rc, 0, 0, 0, false);
}
hand_value = TWO_PAIR;
for r1 in 0..rc {
for r2 in 0..r1 {
self.populate(
(2u64 << (4 * r1)) + (2u64 << (4 * r2)),
4,
&mut hand_value,
rc,
r2,
0,
0,
false,
);
}
}
hand_value = THREE_OF_A_KIND;
for r in 0..rc {
self.populate(3u64 << (4 * r), 3, &mut hand_value, rc, 0, r, 0, false);
}
hand_value = STRAIGHT;
self.populate(0x1000000001111u64, 5, &mut hand_value, rc, rc, rc, 3, false);
for r in 4..rc {
self.populate(
0x11111u64 << (4 * (r - 4)),
5,
&mut hand_value,
rc,
rc,
rc,
r,
false,
);
}
hand_value = FLUSH;
self.populate(0, 0, &mut hand_value, rc, 0, 0, 0, true);
hand_value = FULL_HOUSE;
for r1 in 0..rc {
for r2 in 0..rc {
if r2 != r1 {
self.populate(
(3u64 << (4 * r1)) + (2u64 << (4 * r2)),
5,
&mut hand_value,
rc,
r2,
r1,
rc,
false,
);
}
}
}
hand_value = FOUR_OF_A_KIND;
for r in 0..rc {
self.populate(4u64 << (4 * r), 4, &mut hand_value, rc, rc, rc, rc, false);
}
hand_value = STRAIGHT_FLUSH;
self.populate(0x1000000001111u64, 5, &mut hand_value, rc, 0, 0, 3, true);
for r in 4..rc {
self.populate(
0x11111u64 << (4 * (r - 4)),
5,
&mut hand_value,
rc,
0,
0,
r,
true,
);
}
}
fn populate(
&mut self,
ranks: u64,
n_cards: u8,
hand_value: &mut u16,
end_rank: u8,
max_pair: u8,
max_trips: u8,
max_straight: u8,
flush: bool,
) {
if (n_cards <= 5) && (n_cards >= MIN_CARDS) {
*hand_value += 1;
}
if (n_cards >= MIN_CARDS) || (flush && n_cards >= 5) {
let key = get_key(ranks, flush);
if flush {
self.flush_table[key] = *hand_value;
} else {
self.orig_lookup[key] = *hand_value;
}
if n_cards == MAX_CARDS {
return;
}
}
for r in 0..end_rank {
let new_ranks = ranks + (1u64 << (4 * r));
let rank_count = (new_ranks >> (r * 4)) & 0xf;
if (rank_count == 2) && (r >= max_pair) {
continue;
}
if (rank_count == 3) && (r >= max_trips) {
continue;
}
if rank_count >= 4 {
continue;
}
if get_biggest_straight(new_ranks) > max_straight {
continue;
}
self.populate(
new_ranks,
n_cards + 1,
hand_value,
r + 1,
max_pair,
max_trips,
max_straight,
flush,
);
}
}
fn calc_perfect_hash_offsets(&mut self) {
let mut rows: Vec<(usize, Vec<usize>)> = Vec::new();
for i in 0..(MAX_KEY + 1) {
if self.orig_lookup[i] != 0 {
let row_idx = i >> PERF_HASH_ROW_SHIFT;
if row_idx >= rows.len() {
rows.resize(row_idx + 1, (0, Vec::new()));
}
rows[row_idx].1.push(i);
}
}
for (i, row) in rows.iter_mut().enumerate() {
row.0 = i;
}
rows.sort_by(|a, b| b.1.len().cmp(&a.1.len()));
let mut max_idx = 0usize;
for row in &rows {
let mut offset = 0usize;
loop {
let mut ok = true;
for x in &row.1 {
let val = self.rank_table[(*x & PERF_HASH_COLUMN_MASK) + offset];
if val != 0 && val != self.orig_lookup[*x] {
ok = false;
break;
}
}
if ok {
break;
}
offset += 1;
}
self.perf_hash_offsets[row.0] =
(offset as i32 - (row.0 << PERF_HASH_ROW_SHIFT) as i32) as u32;
for key in &row.1 {
let new_idx = (*key & PERF_HASH_COLUMN_MASK) + offset;
max_idx = if new_idx > max_idx { new_idx } else { max_idx };
self.rank_table[new_idx] = self.orig_lookup[*key];
}
}
self.perf_hash_offsets.resize(rows.len(), 0);
self.rank_table.resize(max_idx + 1, 0);
self.orig_lookup = Vec::with_capacity(0);
}
fn write_files(&mut self) -> Result<()> {
let out_dir = env::var("OUT_DIR").expect("OUT_DIR env var for perfect hash file not set");
let dir = Path::new(&out_dir);
let hash_offsets_path = dir.join(PERF_HASH_FILENAME);
let mut hash_offsets_file = File::create(hash_offsets_path)?;
hash_offsets_file.write_slice_to_file::<u32>(&self.perf_hash_offsets.as_slice())?;
let rank_table_path = dir.join(RANK_TABLE_FILENAME);
let mut rank_table_file = File::create(rank_table_path)?;
rank_table_file.write_slice_to_file::<u16>(&self.rank_table.as_slice())?;
let flush_table_path = dir.join(FLUSH_TABLE_FILENAME);
let mut flush_table_file = File::create(flush_table_path)?;
flush_table_file.write_slice_to_file::<u16>(&self.flush_table.as_slice())?;
Ok(())
}
}
pub fn gen_eval_table() {
let out_dir = env::var("OUT_DIR").expect("OUT_DIR env var for perfect hash file not set");
let fullpath = Path::new(&out_dir);
if fullpath.join(PERF_HASH_FILENAME).exists()
&& fullpath.join(RANK_TABLE_FILENAME).exists()
&& fullpath.join(FLUSH_TABLE_FILENAME).exists() {
return;
}
let mut generator = EvalTableGenerator::new();
generator.start();
}