Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
Home
Discussion GroupsGeneralPHPASPPerlColdFusionFlashHTML, CSS, ScriptsBrowsers

Webmaster Forum / HTML, CSS, Scripts / JavaScript / March 2010



Tip: Looking for answers? Try searching our database.

Poker hand evaluator

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
Johannes Baagoe - 09 Mar 2010 15:22 GMT
Has anybody written a fast Texas hold'em hand evaluator in ECMAScript?

What is wanted is a function, say handEval(hand), which, given a
suitable representation of a hand of 7 cards, returns a value that
accurately represents the strength of the hand. That is,
handEval(hand0) > handEval(hand1) iff hand0 beats hand1,
handEval(hand0) == handEval(hand1) iff hand0 and hand1 tie, and
handEval(hand0) < handEval(hand1) iff hand1 beats hand0.

It would be nice, too, if the return value could easily indicate what
kind of hand it is - high card, pair, two pairs, etc, preferably with
further information to break ties within each kind.

And it would be a further bonus if the evaluator could retain most
of its state when only a few cards change from one hand to another,
in order to gain time when looping through all the possible hands
with a given, fixed subset.

The problem has received a lot of attention in other languages, e.g.,
http://www.codingthewheel.com/archives/poker-hand-evaluator-roundup
But the best solutions can hardly be implemented in ECMAScript - a
123 megabyte lookup table is unpractical. On the other hand, Objects
or sparse Arrays should be reasonably fast and space efficient,
so smaller lookup tables could be fine. How fast can one get?

Any ideas?

(NB: this is Texas hold'em, with 7 cards. Naive assumptions from 5 card
poker do not necessarily apply. There may be a flush and a straight,
but no straight flush. Also, there may be both 4 of a kind and 3 of
a kind, etc. What is wanted is the value of the best 5 cards subset.)

Signature

Johannes

Stevo - 09 Mar 2010 15:26 GMT
> Has anybody written a fast Texas hold'em hand evaluator in ECMAScript?

So you want people here to help you cheat and take people's money?
Ken Snyder - 09 Mar 2010 20:15 GMT
> So you want people here to help you cheat and take people's money?

No, he just wants to evaluate which hand wins at the end of the round.
e.g. determine that a hand is a flush or 2 pair and that a flush beats
2 pair.

I would write a routine that sorts the cards numerically then checks
for each possible hand type. Start with the highest (straight flush)
then go down to the lowest (1 pair). Make sure that you count Aces as
14, not 1.

