1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use super::hand;

use read_write::VecIO;

// use rust_embed::RustEmbed;

use std::num::Wrapping;
use std::fs::File;
use std::env;

/// Must point to same directory as `gen_eval_table`
// #[derive(RustEmbed)]
// #[folder = "eval_table"]
// struct Asset;

/// filename to write and read perf hash offset table
// const OUT_DIR: &str = "STATIC_ASSET_DIR";
// 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 PERF_HASH_ROW_SHIFT: usize = 12;

/// Evaluates a single hand and returns score
pub fn evaluate(hand: &hand::Hand) -> u16 {
    LOOKUP_TABLE.evaluate(hand)
}

lazy_static! {
    /// Global static lookup table used for evaluation
    static ref LOOKUP_TABLE: Evaluator = Evaluator::load();
}

/// Singleton structure
struct Evaluator {
    /// Stores scores of non flush hands
    rank_table: Vec<u16>,
    /// Stores scores of flush hands
    flush_table: Vec<u16>,
    /// Stores offsets to rank table
    perf_hash_offsets: Vec<u32>,
}

impl Evaluator {
    pub fn load() -> Self {
        let perf_hash_file = concat!(env!("OUT_DIR"), "/h_eval_offsets.dat");
        let flush_table_file = concat!(env!("OUT_DIR"), "/h_eval_flush_table.dat");
        let rank_table_file = concat!(env!("OUT_DIR"), "/h_eval_rank_table.dat");
        Self {
            rank_table: File::open(rank_table_file)
                .unwrap()
                .read_vec_from_file::<u16>().unwrap(),
            flush_table: File::open(flush_table_file)
                .unwrap()
                .read_vec_from_file::<u16>().unwrap(),
            perf_hash_offsets: File::open(perf_hash_file)
                .unwrap()
                .read_vec_from_file::<u32>().unwrap(),
        }
    }

    pub fn evaluate(&self, hand: &hand::Hand) -> u16 {
        if hand.has_flush() {
            self.flush_table[hand.get_flush_key()]
        } else {
            self.rank_table[self.perf_hash(hand.get_rank_key())]
        }
    }

    fn perf_hash(&self, key: usize) -> usize {
        // works because of overflow
        (Wrapping(key as u32) + Wrapping(self.perf_hash_offsets[key >> PERF_HASH_ROW_SHIFT])).0
            as usize
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::constants::HAND_CATEGORY_SHIFT;
    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_four_of_a_kind() {
        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));
    }

    #[test]
    fn test_trips() {
        let hand = hand::Hand::empty() + hand::CARDS[0] + hand::CARDS[1] + hand::CARDS[2];
        assert_eq!(4, evaluate(&hand) >> HAND_CATEGORY_SHIFT);
    }

    #[test]
    fn test_pair() {
        let hand = hand::Hand::empty() + hand::CARDS[0] + hand::CARDS[1];
        assert_eq!(2, evaluate(&hand) >> HAND_CATEGORY_SHIFT);
    }

    #[test]
    fn test_highcard() {
        let hand = hand::Hand::empty() + hand::CARDS[0] + hand::CARDS[5];
        assert_eq!(1, evaluate(&hand) >> HAND_CATEGORY_SHIFT);
    }
}