Several days ago, a question on this topic was posted to SO. The problem turned out to be the OP's test cases did not reveal a deficiency in the code. This challenge caught my attention. Writing the following has provided entertainment for a number of rainy hours.
I've had a look at several "Review" postings (looking for C language solutions). I believe this code embodies a representation of the data (card values and tallies) that improves upon those I've seen. (Without its verbose commentary, assess()
is almost trivial.) My focus has been on the code, with little interest in the game of poker. Thank you for your time.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
enum {
highCard, onePair, twoPair, threeOfKind, straight,
flush, fullhouse, fourOfKind, str8Fl, royalFl,
nTallies,
nCard = 7, // # of cards in one hand
aFlush = 5, // # of cards in a flush, royal or otherwise
oneSuit = 0x10, // # "suits" contain cards represented by 0 to 0xC of 0 to 0xF (4 bits)
b43210 = 0x1F // bits 4 to bit 0 (five contiguous set bits)
};
char *name[] = { // for outout
"0 High Card", "1 One Pair", "2 Two Pair", "3 Three of a kind", "4 Straight",
"5 Flush", "6 Full House", "7 Four of a kind", "8 Straight Flush", "9 Royal Flush",
};
int isSTR8( uint64_t x ) { // determine if low order 13 bits contain a "straight"
for( x &= 0x1FFF; x >= b43210; x >>= 1 ) // return as soon as search becomes futile
if( (x & b43210) == b43210 ) return 1;
return 0;
}
int assess( int h[] ) { // assess the hand into one of the ten categories
uint64_t bits = 0, str8 = 0, popS = 0, popV = 0, big1 = 1;
// convert 7 unique card values into one 64 bit field
// 'bits' assembled as 4x16 bit fields, one for each suit
// used bits: b60-b48, b44-b32, b28-b16, b12-b0 ==> single bit in 1 of 4 "suits"
// 'bits' will eventually have only 7 set bits representing the 7 cards' suits & values
// 'popS' accumulates counts of each suit present in the hand
// max possible cnt for any one suit is all seven cards in same suit. ie: 7
// popS uses each 4x4 bit field to sum values in range of 0 to 7 (3 bits)
// 'popV' accumulates counts of each value present in the hand
// max possible cnt for any one value is 4 (eg: 4 Jacks)
// popV uses each 13x4 bit field to sum values in range of 0 to 4 (3 bits)
for( size_t i = 0; i < nCard; i++ ) {
bits |= big1 << h[ i ]; // as single set bit
popS += big1 << (4*(h[ i ] >> 4)); // cnt 'n' in each suit
popV += big1 << (4*(h[ i ] & 0xF)); // cnt 'n' of this face value
}
// destroy 'bits' (right shift)
// if a 'flush' is found, determine "royal", "straight" or "ordinary"
// otherwise, accumulate bits to determine a (mixed) straight soon
for( ; bits; bits >>= oneSuit, popS >>= 4 ) { // finish search asap!
if( (popS & 0xF) >= aFlush )
return (bits & 0x1E01) == 0x1E01 ? royalFl : isSTR8( bits ) ? str8Fl : flush;
str8 |= bits;
}
if( isSTR8( str8 ) ) return straight; // should be obvious
// determine highest and 2nd highest tallies of card face values
int hi1 = 0, hi2 = 0;
for( ; popV; popV >>= 4 ) { // finish search asap!
int pop = (int)(popV & 0xF);
if( hi1 < pop ) { hi2 = hi1; hi1 = pop; }
else if( hi2 < pop ) hi2 = pop;
}
switch( hi1 ) { // dispatch...
case 4: return fourOfKind;
case 3: return hi2 >= 2 ? fullhouse : threeOfKind;
case 2: return hi2 == 2 ? twoPair : onePair;
default: return highCard;
}
}
int *show( int *h ) { // called by test()
for( int i = 0; i < nCard; i++ )
printf( "%c%c ", "hcds"[h[i]/oneSuit], "A23456789XJQK"[h[i]%oneSuit] );
return h;
}
void test( void ) {
// 4 suits of 13 cards each ('X' == 10).
// Note range for each suit changes with each Ace
enum {
hA = 0x00, h2, h3, h4, h5, h6, h7, h8, h9, hX, hJ, hQ, hK, // hearts
cA = 0x10, c2, c3, c4, c5, c6, c7, c8, c9, cX, cJ, cQ, cK, // clubs
dA = 0x20, d2, d3, d4, d5, d6, d7, d8, d9, dX, dJ, dQ, dK, // diamonds
sA = 0x30, s2, s3, s4, s5, s6, s7, s8, s9, sX, sJ, sQ, sK // spades
};
// 10 composed samples to test assess(). Left-to-right order is not special
int i, hands[][ nCard ] = {
{ h4, cJ, d9, d5, s3, h7, sA }, // high card
{ h4, c3, d9, d5, s3, h7, sA }, // one pair
{ h9, c3, d9, d5, s3, h7, sA }, // two pair
{ h9, c3, d9, d5, s9, h7, sA }, // three of a kind
{ d5, c6, sA, h8, s5, c7, d4 }, // straight
{ d5, dJ, dX, dQ, s5, c7, d6 }, // flush
{ d8, h8, s8, hQ, c7, sK, s7 }, // full house
{ h9, c9, d9, d5, s9, h7, sA }, // four of a kind
{ s5, s7, s6, s8, d5, c7, s4 }, // straight flush
{ dA, dJ, dX, dQ, s5, c7, dK }, // royal flush
};
for( i = 0; i < sizeof hands/sizeof hands[0]; i++ )
printf( "... %s\n", name[ assess( show( hands[ i ] ) ) ] );
}
int *deal( int *p ) { // called by run()
// not interested in true randomness and fairness
// sufficient to compose array of 7 unique values (cards) between 0 and 63
// encoding values rejects values 13, 14, 15 in any suit
for( int x, i = 0; i < nCard; ) {
p[i] = rand() % 64;
if( ( p[i] & 0xF ) <= 12 ) { // face is "Ace to King" (0 to 12)?
for( x = 0; x < i && p[x] != p[i]; ) x++; // no duplicates!
i += x == i;
}
}
return p;
}
void run( int nLoop ) {
srand( time( NULL ) );
int i, cnt[ nTallies ] = { 0 }; // simple 'histogram' accumulator for 10 types
int hand[ nCard ];
for( i = 0; i < nLoop; i++ )
++cnt[ assess( deal( hand ) ) ]; // deal, assess, tabulate, repeat
for( i = 0; i < nTallies; i++ )
printf( "%.6f - %s\n", cnt[i]*1. / nLoop, name[ i ] ); // report
}
int main( void ) {
// Activate one or other... or both.
test();
run( 10 * 1000 ); // # of trials
return 0;
}
Results of one run with both test()
and run()
active:
h4 cJ d9 d5 s3 h7 sA ... 0 High Card
h4 c3 d9 d5 s3 h7 sA ... 1 One Pair
h9 c3 d9 d5 s3 h7 sA ... 2 Two Pair
h9 c3 d9 d5 s9 h7 sA ... 3 Three of a kind
d5 c6 sA h8 s5 c7 d4 ... 4 Straight
d5 dJ dX dQ s5 c7 d6 ... 5 Flush
d8 h8 s8 hQ c7 sK s7 ... 6 Full House
h9 c9 d9 d5 s9 h7 sA ... 7 Four of a kind
s5 s7 s6 s8 d5 c7 s4 ... 8 Straight Flush
dA dJ dX dQ s5 c7 dK ... 9 Royal Flush
0.165100 - 0 High Card
0.446500 - 1 One Pair
0.244800 - 2 Two Pair
0.048300 - 3 Three of a kind
0.042600 - 4 Straight
0.028600 - 5 Flush
0.023000 - 6 Full House
0.000800 - 7 Four of a kind
0.000200 - 8 Straight Flush
0.000100 - 9 Royal Flush
EDIT:
What's the mantra? "Always consider edge cases!"
Thanks to @TobySpeight for finding a deficiency in the code above. It fails to find a "royal straight" composed of Ace, King, Queen, Jack, 10 of diverse suits... Doh!
A quick fix would be to change:
if( isSTR8( str8 ) ) return straight; // should be obvious
to
if( ((str8 & 0x1E01) == 0x1E01) || isSTR8( str8 ) ) return straight;
testing for this non-contiguous but valid pattern.
Only 52! - 45! possibilities of hands to test this code... Now, a few less. Thanks again for Toby's sharp eyes.