So, you might have a hand that starts out
["2H","4H","9H","10H","14D,"14H","14C"].

Transform that into two arrays: [2,4,9,10,14,14,14],
["H","H","H","H","D","H","C"].

If you have a total of 5 H's, then you have a flush. If you have 5
numbers in sequence, you have a straight, etc.

That would be a good start.
Johannes Baagoe - 10 Mar 2010 02:27 GMT
Ken Snyder :

> I would write a routine that sorts the cards numerically

Fast solutions tend to avoid sorting - it takes time, and needs to
be redone even if only one card changes.

> then checks for each possible hand type. Start with the highest
> (straight flush) then go down to the lowest (1 pair).

Again, this is hardly optimal, except in terms of being the most
natural to code.

> Make sure that you count Aces as 14, not 1.

What is usually done is to number the cards from 0 to 51, like this :

 2c -> 0
 2d -> 1
 2h -> 2
 2s -> 3
 3c -> 4
   ...
 Ah -> 50
 As -> 51

That makes extracting the suit and the denomination easy :

 suit = card & 3; // 0 -> clubs, 1 -> diamonds, 2 -> hearts, 3 -> spades

(Opinions differ as to the order of suits, which is irrelevant in
poker. Old bridge habits dictate my choice.)

 denomination = card >> 2; // 0 -> deuce, ..., 12 -> ace

> So, you might have a hand that starts out
> ["2H","4H","9H","10H","14D,"14H","14C"].

Or [2, 10, 30, 34, 49, 50, 48].

> Transform that into two arrays: [2,4,9,10,14,14,14],
> ["H","H","H","H","D","H","C"].

If one doesn't mind making new Arrays on the fly (I am not sure they
come cheap), I think they would be more useful in counting :

 [1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 3]

(counts the denominations : deuces, treys, ..., aces)

 [1, 1, 5, 0]

(counts the suits : clubs, diamonds, hearts and spades)

> If you have a total of 5 H's, then you have a flush.

Actually, I think it would be more efficient to implement the
counters as 4 bit wide portions of the 32 bits available for bit
manipulation in ES Numbers. I suspect (correct me if I am wrong) that

 suitCounts += (1 << ((card & 3) << 2)); // suitCounts is a Number used
                                         // as a bitmap and split in
                                         // four 4-bit counters

may be faster than

 suitCounts[card & 3]++; // suitCounts is an Array

and I am pretty sure that

 isFlush = Boolean((suitCounts + 0x00003333) & 0x00008888);

is faster than

 isFlush = false;
 for (i = 0; i < 4; i++) {
   if (suitCounts[i] >= 5) {
     isFlush = true;
     break;
   }
 }

> If you have 5 numbers in sequence, you have a straight, etc.

Table lookup is probably *much* faster.

It seems, rather surprisingly, that there are "only" 49205 different
possible denomination counts for 7 cards, from '0000000000034'
(four deuces and a trey - the two other treys get thrown out) to
'4300000000000' (four aces and a king - three kings, actually, but
only one counts) via things like '0021000001021' (two pairs, queens
and treys, fifth card jack). It is quite easy to construct such
counts in the same way we counted cards within each suit.

Now, ES's internal hashing mechanism is supposed to be good. Why
not simply initialise an Object with 49205 properties, each of which
is associated with a number that indicates the hand's strength ?

Something like this :

 var handRanks = {
   '0000000000034' => 0x700001,
   '0000000000043' => 0x711110,
   '0000000000124' => 0x700002,
   '0000000000133' => 0x611100,
   '0000000000142' => 0x711112,
   
           ...
           
   '4201000000000' => 0x7ccccb,
   '4210000000000' => 0x7ccccb,
   '4300000000000' => 0x7ccccb
 };

The code would weight about 1.5 MB, uncompressed, but is that
prohibitive nowadays ?

A note on the rank format :

The first 4 bits indicate what kind of hand :

 0 => high card,
 1 => one pair,
 2 => two pairs,
 3 => three of a kind,
 4 => straight,
 5 => flush,
 6 => full house,
 7 => four of a kind,
 8 => straight flush

The next 20 bits represent the 5 most significant cards denominations,
4 bits per card, from 0 (deuce) to 0xc (ace).

Altogether, this ensures 1. that stronger hand will always get a
higher value than a weaker hand, and 2. that the information can
easily be translated into something more palatable to human users.

> That would be a good start.

Indeed. I still have to construct the lookup table (by the way, I am
not going to do that in ES) and to figure out what to do with flushes,
whether straight or not. And, of course, test and time it all, and
put it on my site when it is ready.

In the meantime, comments, suggestions and criticism are welcome.

Signature

Johannes

Johannes Baagoe - 10 Mar 2010 04:37 GMT
Johannes Baagoe :

>   var handRanks = {
>     '0000000000034' => 0x700001,
[quoted text clipped - 9 lines]
>     '4300000000000' => 0x7ccccb
>   };

Oops, sorry, it isn't Perl. s/ =>/:/g

Signature

Johannes

Dr J R Stockton - 10 Mar 2010 22:48 GMT
In comp.lang.javascript message <O6Sdnaz_cZjrngrWnZ2dnUVZ8rti4p2d@gigane
ws.com>, Tue, 9 Mar 2010 20:27:02, Johannes Baagoe <baagoe@baagoe.com>
posted:
>Ken Snyder :
>
>> I would write a routine that sorts the cards numerically
>
>Fast solutions tend to avoid sorting - it takes time, and needs to
>be redone even if only one card changes.

Full sorting of N entries takes > o(N) time; removal and correct
insertion could need o(log2 N) time.

> ...

>Now, ES's internal hashing mechanism is supposed to be good. Why
>not simply initialise an Object with 49205 properties, each of which
[quoted text clipped - 4 lines]
>  var handRanks = {
>    '0000000000034' => 0x700001,

>            ...

>    '4300000000000' => 0x7ccccb
>  };
>
>The code would weight about 1.5 MB, uncompressed, but is that
>prohibitive nowadays ?

Can the entries be computed by under 1.5 MB of deliverable JavaScript
that runs in a reasonable time?

It is easy enough to code a page so that some or all of the script on
the page will not run when loaded from YOUR, or from any, server.  Users
will then take, and retain, a copy to use from their local disc, rather
than reloading frequently.

Once the table is stable, it can be supplied separately, say as an
include file.  That should largely remain in cache while you titivate
the rest of it all.

Signature

(c) John Stockton, Surrey, UK.  ?@merlyn.demon.co.uk   Turnpike v6.05   MIME.
Web  <URL:http://www.merlyn.demon.co.uk/> - FAQish topics, acronyms, & links.
Proper <= 4-line sig. separator as above, a line exactly "-- " (RFCs 5536/7)
Do not Mail News to me. Before a reply, quote with ">" or "> " (RFCs 5536/7)

Johannes Baagoe - 11 Mar 2010 08:36 GMT
Dr J R Stockton :

[Object with 49205 properties; I assumed it had to be computed separately
and delivered as a literal]

> Can the entries be computed by under 1.5 MB of deliverable JavaScript
> that runs in a reasonable time?

Actually, yes, thanks for making me reconsider the point.

The problem is to generate a representation of the 49205 different ways
7 cards can be distributed according to rank - how many deuces, treys,
fours, ..., aces.

I had found the answer by a brute-force exploration in C of all possible
hands, followed by a sort and elimination of doubles. But there is a much
better way: to realise that it is equivalent to figuring out all the
ways to accommodate 7 persons in 13 rooms, with the limitation that
no more that 4 persons can be in the same.

The following code does that :

 function place(n, current, capacity, rooms) {
   if (n <= 0) {
     print('[' + rooms + '] ' + ++count);
     return;
   }

   for (var i = current; i < rooms.length ; i++) {
     if (rooms[i] < capacity) {
       rooms[i]++;
       place(n - 1, i, capacity, rooms);
       rooms[i]--;
     }
   }
 }

 count = 0;
 place (7, 0, 4, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);

This promptly produces the desired sequence, from

 [4,3,0,0,0,0,0,0,0,0,0,0,0] 1
 [4,2,1,0,0,0,0,0,0,0,0,0,0] 2
 [4,2,0,1,0,0,0,0,0,0,0,0,0] 3
 [4,2,0,0,1,0,0,0,0,0,0,0,0] 4
 [4,2,0,0,0,1,0,0,0,0,0,0,0] 5

to

 [0,0,0,0,0,0,0,0,0,0,2,1,4] 49200
 [0,0,0,0,0,0,0,0,0,0,1,4,2] 49201
 [0,0,0,0,0,0,0,0,0,0,1,3,3] 49202
 [0,0,0,0,0,0,0,0,0,0,1,2,4] 49203
 [0,0,0,0,0,0,0,0,0,0,0,4,3] 49204
 [0,0,0,0,0,0,0,0,0,0,0,3,4] 49205

(If you want to try it in a browser, change the "print" function
to something else, say, some text that is incremented and inserted
into a <pre> element. I use the standalone console shells js and v8.)

There only remains to compute the hand value for each, assuming
than there are no flushes. Even the most naive evaluator should be
able to do that in less than a second.

Signature

Johannes

Dr J R Stockton - 11 Mar 2010 23:08 GMT
In comp.lang.javascript message <BtadnVz9LeU0NgXWnZ2dnUVZ8lti4p2d@gigane
ws.com>, Thu, 11 Mar 2010 02:36:57, Johannes Baagoe <baagoe@baagoe.com>
posted:

> ...
>The following code does that :
[quoted text clipped - 16 lines]
>  count = 0;
>  place (7, 0, 4, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);

Function place calls itself recursively.  Integers n & current keep
changing, but have a small range.  Argument capacity is a constant.
Argument rooms is a multiple answer.

That reminds me of the code for the number of ways of giving change for,
say, £5.00 given the available lower denominations, which has a simple
recursive expression.  That runs very much faster if a cache of the
answers to sub-problems is used, as demonstrated in
<URL:http://www.merlyn.demon.co.uk/js-misc0.HTM#JAD>, in which array
Cash is the cache.

It seems possible, but no more, that a similar trick might be used in
your case, if the code needs speeding up.  The cached items, indexed by
n & current, would be like a new Array(13) (?).

If the displayed code is not compact for you, View Source may be
preferred.

Signature

(c) John Stockton, nr London, UK.    ?@merlyn.demon.co.uk     Turnpike v6.05.
Web  <URL:http://www.merlyn.demon.co.uk/> - w. FAQish topics, links, acronyms
PAS EXE etc : <URL:http://www.merlyn.demon.co.uk/programs/> - see 00index.htm
Dates - miscdate.htm estrdate.htm js-dates.htm pas-time.htm critdate.htm etc.

Johannes Baagoe - 12 Mar 2010 12:12 GMT
Dr J R Stockton :

> Johannes Baagoe :

>>  function place(n, current, capacity, rooms) {
>>    if (n <= 0) {
[quoted text clipped - 10 lines]
>    }
>  }

> That reminds me of the code for the number of ways of giving change
> for, say, £5.00 given the available lower denominations, which
> has a simple recursive expression.

Aha ! that sounds like a nice problem.

> That runs very much faster if a cache of the answers to sub-problems
> is used, as demonstrated in
> <URL:http://www.merlyn.demon.co.uk/js-misc0.HTM#JAD>, in which array
> Cash is the cache.

Alas, Error 404.

> It seems possible, but no more, that a similar trick might be used
> in your case, if the code needs speeding up.  The cached items,
> indexed by n & current, would be like a new Array(13) (?).

It would hardly be worth the trouble, unless I feel the urge to
turn my ad hoc place function into something more generally useful.
Here, it is just a quick hack to do something I needed. But, as
it happens, Scott Sauyet applied it to a slightly different problem,
and I applied it to yet another, and that added up to an explanation
of a well-known result by "Cactus Kev" - the number of equivalence
classes of 7-card poker hands - which looked like magic to me before.

Signature

Johannes

Johannes Baagoe - 12 Mar 2010 12:17 GMT
Johannes Baagoe :

> a well-known result by "Cactus Kev" - the number of equivalence classes
> of 7-card poker hands

Oops - of 5-card poker hands, or simply of poker hands. It is here :
http://www.suffecool.net/poker/evaluator.html and here :
http://www.suffecool.net/poker/7462.html

Signature

Johannes

Dr J R Stockton - 13 Mar 2010 17:17 GMT
In comp.lang.javascript message <w5GdnfwvJY4rsgfWnZ2dnUVZ7oRi4p2d@gigane
ws.com>, Fri, 12 Mar 2010 06:12:38, Johannes Baagoe <baagoe@baagoe.com>
posted:
>Dr J R Stockton :

>> That reminds me of the code for the number of ways of giving change
>> for, say, £5.00 given the available lower denominations, which
>> has a simple recursive expression.
>
>Aha ! that sounds like a nice problem.

I mean the number of combinations of coins that may be given, regardless
of the order in which they are handed over.  I think the modification
for the latter would be trivial in my code.

>> That runs very much faster if a cache of the answers to sub-problems
>> is used, as demonstrated in
>> <URL:http://www.merlyn.demon.co.uk/js-misc0.HTM#JAD>, in which array
>> Cash is the cache.
>
>Alas, Error 404.

Typo : HTM too big.  Apologies.  This works :
<http://www.merlyn.demon.co.uk/js-misc0.htm#JAD>.
But the 404 text does link to the site index, in which all local links
are tested in at least one way, commonly two.

Signature

(c) John Stockton, Surrey, UK.  ?@merlyn.demon.co.uk   Turnpike v6.05   MIME.
Web  <URL:http://www.merlyn.demon.co.uk/> - FAQish topics, acronyms, & links.
Proper <= 4-line sig. separator as above, a line exactly "-- " (RFCs 5536/7)
Do not Mail News to me. Before a reply, quote with ">" or "> " (RFCs 5536/7)

Johannes Baagoe - 15 Mar 2010 21:17 GMT
Johannes Baagoe :

[Generating a rather large lookup table for fast poker hand evaluations]

> Even the most naive evaluator should be able to do that in less than
> a second.

Actually, not quite. With efficient ES implementations like Chrome's v8,
it takes a bit more than a second on my laptop with a dual T3200 @ 2.00GHz.
With SpiderMonkey / Firefox, it takes more than ten seconds. Opera takes
about 5.

But speed gain is spectacular. With v8, I get close to 4 million
evaluations per second. Pokersource Poker-Eval's fish program (in heavily
macro'ed C) runs at about 16.5 million per second on the same laptop.

The optimised evaluator is like this :

 var handValues = computeHandValues(); // lookup table
 
 function fastHandEval(card0, card1, card2, card3, card4, card5, card6) {
   var suitCounts = 0x3333; // four 4-bit counters, one for each suit
                            // all initialised to 3

   var key; // a 13-digit number in base 5 to serve as lookup key.

   var powersOf5 = [1, 5, 25, 125, 625, 3125, 15625, 78125, 390625,
     1953125, 9765625, 48828125, 244140625];
   // table lookup is faster than Math.pow(5, n)

   suitCounts += (1 << ((card0 & 3) << 2));
   suitCounts += (1 << ((card1 & 3) << 2));
           // ...
   suitCounts += (1 << ((card6 & 3) << 2));

   if (suitCounts & 0x8888) { // there is a flush
     var flushSuit;  // suit of the flush

     if (suitCounts & 0x88) {
       if (suitCounts & 0x8) {
         flushSuit = 0;
       } else {
         flushSuit = 1;
       }
     } else {
       if (suitCounts & 0x800) {
         flushSuit = 2;
       } else {
         flushSuit = 3;
       }
     }

     key  = (card0 & 3) === flushSuit ? powersOf5[(card0 >> 2)] << 1 : 0;
           // ...
     key += (card6 & 3) === flushSuit ? powersOf5[(card6 >> 2)] << 1 : 0;

     return handValues[key];
   }

   // no flush
   key  = powersOf5[card0 >> 2];
           // ...
   key += powersOf5[card6 >> 2];

   return handValues[key];
 }

Signature

Johannes

Lasse Reichstein Nielsen - 16 Mar 2010 07:12 GMT
> The optimised evaluator is like this :

It does look very finely tuned.
I have only a few small suggestions.

>   var handValues = computeHandValues(); // lookup table
>  
>   function fastHandEval(card0, card1, card2, card3, card4, card5, card6) {

>     var powersOf5 = [1, 5, 25, 125, 625, 3125, 15625, 78125, 390625,
>       1953125, 9765625, 48828125, 244140625];

You create a new array on each call, and throw it away at the end of
the call. If you could create it once and pass it as a parameter, you
could save a lot of memory grinding.
(Or, you could have it in a global variable, and then read it into a
local variable at this point to avoid repeatedly accessing global
variables).

>     // table lookup is faster than Math.pow(5, n)
>     if (suitCounts & 0x8888) { // there is a flush
>       var flushSuit;  // suit of the flush
>
>       if (suitCounts & 0x88) {

Ingenious :)

>       key  = (card0 & 3) === flushSuit ? powersOf5[(card0 >> 2)] << 1 : 0;
>             // ...
>       key += (card6 & 3) === flushSuit ? powersOf5[(card6 >> 2)] << 1 : 0;

Here you could do the << 1 at the end, instead of doing it for each card.
I.e., drop it from above and do
       return handValues[key << 1];

And while we are at it, you could pass handValues as a parameter too,
to avoid having it as a global variable. Global variables suck.
Alternatively, you could create this function inside the scope of
another function that declars handValues and powersOf5, so that the
variables are still looked up, but not on the global object.

It might not do much, though, since handValues is only used once
per call - but it's worth a try.

/L
Signature

Lasse Reichstein Holst Nielsen
'Javascript frameworks is a disruptive technology'

Johannes Baagoe - 16 Mar 2010 17:58 GMT
Lasse Reichstein Nielsen :

> Johannes Baagoe :

>> The optimised evaluator is like this :

> I have only a few small suggestions.

Excellent ones, as the tests show. What I use is a rather standard
exploration of all the 133 784 560 possible 7-card hands, like this :

 function allHands() {
   var count = 0;
   var types = [0, 0, 0, 0, 0, 0, 0, 0, 0];
   
   for (var c0 = 0; c0 < 52; c0++) {
     for (var c1 = c0 + 1; c1 < 52; c1++) {
        / ...
                 var type = fastHandEval(c0, c1, c2, c3, c4, c5, c6);
                 types[type >> 20]++;
                 count++;
        /...
     }
   }
   
   for (var type = 0; type < 9; type++) {
     print(types[type]);
   }
   print(count);
 }

 var now = new Date(); allHands() ; print(new Date() - now + ' ms');

All are with Chrome's v8 engine on a dual T3200 @ 2.00GHz. It is IMHO
rather amazing that it is possible to run it at all in a reasonable
time in ES. I wouldn't attempt it with Firefox / SpiderMonkey or
Opera, though, and I have nothing else. I don't know if anybody is
interested enough in trying other platforms to justify that I put
the entire code on my website before I have finalised a version that
actually does something useful.

Before your suggestions, the test ran in 37343 ms.

>>     var powersOf5 = [1, 5, 25, 125, 625, 3125, 15625, 78125, 390625,
>>       1953125, 9765625, 48828125, 244140625];

> You create a new array on each call, and throw it away at the end of the
> call. If you could create it once and pass it as a parameter, you could
> save a lot of memory grinding.

> (Or, you could have it in a global variable, and then read it into a
> local variable at this point to avoid repeatedly accessing global
> variables).

Making it global makes the time drop to 29165 ms. No surprise -
I could kick myself for that oversight.

>>     key  = (card0 & 3) === flushSuit ? powersOf5[(card0 >> 2)] << 1 : 0;
>>             // ...
>>     key += (card6 & 3) === flushSuit ? powersOf5[(card6 >> 2)] << 1 : 0;

> Here you could do the << 1 at the end, instead of doing it for each
> card. I.e., drop it from above and do
>         return handValues[key << 1];

Indeed. And what amazes me is how much that improves performance -
the time drops to 27499 ms, that is by nearly 6 %. I am at a loss to
explain it, since that branch is only supposed to be taken about 3 %
of the time, and this factoring out of a shift affects less that
half of instructions; I would have expected a marginal improvement
of about 1 %, not 6. I repeated the tests a couple of times with both
versions, and the ~ 6 % improvement was consistent, but it may still
have been a fluke. Do you have a better explanation ?

> Global variables suck.

Sure, but avoiding them by the means of closures seems to degrade
performance. If I wrap the whole thing in a

 var texasHoldemHandEval = (function () {
   ...
   return fastHandEval;
 })();

the time of the test increases again to 32412 ms. I shall try to
figure out something better, suggestions are welcome.

On the other hand, the time drops to 26377 ms if I copy powersOf5
on entry. (Copying handValues rather degrades performance a bit, no
surprise, since is only called once.) Altogether, your suggestions have
improved speed by about 30 %, and the evaluator now reaches more than
5 million evaluations per second ! Not bad for a supposedly slow and
inefficient language...

Signature

Johannes

Lasse Reichstein Nielsen - 16 Mar 2010 21:14 GMT
...
> All are with Chrome's v8 engine on a dual T3200 @ 2.00GHz. It is IMHO
> rather amazing that it is possible to run it at all in a reasonable
> time in ES. I wouldn't attempt it with Firefox / SpiderMonkey or
> Opera, though, and I have nothing else.

If you have the new Opera 10.50 (or even 10.51rc), you should try it
there. They seem to have both good memory management and fast
arithmetic, so they should do well.

> Before your suggestions, the test ran in 37343 ms.

...
>>>     key  = (card0 & 3) === flushSuit ? powersOf5[(card0 >> 2)] << 1 : 0;
>>>             // ...
[quoted text clipped - 12 lines]
> versions, and the ~ 6 % improvement was consistent, but it may still
> have been a fluke. Do you have a better explanation ?

My best guess is code density. You do conditional jumps. If they skip
less, they are more likely to hit something that's already in the
instruction look-ahead or code cache.

I just noticed that you are adding zeros (although in at most two out
of seven cases, since you know you have a flush).
How about, for every card except the first, to do:
 if ((card1 & 3) == flushSuit) key += powersOf5[card6 >> 2];
 ...
It seems like it should be slightly shorter and faster.

>> Global variables suck.
>
[quoted text clipped - 7 lines]
>
> the time of the test increases again to 32412 ms.

Probably because the function call must introduce a non-trivial scope.

> I shall try to figure out something better, suggestions are welcome.

Have you tried keeping handValues and powersOf5 in the calling function,
and passing them as parameters to fastHandEval.
I don't know if passing two extra parameters will cost more than reading
them from the global scope, but it's worth a try.

Best of luck.
/L
Signature

Lasse Reichstein Holst Nielsen
'Javascript frameworks is a disruptive technology'

Johannes Baagoe - 09 Mar 2010 20:23 GMT
Stevo :

>> Has anybody written a fast Texas hold'em hand evaluator in
>> ECMAScript?

> So you want people here to help you cheat and take people's money?

That remark is both rude and stupid. If you knew anything about
the subject, you would realise that one cannot cheat people out
of their money merely by using a hand evaluator, let alone one in
ECMAScript. So, in this particular case, I can actually prove my
absence of nefarious motives - but usually one cannot, which is why
the presumption of innocence was invented, and why it is appallingly
bad manners to question motives instead of discussing the facts.

No, I want to share experiences in ECMAScript programming, which I
understand is the purpose of this newsgroup. As it happens, there
are quite a few specific problems that arise. Just one example:

Most of the time, in order to evaluate a hand, it suffices to keep
track of the number of cards in each suit, and of the number of cards
of each denomination - how many aces, kings, etc.

But in one relatively rare case - when there are 5 cards or more in
one suit - this is not enough; one has to know which denominations
are in that suit. So, one has to keep track of the exact cards,
e.g. by pushing them on a stack, or by setting a bit for each in a
52-bit mask, which may be faster, and which is what languages with
64-bit integers usually do.

Now, ECMAScript has no 64-bit integers, but it has Numbers with a
52-bit mantissa, which happens to be just enough. Using it could
well be the fastest way of *saving* the information as it comes, and
while *retrieving* it later may be something of a PINA, it would only
occur in about 3 % of the cases. (4089228 times out of 133784560,
to be exact.) It seems to be worth exploring. Nobody in his right
mind would do that in C, but ECMAScript is different.

Signature

Johannes

Dr J R Stockton - 10 Mar 2010 18:45 GMT
In comp.lang.javascript message <O6Sdna3_cZimMwvWnZ2dnUVZ8rti4p2d@gigane
ws.com>, Tue, 9 Mar 2010 14:23:23, Johannes Baagoe <baagoe@baagoe.com>
posted:
> ...

>Now, ECMAScript has no 64-bit integers, but it has Numbers with a
>52-bit mantissa, which happens to be just enough. Using it could
[quoted text clipped - 3 lines]
>to be exact.) It seems to be worth exploring. Nobody in his right
>mind would do that in C, but ECMAScript is different.

FWIW, the mantissa can be considered as 53 bits (all integer values from
0 to 2^53-1 store exactly), and there is a sign bit too.

As an alternative to using Number, a four-character String cam be
considered, with some handling difficulties, as a 4 element array of
16-bit unsigned integers (4 suits of 13 cards); an 8-character String
can store 8 bytes, etc.  Probably slower, though.

I have code in <URL:http://www.merlyn.demon.co.uk/js-misc0.htm> for
converting a Number to its 64 bits and /vice versa/.

Signature

(c) John Stockton, nr London UK. ?@merlyn.demon.co.uk  BP7, Delphi 3 & 2006.
<URL:http://www.merlyn.demon.co.uk/> TP/BP/Delphi/&c., FAQqy topics & links;
<URL:http://www.bancoems.com/CompLangPascalDelphiMisc-MiniFAQ.htm> clpdmFAQ;
NOT <URL:http://support.codegear.com/newsgroups/>: news:borland.* Guidelines

Scott Sauyet - 09 Mar 2010 21:45 GMT
> Has anybody written a fast Texas hold'em hand evaluator in ECMAScript?

I never have, and would not choose ES to code one without good reason.

If issues of speed are not that important, this would not be hard to
code, either in the compare-two-hands version or the score-a-hand
version.

But it sounds like you're looking for something optimized for speed.
I don't think many ES implementation are optimized for numeric
calculations of that sort.

> (NB: this is Texas hold'em, with 7 cards. Naive assumptions from 5 card
> poker do not necessarily apply. There may be a flush and a straight,
> but no straight flush. Also, there may be both 4 of a kind and 3 of
> a kind, etc. What is wanted is the value of the best 5 cards subset.)

There are only twenty-one 5-card hands that can be made from a 7-card
hand, so it would be reasonable to simply do a maximum score on those
sub-hands to make a 7-card hand evaluator out of a 5-card hand one.
But again, this means 21 runs of the 5-card hand evaluator, clearly
not a technique optimized for speed.

 -- Scott
Johannes Baagoe - 10 Mar 2010 05:26 GMT
Scott Sauyet :

> Johannes Baagoe :

>> Has anybody written a fast Texas hold'em hand evaluator in
>> ECMAScript?

> I never have, and would not choose ES to code one without good reason.

I have no better reason than a taste for challenges.

> If issues of speed are not that important, this would not be hard
> to code, either in the compare-two-hands version or the score-a-hand
> version.

I thought so, too, until I tried :) It is a lot trickier with 7 cards
than with 5. While my present focus is on speed, I would still love to
see a short and truly elegant solution, even if it is slow. I would
love to write one, for that matter, but I am not satisfied by any of
my efforts so far.

> But it sounds like you're looking for something optimized for
> speed. I don't think many ES implementation are optimized for numeric
> calculations of that sort.

Quite, but how much speed can we still tweak our of them ?

> There are only twenty-one 5-card hands that can be made from a
> 7-card hand, so it would be reasonable to simply do a maximum score
> on those sub-hands to make a 7-card hand evaluator out of a 5-card
> hand one. But again, this means 21 runs of the 5-card hand evaluator,
> clearly not a technique optimized for speed.

No, and besides, I would consider *that* to be cheating.

Signature

Johannes

Scott Sauyet - 10 Mar 2010 20:12 GMT
> Scott Sauyet :
>> Johannes Baagoe :
[quoted text clipped - 3 lines]
>
> I have no better reason than a taste for challenges.

That's a good enough reason for me!  There are definitely some
interesting questions about appropriate representation in JS, where 52-
bit structures are not easy to manage and where bit manipulations are
generally not at all optimized.

Maybe if I find some time over the weekend, I'll see what I can
create.

>> If issues of speed are not that important, this would not be hard
>> to code, either in the compare-two-hands version or the score-a-hand
[quoted text clipped - 5 lines]
> love to write one, for that matter, but I am not satisfied by any of
> my efforts so far.

It seems to me that any obvious solution will end up doing one of two
things.  Either it will score each 5-card sub-hand of the 7 cards you
have, or it will successively search for 5-card hands for each score
type starting with straight-flush and working down to 7-high.

>> But it sounds like you're looking for something optimized for
>> speed. I don't think many ES implementation are optimized for numeric
>> calculations of that sort.
>
> Quite, but how much speed can we still tweak our of them ?

I don't know.  Let's see, shall we?

>> There are only twenty-one 5-card hands that can be made from a
>> 7-card hand, so it would be reasonable to simply do a maximum score
[quoted text clipped - 3 lines]
>
> No, and besides, I would consider *that* to be cheating.

Why is that?  Granted, these two are distinct tasks:

   (1) Find the value of the best 5-card hand that can be made from
these seven cards.
   (2) Find the best 5-card hand that can be made from these seven
cards.

But one of the most straightforward ways to do the former involves the
latter as an intermediate step.  And I think most solutions will have
an abstract version of the latter embedded in them.  I'd love to see a
purely mathematical version which doesn't do this in practice, but I'm
not holding my breath.  What sorts of systems are you imagining that
don't in some sense find a five-card hand to compare?

 -- Scott
Johannes Baagoe - 11 Mar 2010 09:51 GMT
Scott Sauyet :

> There are definitely some interesting questions about appropriate
> representation in JS, where 52- bit structures are not easy to manage
> and where bit manipulations are generally not at all optimized.

I agree.

> Maybe if I find some time over the weekend, I'll see what I can
> create.

That would be nice.

>> how much speed can we still tweak our of them ?

> I don't know.  Let's see, shall we?

I'm all in :)

> these two are distinct tasks:

> (1) Find the value of the best 5-card hand that can be made from
> these seven cards.  (2) Find the best 5-card hand that can be made
> from these seven cards.

> But one of the most straightforward ways to do the former involves the
> latter as an intermediate step.  And I think most solutions will have
> an abstract version of the latter embedded in them.  I'd love to see
> a purely mathematical version which doesn't do this in practice, but
> I'm not holding my breath.  What sorts of systems are you imagining
> that don't in some sense find a five-card hand to compare?

They do obviously find a five-card hand, the best one of the 21
possibilities, but not simply by generating the 21 permutations and
evaluating them all. A rough outline:

First, a pass over the seven cards count the number of cards in each
rank and each suit, and keeps a track of the exact seven cards in
case one suit has five cards or more, which means a flush.

If there is a flush, all cards of other suits are meaningless - with
only seven cards, there is no way there could also be a quad or a full
house, and a flush beats anything else. So the only thing to do is
to find the 5 highest cards in that suit, and to check whether there
is an uninterrupted run of five consecutive ranks, making a straight
flush. All that takes is pass through the rank counts, starting from
the top, that is, the aces.

If there is no flush (about 97% of all cases), the only thing that
matters is the number of cards by rank. A provisional version of
the algorithm steps through the rank counts starting from the aces
and keeps count of all the information that is needed - similar to
what is done for the flushes, but rather more involved. The final,
optimised version simply looks in a big table which has been built
by the provisional version and returns the result.

Signature

Johannes

Scott Sauyet - 11 Mar 2010 15:23 GMT
> Scott Sauyet :
>> these two are distinct tasks:
[quoted text clipped - 11 lines]
> possibilities, but not simply by generating the 21 permutations and
> evaluating them all. A rough outline:

I love how quickly I can be proven wrong!  :-)

