Skip to content

Commit

Permalink
Merge branch 'release/v1.6'
Browse files Browse the repository at this point in the history
  • Loading branch information
kennethshackleton committed Feb 21, 2017
2 parents 871bcf7 + 8e6da66 commit 24c71be
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 55 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
### Change Log

#### 1.6.0

* Optimise ranking flushes.

#### 1.5.0

* Static interface.
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Expand Up @@ -5,7 +5,7 @@ project(${PROJECT_NAME})

# Versioning.
set(SK_POKER_EVAL_VERSION_MAJOR 1)
set(SK_POKER_EVAL_VERSION_MINOR 5)
set(SK_POKER_EVAL_VERSION_MINOR 6)
set(SK_POKER_EVAL_VERSION_PATCH 0)

# Get the current commit.
Expand Down
26 changes: 24 additions & 2 deletions README.md
Expand Up @@ -6,9 +6,9 @@ A lightweight 32-bit Texas Hold'em 7-card hand evaluator written in C++.

[![Build Status](https://travis-ci.org/kennethshackleton/SKPokerEval.svg)](https://travis-ci.org/kennethshackleton/SKPokerEval)

## Example
## How do I use it?

```
```cpp
#include <iostream>
#include "SevenEval.h"

Expand All @@ -18,3 +18,25 @@ int main() {
return 0;
}
```

## Why does it work?

We exploit a key-scheme that gives us just enough uniqueness to correctly identify the integral rank of any 7-card hand, where the greater this rank is the better the hand we hold and two hands of the same rank always draw. Typically we require six additions and a memory footprint just shy of 400kB.

To start with we computed by brute force the first thirteen non-negative integers such that the sum of exactly seven with each taken at most four times is unique among all such sums: 0, 1, 5, 22, 98, 453, 2031, 8698, 22854, 83661, 262349, 636345 and 1479181. A valid sum might be 0+0+1+1+1+1+5 = 9 or 0+98+98+453+98+98+1 = 846, but invalid sum expressions include 0+262349+0+0+0+1 (too few summands), 1+1+5+22+98+453+2031+8698 (too many summands), 0+1+5+22+98+453+2031+8698 (again too many summands, although 1+5+22+98+453+2031+8698 is a legitimate expression) and 1+1+1+1+1+98+98 (too many 1's). We assign these integers as the card face values and add these together to generate a key for any non-flush 7-card hand. The largest non-flush key we see is 7825759, corresponding to any of the four quad-of-aces-full-of-kings.

Similarly, we assign the integer values 0, 1, 8 and 57 for spade, heart, diamond and club respectively. Any sum of exactly seven values taken from {0, 1, 8, 57} is unique among all such sums. We add up the suits of a 7-card hand to produce a "flush check" key and use this to find a pre-calculated flush suit value (in the case we're looking at a flush) or otherwise a defined non-flush constant. The largest flush key we see is 7999, corresponding to any of the four 7-card straight flushes with ace high.

The extraordinarily lucky aspect of this is that the maximum non-flush key we have, 7825759, is a 23-bit integer (note 2^23 = 8388608) and the largest suit key we find, 57*7 = 399, is a 9-bit integer (note 2^9 = 512). If we bit-shift a card's non-flush face value and add to this its flush check to make a card key in advance, when we aggregate the resulting card keys over a given 7-card hand we generate a 23+9 = 32-bit integer key for the whole hand. This integer key can only just be accommodated on a 32-bit machine and yet still carries enough information to decide if we're looking at a flush and if not to then look up the rank of the hand.

## How might I profile my contribution?

The project contains a [profiler](src/Profiler.cpp) which might be used to help benchmark your changes.

```bash
g++ -c -std=c++11 -O3 Profiler.cpp
g++ -o profile Profiler.o
./profile
```

Crudely, the lower the result the more efficiently the ranks were computed. This starts to be compelling with consistent gains of, say, 30% or more.
5 changes: 5 additions & 0 deletions src/Constants.h
Expand Up @@ -30,6 +30,11 @@
#define DIAMOND 8
#define CLUB 57

#define INDEX_SPADE 0
#define INDEX_HEART 1
#define INDEX_DIAMOND 2
#define INDEX_CLUB 3

#define TWO_FIVE 0
#define THREE_FIVE 1
#define FOUR_FIVE 5
Expand Down
95 changes: 79 additions & 16 deletions src/Deckcards.h
Expand Up @@ -23,7 +23,7 @@
#include "Constants.h"
#include <cstdint>

uint32_t const card[DECK_SIZE] = {
uint_fast32_t const card[DECK_SIZE] = {
(ACE << NON_FLUSH_BIT_SHIFT) + SPADE,
(ACE << NON_FLUSH_BIT_SHIFT) + HEART,
(ACE << NON_FLUSH_BIT_SHIFT) + DIAMOND,
Expand Down Expand Up @@ -90,23 +90,23 @@ uint32_t const card[DECK_SIZE] = {
(TWO << NON_FLUSH_BIT_SHIFT) + CLUB
};

uint16_t const suit[DECK_SIZE] = {
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB,
SPADE, HEART, DIAMOND, CLUB
uint_fast8_t const suit[DECK_SIZE] = {
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB
};

uint16_t const flush[DECK_SIZE] = {
uint_fast16_t const flush[DECK_SIZE] = {
ACE_FLUSH, ACE_FLUSH, ACE_FLUSH, ACE_FLUSH,
KING_FLUSH, KING_FLUSH, KING_FLUSH, KING_FLUSH,
QUEEN_FLUSH, QUEEN_FLUSH, QUEEN_FLUSH, QUEEN_FLUSH,
Expand All @@ -122,4 +122,67 @@ uint16_t const flush[DECK_SIZE] = {
TWO_FLUSH, TWO_FLUSH, TWO_FLUSH, TWO_FLUSH
};

uint_fast16_t const flushes[NUMBER_OF_SUITS][DECK_SIZE] = {
{
ACE_FLUSH, 0, 0, 0,
KING_FLUSH, 0, 0, 0,
QUEEN_FLUSH, 0, 0, 0,
JACK_FLUSH, 0, 0, 0,
TEN_FLUSH, 0, 0, 0,
NINE_FLUSH, 0, 0, 0,
EIGHT_FLUSH, 0, 0, 0,
SEVEN_FLUSH, 0, 0, 0,
SIX_FLUSH, 0, 0, 0,
FIVE_FLUSH, 0, 0, 0,
FOUR_FLUSH, 0, 0, 0,
THREE_FLUSH, 0, 0, 0,
TWO_FLUSH, 0, 0, 0
},
{
0, ACE_FLUSH, 0, 0,
0, KING_FLUSH, 0, 0,
0, QUEEN_FLUSH, 0, 0,
0, JACK_FLUSH, 0, 0,
0, TEN_FLUSH, 0, 0,
0, NINE_FLUSH, 0, 0,
0, EIGHT_FLUSH, 0, 0,
0, SEVEN_FLUSH, 0, 0,
0, SIX_FLUSH, 0, 0,
0, FIVE_FLUSH, 0, 0,
0, FOUR_FLUSH, 0, 0,
0, THREE_FLUSH, 0, 0,
0, TWO_FLUSH, 0, 0
},
{
0, 0, ACE_FLUSH, 0,
0, 0, KING_FLUSH, 0,
0, 0, QUEEN_FLUSH, 0,
0, 0, JACK_FLUSH, 0,
0, 0, TEN_FLUSH, 0,
0, 0, NINE_FLUSH, 0,
0, 0, EIGHT_FLUSH, 0,
0, 0, SEVEN_FLUSH, 0,
0, 0, SIX_FLUSH, 0,
0, 0, FIVE_FLUSH, 0,
0, 0, FOUR_FLUSH, 0,
0, 0, THREE_FLUSH, 0,
0, 0, TWO_FLUSH, 0
},
{
0, 0, 0, ACE_FLUSH,
0, 0, 0, KING_FLUSH,
0, 0, 0, QUEEN_FLUSH,
0, 0, 0, JACK_FLUSH,
0, 0, 0, TEN_FLUSH,
0, 0, 0, NINE_FLUSH,
0, 0, 0, EIGHT_FLUSH,
0, 0, 0, SEVEN_FLUSH,
0, 0, 0, SIX_FLUSH,
0, 0, 0, FIVE_FLUSH,
0, 0, 0, FOUR_FLUSH,
0, 0, 0, THREE_FLUSH,
0, 0, 0, TWO_FLUSH
}
};

#endif
42 changes: 21 additions & 21 deletions src/FlushCheck.h
Expand Up @@ -22,27 +22,27 @@

#include <cstdint>

int8_t const flush_check[400] = {
0, 0, 0, -2, -2, 1, 1, 1, 0, 0, -2, -2, -2, 1, 1, -1, 0, -2, -2, -2, -2, 1,
-1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, 8, 8,
8, -1, -1, -1, -1, -1, 8, 8, -1, -1, -1, -1, -1, -1, 8, 0, 0, -2, -2, -2, 1, 1,
-1, 0, -2, -2, -2, -2, 1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2,
-2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1, 8, 8, -1, -1, -1, -1, -1,
-1, 8, -1, -1, -1, -1, -1, -1, -1, -1, 0, -2, -2, -2, -2, 1, -1, -1, -2, -2,
-2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1,
-1, -1, -2, -2, -1, -1, -1, -1, -1, -1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1,
-1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -1, -1, -2,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1,
-2, -2, -1, -1, -1, -1, -1, -1, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 57, 57, 57, -1, -1, -1, -1, -1, 57, 57, -1, -1, -1, -1, -1, -1, 57, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 57, 57,
-1, -1, -1, -1, -1, -1, 57, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 57
int_fast8_t const flush_check[400] = {
0, 0, 0, -2, -2, 1, 1, 1, 0, 0, -2, -2, -2, 1, 1, -1, 0, -2, -2, -2, -2, 1,
-1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, 2, 2,
2, -1, -1, -1, -1, -1, 2, 2, -1, -1, -1, -1, -1, -1, 2, 0, 0, -2, -2, -2, 1, 1,
-1, 0, -2, -2, -2, -2, 1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2,
-2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1, 2, 2, -1, -1, -1, -1, -1,
-1, 2, -1, -1, -1, -1, -1, -1, -1, -1, 0, -2, -2, -2, -2, 1, -1, -1, -2, -2,
-2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1,
-1, -1, -2, -2, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1,
-1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -1, -1, -2,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1,
-2, -2, -1, -1, -1, -1, -1, -1, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 3, 3, 3, -1, -1, -1, -1, -1, 3, 3, -1, -1, -1, -1, -1, -1, 3, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 3,
-1, -1, -1, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3
};

#endif // SKPOKEREVAL_FLUSHCHECK_H
27 changes: 23 additions & 4 deletions src/Profiler.cpp
Expand Up @@ -37,7 +37,7 @@ class Profiler {
static clock_t Profile(unsigned const count) {
std::default_random_engine gen;
std::uniform_int_distribution<int> dist(0, 51);
int const length = 7*count;
int const length = 28*count;
unsigned * const buffer = (unsigned *) malloc(length * sizeof(unsigned));
for (int i = 0; i < length; i += 7) {
int j = 0;
Expand All @@ -54,24 +54,43 @@ class Profiler {
}
}
std::clock_t const start = std::clock();
for (int i = 0; i < length; i += 7) {
for (int i = 0; i < length; i += 28) {
doNotOptimiseAway(
SevenEval::GetRank(buffer[i+21], buffer[i+22], buffer[i+23],
buffer[i+24], buffer[i+25], buffer[i+26], buffer[i+27])
);
doNotOptimiseAway(
SevenEval::GetRank(buffer[i], buffer[i+1], buffer[i+2],
buffer[i+3], buffer[i+4], buffer[i+5], buffer[i+6])
);
doNotOptimiseAway(
SevenEval::GetRank(buffer[i+7], buffer[i+8], buffer[i+9],
buffer[i+10], buffer[i+11], buffer[i+12], buffer[i+13])
);
doNotOptimiseAway(
SevenEval::GetRank(buffer[i+14], buffer[i+15], buffer[i+16],
buffer[i+17], buffer[i+18], buffer[i+19], buffer[i+20])
);
}
std::clock_t const end = std::clock();
delete buffer;
return end-start;
}
};

float clocksToMilliseconds(clock_t c) {
return 1000.0f * c / CLOCKS_PER_SEC;
}

int main() {
clock_t fastest = std::numeric_limits<clock_t>::max();
for (int i = 0; i < 20; ++i) {
fastest = std::min(fastest, Profiler::Profile(50000000));
clock_t const profile = Profiler::Profile(12500000);
fastest = std::min(fastest, profile);
std::cout << i << ": " << clocksToMilliseconds(profile) << "ms"
<< std::endl;
}
std::cout << 1000.0f * fastest / CLOCKS_PER_SEC << " ms" << std::endl;
std::cout << "Result: " << clocksToMilliseconds(fastest) << "ms" << std::endl;
}

#endif
15 changes: 4 additions & 11 deletions src/SevenEval.h
Expand Up @@ -39,23 +39,16 @@ class SevenEval {
uint_fast32_t key = card[i] + card[j] + card[k] + card[l] + card[m] +
card[n] + card[p];
// Tear off the flush check strip.
int_fast8_t const flush_suit = flush_check[key & SUIT_BIT_MASK];
if (NOT_A_FLUSH == flush_suit) {
int_fast8_t const suit = flush_check[key & SUIT_BIT_MASK];
if (NOT_A_FLUSH == suit) {
// Tear off the non-flush key strip, and look up the rank.
key >>= NON_FLUSH_BIT_SHIFT;
return rank_hash[offsets[key >> RANK_OFFSET_SHIFT] +
(key & RANK_HASH_MOD)];
}
// Generate a flush key, and look up the rank.
int flush_key = 0;
if (suit[i] == flush_suit) flush_key = flush[i];
if (suit[j] == flush_suit) flush_key += flush[j];
if (suit[k] == flush_suit) flush_key += flush[k];
if (suit[l] == flush_suit) flush_key += flush[l];
if (suit[m] == flush_suit) flush_key += flush[m];
if (suit[n] == flush_suit) flush_key += flush[n];
if (suit[p] == flush_suit) flush_key += flush[p];
return flush_ranks[flush_key];
uint_fast16_t const * const s = flushes[suit];
return flush_ranks[s[i] + s[j] + s[k] + s[l] + s[m] + s[n] + s[p]];
}
};

Expand Down

0 comments on commit 24c71be

Please sign in to comment.