Skip to content

Commit 24c71be

Browse files
Merge branch 'release/v1.6'
2 parents 871bcf7 + 8e6da66 commit 24c71be

8 files changed

+161
-55
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
### Change Log
22

3+
#### 1.6.0
4+
5+
* Optimise ranking flushes.
6+
37
#### 1.5.0
48

59
* Static interface.

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ project(${PROJECT_NAME})
55

66
# Versioning.
77
set(SK_POKER_EVAL_VERSION_MAJOR 1)
8-
set(SK_POKER_EVAL_VERSION_MINOR 5)
8+
set(SK_POKER_EVAL_VERSION_MINOR 6)
99
set(SK_POKER_EVAL_VERSION_PATCH 0)
1010

1111
# Get the current commit.

README.md

+24-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ A lightweight 32-bit Texas Hold'em 7-card hand evaluator written in C++.
66

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

9-
## Example
9+
## How do I use it?
1010

11-
```
11+
```cpp
1212
#include <iostream>
1313
#include "SevenEval.h"
1414

@@ -18,3 +18,25 @@ int main() {
1818
return 0;
1919
}
2020
```
21+
22+
## Why does it work?
23+
24+
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.
25+
26+
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.
27+
28+
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.
29+
30+
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.
31+
32+
## How might I profile my contribution?
33+
34+
The project contains a [profiler](src/Profiler.cpp) which might be used to help benchmark your changes.
35+
36+
```bash
37+
g++ -c -std=c++11 -O3 Profiler.cpp
38+
g++ -o profile Profiler.o
39+
./profile
40+
```
41+
42+
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.

src/Constants.h

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
#define DIAMOND 8
3131
#define CLUB 57
3232

33+
#define INDEX_SPADE 0
34+
#define INDEX_HEART 1
35+
#define INDEX_DIAMOND 2
36+
#define INDEX_CLUB 3
37+
3338
#define TWO_FIVE 0
3439
#define THREE_FIVE 1
3540
#define FOUR_FIVE 5

src/Deckcards.h

+79-16
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
#include "Constants.h"
2424
#include <cstdint>
2525

26-
uint32_t const card[DECK_SIZE] = {
26+
uint_fast32_t const card[DECK_SIZE] = {
2727
(ACE << NON_FLUSH_BIT_SHIFT) + SPADE,
2828
(ACE << NON_FLUSH_BIT_SHIFT) + HEART,
2929
(ACE << NON_FLUSH_BIT_SHIFT) + DIAMOND,
@@ -90,23 +90,23 @@ uint32_t const card[DECK_SIZE] = {
9090
(TWO << NON_FLUSH_BIT_SHIFT) + CLUB
9191
};
9292

93-
uint16_t const suit[DECK_SIZE] = {
94-
SPADE, HEART, DIAMOND, CLUB,
95-
SPADE, HEART, DIAMOND, CLUB,
96-
SPADE, HEART, DIAMOND, CLUB,
97-
SPADE, HEART, DIAMOND, CLUB,
98-
SPADE, HEART, DIAMOND, CLUB,
99-
SPADE, HEART, DIAMOND, CLUB,
100-
SPADE, HEART, DIAMOND, CLUB,
101-
SPADE, HEART, DIAMOND, CLUB,
102-
SPADE, HEART, DIAMOND, CLUB,
103-
SPADE, HEART, DIAMOND, CLUB,
104-
SPADE, HEART, DIAMOND, CLUB,
105-
SPADE, HEART, DIAMOND, CLUB,
106-
SPADE, HEART, DIAMOND, CLUB
93+
uint_fast8_t const suit[DECK_SIZE] = {
94+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
95+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
96+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
97+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
98+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
99+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
100+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
101+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
102+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
103+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
104+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
105+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB,
106+
INDEX_SPADE, INDEX_HEART, INDEX_DIAMOND, INDEX_CLUB
107107
};
108108

109-
uint16_t const flush[DECK_SIZE] = {
109+
uint_fast16_t const flush[DECK_SIZE] = {
110110
ACE_FLUSH, ACE_FLUSH, ACE_FLUSH, ACE_FLUSH,
111111
KING_FLUSH, KING_FLUSH, KING_FLUSH, KING_FLUSH,
112112
QUEEN_FLUSH, QUEEN_FLUSH, QUEEN_FLUSH, QUEEN_FLUSH,
@@ -122,4 +122,67 @@ uint16_t const flush[DECK_SIZE] = {
122122
TWO_FLUSH, TWO_FLUSH, TWO_FLUSH, TWO_FLUSH
123123
};
124124

125+
uint_fast16_t const flushes[NUMBER_OF_SUITS][DECK_SIZE] = {
126+
{
127+
ACE_FLUSH, 0, 0, 0,
128+
KING_FLUSH, 0, 0, 0,
129+
QUEEN_FLUSH, 0, 0, 0,
130+
JACK_FLUSH, 0, 0, 0,
131+
TEN_FLUSH, 0, 0, 0,
132+
NINE_FLUSH, 0, 0, 0,
133+
EIGHT_FLUSH, 0, 0, 0,
134+
SEVEN_FLUSH, 0, 0, 0,
135+
SIX_FLUSH, 0, 0, 0,
136+
FIVE_FLUSH, 0, 0, 0,
137+
FOUR_FLUSH, 0, 0, 0,
138+
THREE_FLUSH, 0, 0, 0,
139+
TWO_FLUSH, 0, 0, 0
140+
},
141+
{
142+
0, ACE_FLUSH, 0, 0,
143+
0, KING_FLUSH, 0, 0,
144+
0, QUEEN_FLUSH, 0, 0,
145+
0, JACK_FLUSH, 0, 0,
146+
0, TEN_FLUSH, 0, 0,
147+
0, NINE_FLUSH, 0, 0,
148+
0, EIGHT_FLUSH, 0, 0,
149+
0, SEVEN_FLUSH, 0, 0,
150+
0, SIX_FLUSH, 0, 0,
151+
0, FIVE_FLUSH, 0, 0,
152+
0, FOUR_FLUSH, 0, 0,
153+
0, THREE_FLUSH, 0, 0,
154+
0, TWO_FLUSH, 0, 0
155+
},
156+
{
157+
0, 0, ACE_FLUSH, 0,
158+
0, 0, KING_FLUSH, 0,
159+
0, 0, QUEEN_FLUSH, 0,
160+
0, 0, JACK_FLUSH, 0,
161+
0, 0, TEN_FLUSH, 0,
162+
0, 0, NINE_FLUSH, 0,
163+
0, 0, EIGHT_FLUSH, 0,
164+
0, 0, SEVEN_FLUSH, 0,
165+
0, 0, SIX_FLUSH, 0,
166+
0, 0, FIVE_FLUSH, 0,
167+
0, 0, FOUR_FLUSH, 0,
168+
0, 0, THREE_FLUSH, 0,
169+
0, 0, TWO_FLUSH, 0
170+
},
171+
{
172+
0, 0, 0, ACE_FLUSH,
173+
0, 0, 0, KING_FLUSH,
174+
0, 0, 0, QUEEN_FLUSH,
175+
0, 0, 0, JACK_FLUSH,
176+
0, 0, 0, TEN_FLUSH,
177+
0, 0, 0, NINE_FLUSH,
178+
0, 0, 0, EIGHT_FLUSH,
179+
0, 0, 0, SEVEN_FLUSH,
180+
0, 0, 0, SIX_FLUSH,
181+
0, 0, 0, FIVE_FLUSH,
182+
0, 0, 0, FOUR_FLUSH,
183+
0, 0, 0, THREE_FLUSH,
184+
0, 0, 0, TWO_FLUSH
185+
}
186+
};
187+
125188
#endif

src/FlushCheck.h

+21-21
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,27 @@
2222

2323
#include <cstdint>
2424

25-
int8_t const flush_check[400] = {
26-
0, 0, 0, -2, -2, 1, 1, 1, 0, 0, -2, -2, -2, 1, 1, -1, 0, -2, -2, -2, -2, 1,
27-
-1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, 8, 8,
28-
8, -1, -1, -1, -1, -1, 8, 8, -1, -1, -1, -1, -1, -1, 8, 0, 0, -2, -2, -2, 1, 1,
29-
-1, 0, -2, -2, -2, -2, 1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2,
30-
-2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1, 8, 8, -1, -1, -1, -1, -1,
31-
-1, 8, -1, -1, -1, -1, -1, -1, -1, -1, 0, -2, -2, -2, -2, 1, -1, -1, -2, -2,
32-
-2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1,
33-
-1, -1, -2, -2, -1, -1, -1, -1, -1, -1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1,
34-
-1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1,
35-
-1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -1, -1, -2,
36-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
37-
-1, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1,
38-
-2, -2, -1, -1, -1, -1, -1, -1, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
39-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
40-
-1, 57, 57, 57, -1, -1, -1, -1, -1, 57, 57, -1, -1, -1, -1, -1, -1, 57, -1, -1,
41-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
42-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 57, 57,
43-
-1, -1, -1, -1, -1, -1, 57, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
44-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
45-
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 57
25+
int_fast8_t const flush_check[400] = {
26+
0, 0, 0, -2, -2, 1, 1, 1, 0, 0, -2, -2, -2, 1, 1, -1, 0, -2, -2, -2, -2, 1,
27+
-1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, 2, 2,
28+
2, -1, -1, -1, -1, -1, 2, 2, -1, -1, -1, -1, -1, -1, 2, 0, 0, -2, -2, -2, 1, 1,
29+
-1, 0, -2, -2, -2, -2, 1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2,
30+
-2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1, 2, 2, -1, -1, -1, -1, -1,
31+
-1, 2, -1, -1, -1, -1, -1, -1, -1, -1, 0, -2, -2, -2, -2, 1, -1, -1, -2, -2,
32+
-2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1,
33+
-1, -1, -2, -2, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1,
34+
-1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -2, -2, -2, -2, -1,
35+
-1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -1, -1, -2,
36+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
37+
-1, -1, -1, -1, -2, -2, -2, -2, -1, -1, -1, -1, -2, -2, -2, -1, -1, -1, -1, -1,
38+
-2, -2, -1, -1, -1, -1, -1, -1, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
39+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
40+
-1, 3, 3, 3, -1, -1, -1, -1, -1, 3, 3, -1, -1, -1, -1, -1, -1, 3, -1, -1,
41+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
42+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 3,
43+
-1, -1, -1, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
44+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
45+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3
4646
};
4747

4848
#endif // SKPOKEREVAL_FLUSHCHECK_H

src/Profiler.cpp

+23-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class Profiler {
3737
static clock_t Profile(unsigned const count) {
3838
std::default_random_engine gen;
3939
std::uniform_int_distribution<int> dist(0, 51);
40-
int const length = 7*count;
40+
int const length = 28*count;
4141
unsigned * const buffer = (unsigned *) malloc(length * sizeof(unsigned));
4242
for (int i = 0; i < length; i += 7) {
4343
int j = 0;
@@ -54,24 +54,43 @@ class Profiler {
5454
}
5555
}
5656
std::clock_t const start = std::clock();
57-
for (int i = 0; i < length; i += 7) {
57+
for (int i = 0; i < length; i += 28) {
58+
doNotOptimiseAway(
59+
SevenEval::GetRank(buffer[i+21], buffer[i+22], buffer[i+23],
60+
buffer[i+24], buffer[i+25], buffer[i+26], buffer[i+27])
61+
);
5862
doNotOptimiseAway(
5963
SevenEval::GetRank(buffer[i], buffer[i+1], buffer[i+2],
6064
buffer[i+3], buffer[i+4], buffer[i+5], buffer[i+6])
6165
);
66+
doNotOptimiseAway(
67+
SevenEval::GetRank(buffer[i+7], buffer[i+8], buffer[i+9],
68+
buffer[i+10], buffer[i+11], buffer[i+12], buffer[i+13])
69+
);
70+
doNotOptimiseAway(
71+
SevenEval::GetRank(buffer[i+14], buffer[i+15], buffer[i+16],
72+
buffer[i+17], buffer[i+18], buffer[i+19], buffer[i+20])
73+
);
6274
}
6375
std::clock_t const end = std::clock();
6476
delete buffer;
6577
return end-start;
6678
}
6779
};
6880

81+
float clocksToMilliseconds(clock_t c) {
82+
return 1000.0f * c / CLOCKS_PER_SEC;
83+
}
84+
6985
int main() {
7086
clock_t fastest = std::numeric_limits<clock_t>::max();
7187
for (int i = 0; i < 20; ++i) {
72-
fastest = std::min(fastest, Profiler::Profile(50000000));
88+
clock_t const profile = Profiler::Profile(12500000);
89+
fastest = std::min(fastest, profile);
90+
std::cout << i << ": " << clocksToMilliseconds(profile) << "ms"
91+
<< std::endl;
7392
}
74-
std::cout << 1000.0f * fastest / CLOCKS_PER_SEC << " ms" << std::endl;
93+
std::cout << "Result: " << clocksToMilliseconds(fastest) << "ms" << std::endl;
7594
}
7695

7796
#endif

src/SevenEval.h

+4-11
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,16 @@ class SevenEval {
3939
uint_fast32_t key = card[i] + card[j] + card[k] + card[l] + card[m] +
4040
card[n] + card[p];
4141
// Tear off the flush check strip.
42-
int_fast8_t const flush_suit = flush_check[key & SUIT_BIT_MASK];
43-
if (NOT_A_FLUSH == flush_suit) {
42+
int_fast8_t const suit = flush_check[key & SUIT_BIT_MASK];
43+
if (NOT_A_FLUSH == suit) {
4444
// Tear off the non-flush key strip, and look up the rank.
4545
key >>= NON_FLUSH_BIT_SHIFT;
4646
return rank_hash[offsets[key >> RANK_OFFSET_SHIFT] +
4747
(key & RANK_HASH_MOD)];
4848
}
4949
// Generate a flush key, and look up the rank.
50-
int flush_key = 0;
51-
if (suit[i] == flush_suit) flush_key = flush[i];
52-
if (suit[j] == flush_suit) flush_key += flush[j];
53-
if (suit[k] == flush_suit) flush_key += flush[k];
54-
if (suit[l] == flush_suit) flush_key += flush[l];
55-
if (suit[m] == flush_suit) flush_key += flush[m];
56-
if (suit[n] == flush_suit) flush_key += flush[n];
57-
if (suit[p] == flush_suit) flush_key += flush[p];
58-
return flush_ranks[flush_key];
50+
uint_fast16_t const * const s = flushes[suit];
51+
return flush_ranks[s[i] + s[j] + s[k] + s[l] + s[m] + s[n] + s[p]];
5952
}
6053
};
6154

0 commit comments

Comments
 (0)