> First, a pass over the seven cards count the number of cards in each
> rank and each suit, and keeps a track of the exact seven cards in
[quoted text clipped - 3 lines]
> only seven cards, there is no way there could also be a quad or a full
> house, and a flush beats anything else.

Ahh, this is a point I had missed.

> So the only thing to do is
> to find the 5 highest cards in that suit, and to check whether there
> is an uninterrupted run of five consecutive ranks, making a straight
> flush. All that takes is pass through the rank counts, starting from
> the top, that is, the aces.

Well, you'd have to check for the straight before selecting the
highest ones, and you'd have to have already removed non-flush cards
from the rank count, but yes, this makes sense.

> If there is no flush (about 97% of all cases), the only thing that
> matters is the number of cards by rank. A provisional version of
[quoted text clipped - 3 lines]
> optimised version simply looks in a big table which has been built
> by the provisional version and returns the result.

And presumably each rating would be fast enough that the startup time
to calculate the table (if it's not actually loaded as code or simple
data) would be fast enough to not matter in any reasonable system.
Then the per-hand calculation would amount to the flush-check, and the
creation of the lookup key -- a list of rank counts.  It's an
interesting approach.  I'd love to see how it compares to the naive
check-all-combinations approach (with of course a similar table
creation for the 6175 different rank counts on five cards.)

