(This post is a continued in Part II here.)
The idea for a new 7-card evaluator was inspired by Cactus Kev's blog, describing his 5-card evaluator. Setting flushes to one side for the time being, his idea is to assign thirteen pairwise distinct prime numbers as the face values of each card. He chooses the first thirteen prime numbers, from 2 to 41 inclusive. Every integer greater than or equal to 2 is uniquely expressed as a finite product of primes so all the information we need regarding the five-card hand modulo flushes can be recovered from the product of its face values, its "signature" or key. This neatly gets around having to worry about whether or how a hand should be presented in some standard form before carrying out a lot of computationally expensive sorting to achieve this. It's a smart thing to do.
The idea for a new 7-card evaluator was inspired by Cactus Kev's blog, describing his 5-card evaluator. Setting flushes to one side for the time being, his idea is to assign thirteen pairwise distinct prime numbers as the face values of each card. He chooses the first thirteen prime numbers, from 2 to 41 inclusive. Every integer greater than or equal to 2 is uniquely expressed as a finite product of primes so all the information we need regarding the five-card hand modulo flushes can be recovered from the product of its face values, its "signature" or key. This neatly gets around having to worry about whether or how a hand should be presented in some standard form before carrying out a lot of computationally expensive sorting to achieve this. It's a smart thing to do.
On the other hand, we recognised that we really don't need anything like the full power of the fundamental theorem here. For instance we don't need to know that 43*47*53*59*61 is a unique factorisation, and more to the point we don't even need to know that 41*41*41*41*41 is also a unique factorisation because no legitimate poker hand ever contains five cards with the same face value. Pushing this scheme further a legitimate 7-card hand could find itself with the key 143,133,271,933 (~143 billion), equal to the product 41*41*41*41*37*37*37 and corresponding to a 7-card hand comprising all four aces and three of the four kings. An array of short-integers that size would be enormous, requiring almost 280Gb of contiguous memory virtually all of which would go unused. We can use a 5-card evaluator to evaluate 7-card hands but we will always be faced with the overhead of comparing the twenty-one possible 5-card hands from a hand of seven.
With these observations we've found a new 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. 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, namely 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, and similarly the largest flush key we see is 7999, corresponding to any of the four 7-card straight flushes with ace high. These keys are all small enough to act as indices for all of the ranks stored in two arrays, one for the non-flushes and one for the flushes. The memory footprint can be reduced by mapping the array of 7-card ranks on to a circle of circumference 4565145, again computed by brute force; given the Birthday problem, it seems lucky that such a good circumference should even exist. This all comfortably runs on 32-bit systems.
We expect this schema can be applied in other similar contexts.
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, and similarly the largest flush key we see is 7999, corresponding to any of the four 7-card straight flushes with ace high. These keys are all small enough to act as indices for all of the ranks stored in two arrays, one for the non-flushes and one for the flushes. The memory footprint can be reduced by mapping the array of 7-card ranks on to a circle of circumference 4565145, again computed by brute force; given the Birthday problem, it seems lucky that such a good circumference should even exist. This all comfortably runs on 32-bit systems.
We expect this schema can be applied in other similar contexts.
Click SpecialKEvalBeta to begin downloading a .zip file containing the Objective-C, Java and C++ source code files. In doing so you are accepting all of the terms and conditions of the GNU General Public License v3 under which the work is distributed. Please also carefully report any bugs you encounter either by commenting on this article or by sending an e-mail to the address made available here.
The list of references here is not intended to be exhaustive or even representative of what is available online these days, it's just the result of sifting through the first twenty pages or so of a Google search. If there's a hand evaluator you would like to have linked here that is not already listed then by all means let us know and we'll be happy to oblige. If you decide to include my work and ideas in a GPL-project of your own I'd appreciate being openly acknowledged in your work and if you would like us to advertise your project here let us know and we will be happy to oblige.
A commercial-grade evolution of the beta evaluator has been released under a different license and in a different guise on the iTunes Store. It's called "Poker Ace".
Tweet
I like the idea, but I think I have found a bug in your implementation. I run the following two hands through SevenEval.java, and get the same rank when they should be different ranks:
ReplyDeleteeval.getRankOf(1,2,3,4,5,14,15)
7296
eval.getRankOf(1,2,3,4,5,6,8)
7296
The second should have a higher rank because it is a higher straight flush. I'd like to use your engine to do some analysis, but I can't tell whether this problem is just with straight flushes or with hands in general. It seems to be a deeper problem in your codebase. Please let me know when it is fixed.
Hello J2kun,
ReplyDeleteThanks for your post and for going through my code.
This doesn't appear to be a bug, rather a misunderstanding of how I've encoded the deck of cards.
The two hands you've described share the same best five card hand - namely, a full house (three aces, two kings). Remember, 0 to 3 inclusive are the aces (0 is the ace of spades, 1 the ace of hearts and so forth).
The remaining two cards in either hand of seven are extraneous. Thus, the two hands are expected to have the same rank.
You asked me to contact you to let you know (that there doesn't appear to be a problem). I clicked through your blog but I regret I couldn't find a contact address for you.
SpecialK
I am just a little confused on how this works. Correct me if I am wrong, but by what you have recently posted, the numbers entered in "getRankOf" correspond to cards in the deck as follows:
ReplyDelete0 1 2 3 = A spades, A hearts, A clubs, A diamonds
4 5 6 7 = K spades, K hearts, K clubs, K diamonds
Is this correct?
Hello Taskforce141,
ReplyDeleteThat's essentially correct - just switch clubs and diamonds to use my scheme.
i.e.
0 = Ace of Spades
1 = Ace of Hearts
2 = Ace of Diamonds
3 = Ace of Clubs
4 = King of Spades
5 = King of Hearts
6 = King of Diamonds
7 = King of Clubs
and so forth.
There's nothing stopping you reworking this to suit your preferred index, this is just how I chose to set it up.
SpecialK
Thanks a bunch. I also am trying to create a poker calculator online application like what you have done in "poker ace." My user interface is all done, but I am really struggling with coding the math portion of it. I understand the math portion of it, but coding it is too advanced for me. Do you have any tips or advice, or other downloadable code I can use to finish this project? It would be much appreciated. Thanks.
ReplyDeleteHello taskforce141,
ReplyDeleteYou're very welcome.
As for advice - it really depends on exactly what you want to do. Are you wanting to calculate equity, and if so in which language? For performance reasons I'd recommend doing this in C or C++. If there's any demand I can add equity calculating methods to the code I made public in due course. You're welcome to write to me further at codingfeedback@gmail.com.
That said, I'll add that I started out on this project to learn how to code - I really didn't know a thing back then and it embarrassed me a lot. But I was interested enough in this area to use it to cut my teeth, and while I'm happy to help you out I really recommend you take this as an opportunity.
SpecialK
I sent an e-mail to the given address under b.bilfred@gmail.com. Thanks for the help again.
ReplyDeleteHey man, was just wondering if a java version will be available soon. It would help a lot. Thanks
ReplyDeleteThe Java version now has at least one equity method. More to follow in time. See the latest version on Google projects or over on github. Links at the bottom of the right hand panel on this page.
DeleteGreat work. One question
ReplyDelete'The memory footprint can be reduced by mapping the array of 7-card ranks on to a circle of circumference 4565145, again computed by brute force'
Can you explain further how this was done?
Thanks,
I can't seem to understand bits of your code - I'm using Xcode 4.2, so a lot of the releasing doesn't agree with ARC. But was confused by:
ReplyDeletedeckcardsKey[4*n] = (face[n] << NON_FLUSH_BIT_SHIFT) + SPADE;
deckcardsKey[4*n + 1] = (face[n] << NON_FLUSH_BIT_SHIFT) + HEART;
deckcardsKey[4*n + 2] = (face[n] << NON_FLUSH_BIT_SHIFT) + DIAMOND;
deckcardsKey[4*n + 3] = (face[n] << NON_FLUSH_BIT_SHIFT) + CLUB;
Since NON_FLUSH_BIT_SHIFT appears to be undefined... So my code won't compile.
So I had a hunt through the code for the other versions and realised that these two lines were missing from your prefix file:
ReplyDelete//Bit masks
#define SUIT_BIT_MASK 511
#define NON_FLUSH_BIT_SHIFT 9
/////////
The code is now working wonderfully! Thank you so much!
Great job on this evaluator and thanks for making it GPL.
ReplyDeleteI was wondering if there was a way to get the hand category (i.e. Flush, 2-pair, Full House, etc) from the rank of a 7-card hand or if there was some advice you could give me on how to program a way to get the hand category so that it works with your code.
After finding the rank for the 7-card hand, check if it lies in a certain range. For example, if it has rank at least 5854 and at most 5863 then it must be a non-flush straight.
Delete