use super::hand;
use crate::constants::*;
use std::num::Wrapping;
use std::fs::File;
use std::env;
use std::path::{Path};
use std::iter::repeat;
use bytepack::{LEPacker, LEUnpacker};
const HASH_OFFSETS_FILENAME: &str = "offset_table.dat";
const HAND_CATEGORY_OFFSET: u16 = 0x1000;
const HAND_CATEGORY_SHIFT: u8 = 12;
const HIGH_CARD: u16 = 1 * 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;
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 MAX_KEY: usize = (4 * RANKS[12] + 3 * RANKS[11]) as usize + 1;
const RANK_TABLE_SIZE: usize = 86362;
const FLUSH_TABLE_SIZE: usize = 8192;
fn read_perf_hash_file(filename: &str) -> Result<Vec<u32>, std::io::Error> {
let out_dir = env::var("OUT_DIR").unwrap();
let fullpath = Path::new(&out_dir).join(filename);
let mut file = File::open(fullpath)?;
let num_samples : u32 = file.unpack()?;
let mut samples : Vec<u32> = repeat(0u32).take(num_samples as usize).collect();
file.unpack_exact(&mut samples[..]).unwrap();
Ok(samples)
}
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)] });
}
return key as usize;
}
pub fn evaluate(hand: &hand::Hand) -> u16 {
return LOOKUP_TABLE.evaluate(hand);
}
lazy_static! {
static ref LOOKUP_TABLE: Evaluator = Evaluator::init();
}
struct Evaluator {
orig_lookup: Vec<u16>,
rank_table: Vec<u16>,
flush_table: Vec<u16>,
calc_offsets: bool,
perf_hash_offsets: Vec<u32>
}
impl Evaluator {
pub fn init() -> Self {
let rank_table: Vec<u16>;
let orig_lookup: Vec<u16>;
let perf_hash_offsets: Vec<u32>;
let calc_offsets: bool;
match read_perf_hash_file(HASH_OFFSETS_FILENAME) {
Ok(offsets) => {
orig_lookup = Vec::with_capacity(0);
rank_table = vec![0; RANK_TABLE_SIZE];
perf_hash_offsets = offsets;
calc_offsets = false;
},
Err(_) => {
rank_table = vec![0; MAX_KEY + 1];
orig_lookup = vec![0; MAX_KEY + 1];
perf_hash_offsets = vec![0; 100000];
calc_offsets = true;
}
}
let mut eval = Evaluator {
orig_lookup,
perf_hash_offsets,
rank_table,
calc_offsets,
flush_table: vec![0; FLUSH_TABLE_SIZE],
};
eval.static_init();
if calc_offsets {
eval.recalculate_perfect_hash_offsets();
}
return eval;
}
pub fn evaluate(&self, hand: &hand::Hand) -> u16 {
if hand.has_flush() {
return self.flush_table[hand.get_flush_key()];
} else {
return self.rank_table[self.perf_hash(hand.get_rank_key())];
}
}
fn perf_hash(&self, key: usize) -> usize {
return (Wrapping(key as u32) + Wrapping(self.perf_hash_offsets[key >> PERF_HASH_ROW_SHIFT])).0 as usize
}
fn static_init(&mut self) {
let rc = RANK_COUNT;
let mut hand_value: u16 = 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 {
if self.calc_offsets {
self.orig_lookup[key] = *hand_value;
} else {
self.rank_table[
(Wrapping(key as u32)
+ Wrapping(self.perf_hash_offsets[key >> PERF_HASH_ROW_SHIFT])).0 as usize
] = *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 Evaluator::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);
}
return;
}
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;
}
return 0;
}
fn recalculate_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 in 0..rows.len() {
rows[i].0 = i;
}
rows.sort_by(|a, b| b.1.len().cmp(&a.1.len()));
let mut max_idx = 0usize;
for i in 0..rows.len() {
let mut offset = 0usize;
loop {
let mut ok = true;
for x in &rows[i].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[rows[i].0] = (offset as i32 - (rows[i].0 << PERF_HASH_ROW_SHIFT) as i32) as u32;
for key in &rows[i].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];
}
}
let fullpath = Path::new(&env::var("OUT_DIR").unwrap()).join(HASH_OFFSETS_FILENAME);
let mut file = File::create(fullpath).unwrap();
file.pack(rows.len() as u32).unwrap();
file.pack_all(&self.perf_hash_offsets[0..rows.len()]).unwrap();
self.rank_table.resize(max_idx + 1, 0);
self.orig_lookup = Vec::with_capacity(0);
}
}
#[cfg(test)]
mod tests {
use super::*;
use test::Bencher;
#[bench]
fn bench_lookup(b: &mut Bencher) {
let hand = hand::Hand::empty()
+ hand::CARDS[0]
+ hand::CARDS[1];
b.iter(|| evaluate(&hand));
}
#[test]
fn test_2222() {
let hand = hand::Hand::empty()
+ hand::CARDS[0]
+ hand::CARDS[1]
+ hand::CARDS[2]
+ hand::CARDS[3];
assert_eq!(8, evaluate(&hand) >> HAND_CATEGORY_SHIFT);
assert_eq!(32769, evaluate(&hand));
}
}