I hope I have time to play with this over the weekend.  Thanks for
posing the problem.

 -- Scott
Johannes Baagoe - 11 Mar 2010 17:17 GMT
Scott Sauyet :

> Johannes Baagoe :

[Determining the ranks of the five most significant cards in a flush,
and whether it is a straight flush]

>> So the only thing to do is to find the 5 highest cards in that
>> suit, and to check whether there is an uninterrupted run of five
>> consecutive ranks, making a straight flush. All that takes is pass
>> through the rank counts, starting from the top, that is, the aces.

> Well, you'd have to check for the straight before selecting the
> highest ones, and you'd have to have already removed non-flush cards
> from the rank counter

Not necessarily.

Instead of removing non-flush cards first, I check for each rank with
a non-zero count whether the card with that rank and the right suit
is present in the hand. If not, I treat that rank as if it had no
cards at all.

And instead of checking for straights first, I do it while pushing
cards on an array in case I don't find one - chances are that they
will end up being the relevant information. If I find a straight,
it is easy enough to take its (consecutive) ranks instead.

The same kind of reasoning also applies to non-flush hands in the
provisional evaluator. While I walk down the ranks, I push single
cards, pairs (there may be three of them !), trips (could be two) and
quads on separate arrays - and I also keep count of run lengths. At
the end of the walk, it is a simple matter of choosing from which
arrays to unshift a total of five card ranks.

Mind you, this is mainly for the sake of elegance. That evaluator
is only going to be used about 55,000 times at startup, in order to
build the lookup table. It needs not be blazingly fast.

> the per-hand calculation would amount to the flush-check,

Not even that - I found since that flushes could also be incorporated
in the lookup table without making it too big. Actually, when I have
finished the ES version, I shall have a go at a C version with a
hash table - it could be small enough to reside entirely in the cache
memory of many recent processors, which should make it *very* fast.

> It's an interesting approach.  I'd love to see how it compares to
> the naive check-all-combinations approach

I should be very surprised if it were not at least ten times faster.
But it depends a lot on how efficient the internal hash functions
of ES implementations are.

> (with of course a similar table creation for the 6175 different rank
> counts on five cards.)

6175 ? http://www.suffecool.net/poker/evaluator.html says 7462,
but I haven't checked. The same source says 4824 for 7-card hands,
choosing best five.

> I hope I have time to play with this over the weekend.

I'll try to stop spoiling till then :)

> Thanks for posing the problem.

You're welcome.

Signature

Johannes

Scott Sauyet - 11 Mar 2010 17:55 GMT
> Scott Sauyet :
>
[quoted text clipped - 29 lines]
> the end of the walk, it is a simple matter of choosing from which
> arrays to unshift a total of five card ranks.

So if you're walking [K, 8, 7, 6, 5, 4, 2], you're tracking the run
length and hit a run of 5 when you reach the 4.  Do you stop there?
Obviously you don't need to know any more, except about the flush.
But have you pushed the initial cards onto a another array?  Or do you
reuse this original one if you end up with nothing better than King-
high?

> Mind you, this is mainly for the sake of elegance. That evaluator
> is only going to be used about 55,000 times at startup, in order to
> build the lookup table. It needs not be blazingly fast.

>> the per-hand calculation would amount to the flush-check,
>
[quoted text clipped - 3 lines]
> hash table - it could be small enough to reside entirely in the cache
> memory of many recent processors, which should make it *very* fast.

How do you include them in the table?  I was assuming that there would
be an initial check for a flush, then a isFlush boolean would be
passed to the lookup.  Do you have a technique that skips that initial
check?

>> It's an interesting approach.  I'd love to see how it compares to
>> the naive check-all-combinations approach
>
> I should be very surprised if it were not at least ten times faster.
> But it depends a lot on how efficient the internal hash functions
> of ES implementations are.

Since the entire object design of ES is based upon hash lookups, I'm
guessing that they are relatively optimized.

>> (with of course a similar table creation for the 6175 different rank
>> counts on five cards.)
>
> 6175 ?http://www.suffecool.net/poker/evaluator.htmlsays 7462,
> but I haven't checked. The same source says 4824 for 7-card hands,
> choosing best five.

Cactus Kev's numbers are for the number of distinct hand value
classes, from. four of the two million+ possible hands have the
highest values of royal flushes.down to 1020 possible non-flush [7, 5,
4, 3, 2]-hands.  I'm discussing only the counts of the ranks, i.e. the
results of

   place (5, 0, 4, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);

with the function you supplied earlier.

>> I hope I have time to play with this over the weekend.
>
> I'll try to stop spoiling till then :)

Well my thinking has so far been in an entirely different direction,
so...
Johannes Baagoe - 12 Mar 2010 03:59 GMT
Scott Sauyet :

> So if you're walking [K, 8, 7, 6, 5, 4, 2], you're tracking the
> run length and hit a run of 5 when you reach the 4. Do you stop
> there? Obviously you don't need to know any more, except about the
> flush. But have you pushed the initial cards onto a another array?

> Or do you reuse this original one if you end up with nothing better
> than King- high?

Actually, what I am walking through is the *rank counts*, O ace,
1 king, 0 queen, etc: [0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1].
Not the 7 cards.

And while I walk through them, I maintain an array of five arrays:
one for runs (it is reset to [] whenever I encounter a gap unless it
has a length of five, otherwise, the current rank is pushed on it),
one for high cards (whenever the count is 1, the current rank is
pushed on it), one for pairs (whenever the count is 2, the current
rank is pushed on it *twice*, etc.

Here, we are talking of the first, somewhat naive version of the
evaluator. It will only be executed 53,924 times - speed matters
less than clarity, especially since its overall structure helps
to explain how the second version it is used to create, the one
with the table lookup, is going to work. In both versions, we first
determine if there is a flush, and then, we exploit the rank counts
in two different ways according to whether there is one or not. The
complexity of those treatments in the first version shows the merit
of replacing them by a simple table lookup in the second.

But that argument is worthless if the first version is complicated
and messy. Therefore, I aim to make it as simple and elegant as I
can. So, instead of complicated ad hoc mechanisms for each of the
cases, I systematically use my array of arrays - it simplifies the
code. At the end of the walk, I shall use the 5 arrays as FIFOs and
shift from them, in the appropriate order according to which ones
have a non-zero length, until I have the 5 most significant ranks.

Of course, I could stop the walk before reaching the end in many
cases, like the one you mention. Or at least stop pushing on some
of the arrays. But I believe the rather complicated tests it would
entail would do more harm than good - certainly from the point of
view of clarity, and perhaps from the point of view of performance
as well. Such tests often cost more than the slight gain they give
in comparatively rare cases. Premature optimisation, and all that.

>> I found since that flushes could also be incorporated
>> in the lookup table without making it too big.

> How do you include them in the table?  I was assuming that there
> would be an initial check for a flush, then a isFlush boolean would
> be passed to the lookup.  Do you have a technique that skips that
> initial check?

No, it does not skip the initial check, even if I use a more efficient
means in the optimised version. But after that check, I construct a
31-bit "signature" of the 13 rank counts that is slightly different
in the two cases. If there is no flush, I simply code the counts as
a 13-digit number in base 5. If there is a flush, the only thing that
matters is the presence or absence of a card in the right suit for each
of the ranks. So, I code the ranks where it is present with a digit of
2, and those where it is not with 0, still giving a 13-digit number
in base 5. Since there are at least 5 places where it is present in
the case of a flush, the sum of the digits is at least 10, whereas in
the case of no flush, that sum is always 7. Therefore, there can be no
collisions. The table doesn't care about the meaning of the signature,
it is just an access key. Incorporating the flushes increases the
number of entries by less than 10 %, from 49205 to 53924 - probably
well worth the simplification in logic.

>>> It's an interesting approach.  I'd love to see how it compares
>>> to the naive check-all-combinations approach

>> I should be very surprised if it were not at least ten times faster.
>> But it depends a lot on how efficient the internal hash functions
>> of ES implementations are.

> Since the entire object design of ES is based upon hash lookups,
> I'm guessing that they are relatively optimized.

That is what I rely on. We shall see.

>>> (with of course a similar table creation for the 6175 different
>>> rank counts on five cards.)

>> 6175 ? http://www.suffecool.net/poker/evaluator.html says 7462,
>> but I haven't checked. The same source says 4824 for 7-card hands,
>> choosing best five.

> Cactus Kev's numbers are for the number of distinct hand value
> classes, from. four of the two million+ possible hands have the
> highest values of royal flushes.down to 1020 possible non-flush [7,
> 5, 4, 3, 2]-hands.  I'm discussing only the counts of the ranks,
> i.e. the results of

> place (5, 0, 4, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);

> with the function you supplied earlier.

Aha ! And place(5, 0, 1, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
which is appropriate for flushes yields 1287, and 6175 + 1287 = 7462,
giving Cactus Kev's result. It makes sense :)

Signature

Johannes

Lasse Reichstein Nielsen - 11 Mar 2010 17:42 GMT
> They do obviously find a five-card hand, the best one of the 21
> possibilities, but not simply by generating the 21 permutations and
[quoted text clipped - 3 lines]
> rank and each suit, and keeps a track of the exact seven cards in
> case one suit has five cards or more, which means a flush.

I have wrapped up a quick attempt at a value function at
<URL:http://www.infimum.dk/private/pokerhand.js>
Not tested that well yet (removeCard in particular), but seems to
correctly evaluate the simple cases I have manually thrown at it.

I keep track of the card values in each suit, as a bitmap, and
the count of cards in each suit (to detect a flush).

> If there is a flush, all cards of other suits are meaningless - with
> only seven cards, there is no way there could also be a quad or a full
[quoted text clipped - 3 lines]
> flush. All that takes is pass through the rank counts, starting from
> the top, that is, the aces.

If you have a bit-vector of cards in the flsuh suit (e.g., by keeping
one for each suit), it's only a 269 element lookup table to see if
there is a straight. That's fairly easy (otherwise, you can do a little
bit fiddling to find the straight). I haven't used the table.

> If there is no flush (about 97% of all cases), the only thing that
> matters is the number of cards by rank. A provisional version of
[quoted text clipped - 3 lines]
> optimised version simply looks in a big table which has been built
> by the provisional version and returns the result.

It seems that the ~49k entries can be seen as numbers in base 5 as
well as strings. All the values are actually valid 32-bit (signed)
numbers (four aces and two or more kings is sadly not a valid
signed 31 bit integer).

I have kept a sorted list of counts of each rank present, sorted
by count then rank. It's at most seven entries, so I haven't done
anything fancy to keep them in order (like a red/black binary tree
or similar).

/L
Signature

Lasse Reichstein Holst Nielsen
'Javascript frameworks is a disruptive technology'

Scott Sauyet - 11 Mar 2010 18:37 GMT
> I have wrapped up a quick attempt at a value function at
> <URL:http://www.infimum.dk/private/pokerhand.js>

I get a 404 on that script.

 -- Scott
Lasse Reichstein Nielsen - 11 Mar 2010 19:50 GMT
>> I have wrapped up a quick attempt at a value function at
>> <URL:http://www.infimum.dk/private/pokerhand.js>
>
> I get a 404 on that script.

Argh, typical typo for me:
<URL:http://www.infimum.dk/privat/pokerhand.js>
Not the first time that particular error has been made.

/L
Signature

Lasse Reichstein Holst Nielsen
'Javascript frameworks is a disruptive technology'

Johannes Baagoe - 12 Mar 2010 05:55 GMT
Lasse Reichstein Nielsen :

> I have wrapped up a quick attempt at a value function at
> <URL:http://www.infimum.dk/private/pokerhand.js>

For the benefit of other readers:
<URL:http://www.infimum.dk/privat/pokerhand.js>, as you corrected,
.
> Not tested that well yet (removeCard in particular), but seems to
> correctly evaluate the simple cases I have manually thrown at it.

I like the way you wrap everything nicely in a constructor, which is
clearly the way to go.

I also like the idea of a removeCard function, if it can be made
significantly faster than simply starting over again. The big problem
I encounter is when removing a card (or replacing it) changes the
status from a flush being present to no flush being present.

> I keep track of the card values in each suit, as a bitmap,

Same for me in the optimised version of the evaluator. In the naive
version I use an Object: isPresent[card] == true iff card (a number
in 0 .. 51) is in the hand.  (The naive version is used to build the
table that drives the optimised version. It can also serve to measure
just how much is gained - or not - by table lookup.)

> and the count of cards in each suit (to detect a flush).

That appears to be the first thing to check in any reasonable algorithm.

I put four 4-bit counters on the 16 lower bits of an integer, say
suitCounts, and test like this (with my encoding of suits) :

 switch ((suitCounts + 0x00003333) & 0x00008888) {
   case 0: print('No flush');
     flushSuit = -1; // no flush
     break;
   case 0x00000008:
     flushSuit = 0; // flush in clubs
     break;
   case 0x00000080:
     flushSuit = 1; // flush in diamonds
     break;
   case 0x00000800:
     flushSuit = 2; // flush in hearts
     break;
   case 0x00008000:
     flushSuit = 3; // flush in spades
     break;
 }

I wonder how efficient that is in ES, though. Maybe if statements
are faster. Perhaps it depends on the implementations. Any information
on that, anyone ?

> If you have a bit-vector of cards in the flsuh suit (e.g., by keeping
> one for each suit), it's only a 269 element lookup table to see if
> there is a straight. That's fairly easy (otherwise, you can do a
> little bit fiddling to find the straight). I haven't used the table.

I don't think I will use that either. In the naive version I avoid
anything fancy. In the optimised version, one single table lookup
takes care of everything.

> It seems that the ~49k entries can be seen as numbers in base 5 as
> well as strings.

That is indeed my first choice - 13 multiply-adds should be about as
fast a way to code 13 chunks of information as one can get. Not to
mention that the multiplications are by 5 - increment by self shifted 2
bits up, but perhaps that kind of tricks don't make sense any longer.

I expect integer indexes into a sparse Array to be more efficient than
Objects with strings as access keys, since converting between numbers
and strings take time. But perhaps one should also check an Object with
e.g. toString(36) for minimum key length, and see how that compares.

It is no longer ~49k entries, by the way, rather 55 - I have added the
flushes. They get a count of 2 (could be 3 or 4) for each rank in the
suit, and O for any rank without a card in the suit. Since there is no
way a real rank distribution can have 5 pairs or more, these entries
won't collide with the "normal" ones. There are 4719 such entries -
1287 for 5 cards in the flush, and 1716 for each of 6 or 7 cards.

> All the values are actually valid 32-bit (signed) numbers (four aces
> and two or more kings is sadly not a valid signed 31 bit integer).

What is wrong with 32 bit unsigned integers ?

Anyway, Math.pow(5, 13) is 1 220 703 125, and parseInt('4300000000000', 5)
is 1 123 046 875. 31 bits are enough.

> I have kept a sorted list of counts of each rank present, sorted
> by count then rank. It's at most seven entries, so I haven't done
> anything fancy to keep them in order (like a red/black binary tree
> or similar).

That would indeed seem to be overkill, but why do you need that list ?

Signature

Johannes

Johannes Baagoe - 13 Mar 2010 17:40 GMT
Johannes Baagoe :

> Has anybody written a fast Texas hold'em hand evaluator in ECMAScript?

> What is wanted is a function, say handEval(hand), which, given a
> suitable representation of a hand of 7 cards, returns a value that
> accurately represents the strength of the hand. That is, handEval(hand0)
> > handEval(hand1) iff hand0 beats hand1, handEval(hand0) ==
> handEval(hand1) iff hand0 and hand1 tie, and handEval(hand0) <
> handEval(hand1) iff hand1 beats hand0.

> It would be nice, too, if the return value could easily indicate what
> kind of hand it is - high card, pair, two pairs, etc, preferably with
> further information to break ties within each kind.

My preliminary, somewhat naive version is here :
file:///home/baagoe/html/baagoe.com/en/ES/Poker/pokerDemo.xhtml

As indicated, it will serve to build a lookup table which a second,
hopefully much faster version is going to use.

Signature

Johannes

Johannes Baagoe - 13 Mar 2010 17:51 GMT
Johannes Baagoe :

> My preliminary, somewhat naive version is here :
> file:///home/baagoe/html/baagoe.com/en/ES/Poker/pokerDemo.xhtml

Oops, copied the wrong tab.

http://baagoe.com/en/ES/Poker/pokerDemo.xhtml

Signature

Johannes

Scott Sauyet - 14 Mar 2010 02:53 GMT
> Johannes Baagoe :
>
[quoted text clipped - 4 lines]
>
> http://baagoe.com/en/ES/Poker/pokerDemo.xhtml

I haven't yet looked at the code, but there is some bug in it right
now.  A random test gave this hand:

   Jd, 6s, 3s, Ah, 4s, 5d, 10c

and this result: 0x44321c, Straight; top card: 6.

 -- Scott
Johannes Baagoe - 14 Mar 2010 04:30 GMT
Scott Sauyet :

> Johannes Baagoe :

>> http://baagoe.com/en/ES/Poker/pokerDemo.xhtml

> I haven't yet looked at the code, but there is some bug in it right now.
>  A random test gave this hand:
>
>     Jd, 6s, 3s, Ah, 4s, 5d, 10c
>
> and this result: 0x44321c, Straight; top card: 6.

Yes, indeed. My test for "wheels" was flawed - it showed a straight on
6543A if there was no 2 present. It even showed a straight flush if the
6543A was in the same suit.

It has been corrected. Thanks a lot for pointing it out.

Signature

Johannes

 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2010 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.