Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Hi all, new user but was told this was a good poker forum!
Not sure if this is the right place to post, please move if it is not.
Anyway, I've finished writing a 7 card hand evaluator for poker, and I'm managing to get around 14-15 million hands evaluated per second (14,000 to 15,000 per millisecond). I've had a look around, and is anyone aware of any software that can beat this?
Tests where done on a P4 2.4ghz PC with 1GB Ram.
Thanks for your time!
Tom
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
When u say it evaluates hands, what exactly does it do? Solve for the equity value vs a random distribution, a specified distribution, tell you your hands rank, or does some sort of opponent/bidding modelling?
As far as whether I'm aware of faster software? Again it depends what it does. If you just have it look up equity values poker stove caps off at about 100,000 million games a second - but it "cheats" - because it looks them up in tables. If you specify part of the flop, then it slows to about 5-10 million games a second. And if you specify distribution vs. distribution analysis with multiple players it slows a to a crawl, it slows to a few thousand a second.
Because of that I've wrote a poker evaluator as well that is 10,000 faster than poker stove for distribution vs. distribution analysis and partial flop analysis, which i'm using as an engine to try write a bot that approximates Nash EQ play. It lets you specify distribtuions more precisely than pokerstove currently does - which honestly isn't much use to the average poker player. E.g. you can assign any decimal between [0,1] to the probability that when your opponent is dealt XY, he plays XY (or is on XY at that point in time). This allows you to capture the full range of possible distributions. The purpose of my program is to be interacted with by another program, not realy for some human to plug in by hand a bunch of values between [0,1] for each of the 1326 XY combinations.
Anyways, need to know what exactly your program does, if u want to know if something else does it faster. "Evaluation" is an ambiguous term
|
justkevin
enthusiast
Reged: 10/05/06
Posts: 203
|
|
I think he means ranks hands-- takes a combination of 7 cards and returns an integer. A better hand has a higher integer value than a worse hand.
A faster evaluator means a faster bot/analysis program etc.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
oh ok. In terms of merely assigning a integer value to a hand ranking i'm getting slightly over 60 million per second. I have a AMD Dual FX-60, 2 Gigs of ram. For purposes of conductnig the evaluation I assigned a static flop and hands to 10 players and fed that repeatedly to my "determine winner" function which ranks all 10 hands.
The determine winner function contains no memory of the previous hand fed to it, so its like receiving a new hand, but it isolates the processing time, from the time it takes to generate new hands, etc....
I could write a function that is about 1000x faster but the memory requirements would increase 10 fold. Right now it uses about 200 Mbs of memory.
Well, I would still say yours might be faster than any available commercial software, (I think mine are). If anyone has serious intentions on making commercial or proprietary software using these types of algorithms they can contact me. Right now i'm using mine to design bots.
|
DonkBluffer
veteran
Reged: 08/26/05
Posts: 1597
|
|
I'd be very interested in a program where you could put a player on a preflop range, choose a flop, and let the program calculate how often the player would have flopped every possible combination. (Top pair, a gutshot, top pair AND a gutshot should be different combinations, so the total adds up to 100%, if you know what i mean)
I don't know if such a thing exists, but you guys seem to know a lot about programming.
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
D&L:
I find 60 million per second extremely impressive. Because you are on dual core, we can cut that in half and say 30 million per second, which is still a darn sight faster than mine.
Just to clarrify:
The problem is, to take in 7 UNORDERED integers (0-52) representing a hand, and convert them into a ranking which can be compared with other hands ranking to quickly determine the winner. I can't cap 13.5million per second! Is this what your evaluator does?
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Update: Managed to get it to nearly 15 million per second.
|
bachfan
member
Reged: 11/22/05
Posts: 196
|
|
Quote:
Update: Managed to get it to nearly 15 million per second.
This is similar to my numbers on propokertools , although ranking a 7-card hand is not what my code spends most of its time doing these days.
I might try and whip something up faster for fun in the next few hours.
Cheers, bachfan
|
bachfan
member
Reged: 11/22/05
Posts: 196
|
|
Quote:
Quote:
Update: Managed to get it to nearly 15 million per second.
This is similar to my numbers on propokertools , although ranking a 7-card hand is not what my code spends most of its time doing these days.
I might try and whip something up faster for fun in the next few hours.
Cheers, bachfan
Oops, off by a factor of two. I'm closer to 30million on one core of a core duo 1.6ghz. This is JUST the compute ranking function.
- bachfan
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
So, to clear things up, your program can work out the rank of a hand given to it in any order, IE given random unordered set of 7 integers it can work out 30 million of them per second? Or as a pre condition must they be ordered?
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
actually dual core isn't coming into play yet...it simiply allows me to be surfing the web without degrading the speed of other programs. Its still 60 million...but i am on a faster machine overall i think.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Mine is 60million unordered. Keep in mind my function takes about 200MB of memory. I was trying to make the fastest algorithm my machine could support. The only faster algorithm I could think of would have taken multiple gigabytes of memory. Memory and speed are almost always tradeoffs.
Remember my dual core processor doesn't effect speed. Although I am on a faster comp i think.
Anyways, I'm not writing to say that mines faster. I'm really impressed that others are coming up with similar results. I've worked for a while on various altorithms, as part of trying to apply game theory to poker. If any of u are working on commercial software, tell me about it, and if its interesting and u sound serious, I may be willing to share my techniques.
P.s. I'm not a programmer. _D&L_ = Dirty and Litigious - i'm a lawyer, with a background in gametheory and economics. It's quite possible that if a professional programmer looked at my code they could find further optimizations. I programmed in VisualBasic 5.0, which might be slower than if it were converted to something like C++.
-_D&L_
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
So you are getting 60 million evaluations per second (that's 40 cpu cycles per hand) using VB?
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Yes Gull, thats what I said...
Look if memory was no object you could just do this:
Dim HandValue(52, 52, 52, 52, 52, 52, 52) as Integer
And write hand values for every combination of cards, for any order they are entered. It would take, i think, 2000 gigabytes of memory. I don't think there are computers that support that much memory. But the result would be that you would have hand evaluations that could be achieved in maybe 1 cpu cycle - that is the array would just be a pointer to a result.
Obviously, not feasible given memory constraints. I can't reveal my methods, though i sense disbelief, but hopefully this extreme example shows what is possible. Indeed, this extreme example shows that if memory were not an object we could have a function that solved billions or maybe trillions of games a second.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Yeah, i don't think anything exists for that right now. It would take me about 3-7 days to transform my program into something that could do that...as well as adding a decent user interface to edit distributions (right now I use access).
If someone with (proven) experience in publishing share-ware like programs wanted to co-publish a program like this with me, I could probably be talked into doing it. As I said elsewhere in this thread, I'm not a programmer by trade. I have difficulty getting my program to run on other systems, don't know how to create an installation package, and i would want to protect my databases (part of my algorithm) from being hacked and used for other un-liscensed purposes, etc.
|
Phil153
Notable Twit
Reged: 10/18/05
Posts: 4905
|
|
Quote:
Because of that I've wrote a poker evaluator as well that is 10,000 faster than poker stove for distribution vs. distribution analysis and partial flop analysis
I call BS. It may be faster than pokerstove in very specific situations, but for the majority of analyses I doubt there's much difference.
And anything that requires a 200MB database on disk is not commercially viable except as a web application, a market which propokertools has pretty much owned. Sorry.
Anyone can generate the lookup tables required for a blazing fast algorithm, so I don't know how you claim that PokerStove "cheats" by using them, when that's exactly what you use. You can only do about a billion bitwise (and, or, xor), comparison, or iteration operations a second, and without lookup tables you'll never get a reasonable speed using any algorithm.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Phil why are you hostile? Did I ever say the world needed a poker stove that was 10,000 times faster? In fact, you gloss over the fact I said the exact opposite, because you want to pick a fight.
I didn't design my program to be the next poker stove, i designed it for speed - to be used in high end bots that i work on, or game theory software, which is pretty much the same thing.
BTW i'm not a programmer, and haven't worked on these algorithms in over a year, so i mistook how much memory they used. I said 200mb, that was an earlier version, I double checked, they use about 15MBs. My original version was 8 gigs of hd space...so i've made lots improvements over time.
Anyways, do i fault poker stove for using "lookup tables" - of course not. I use them too. My point - which u missed, because u want a fight - was that if a result is not on the lookup table, the program slows considerably. From 100,000 million games a second, to 5,000. The difference being is that for my algorithm no result is "off table."
-Dirty & Litigious
|
Andrew Prock
enthusiast
Reged: 09/02/02
Posts: 346
Loc: oakland
|
|
5,000 per second? On what platform? The slowest it ever goes for me about 500,000 evals per second.
- Andrew
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
I've written an algorithm that uses NO lookup tables and can do 1.5 to 2 million evaluations per second.
My look up table version runs at 14-15 million per second. I'm sorry but without seeing your code I find it extremely difficult to believe that you can do 60 million per second with VB with a 15MB look up table. Would you be willing to expand on your methods?
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
oh ok. In terms of merely assigning a integer value to a hand ranking i'm getting slightly over 60 million per second. I have a AMD Dual FX-60, 2 Gigs of ram. For purposes of conductnig the evaluation I assigned a static flop and hands to 10 players and fed that repeatedly to my "determine winner" function which ranks all 10 hands.
The determine winner function contains no memory of the previous hand fed to it, so its like receiving a new hand, but it isolates the processing time, from the time it takes to generate new hands, etc....
Quote:
So, to clear things up, your program can work out the rank of a hand given to it in any order, IE given random unordered set of 7 integers it can work out 30 million of them per second? Or as a pre condition must they be ordered?
Quote:
Mine is 60million unordered. Keep in mind my function takes about 200MB of memory. I was trying to make the fastest algorithm my machine could support. The only faster algorithm I could think of would have taken multiple gigabytes of memory. Memory and speed are almost always tradeoffs.
Quote:
Oops, off by a factor of two. I'm closer to 30million on one core of a core duo 1.6ghz. This is JUST the compute ranking function.
There's a HUGE difference between assigning static cards for the flop and the players, and the general purpose solution. Are you both actually comparing your algorithms using the same metric?
IE: Your function is passed an unordered vector of 7 integers in the range {0..51} and returns the hand rank for the input combination.
From what I just read it sounds like one of you is generating your timings with essentially 5 of the 7 cards statically pre-assigned, and the other is generating the timings with no static cards and possibly some are using a 52 element bitstring rather than 7 unordered integers (bachfan?).
Quote:
I could write a function that is about 1000x faster but the memory requirements would increase 10 fold. Right now it uses about 200 Mbs of memory.
Quote:
So you are getting 60 million evaluations per second (that's 40 cpu cycles per hand) using VB?
Quote:
Yes Gull, thats what I said...
A 1000 fold increase of the existing 40 cycles per hand would be 0.04 cycles per hand??? 
Juk 
PS: I'm supposed to be having a rest from poker/coding atm (until new year at least), but this thread got me interested now...
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
Phil why are you hostile? Did I ever say the world needed a poker stove that was 10,000 times faster? In fact, you gloss over the fact I said the exact opposite, because you want to pick a fight.
Phil153 + Poker AI = Flames
Juk
|
Andrew Prock
enthusiast
Reged: 09/02/02
Posts: 346
Loc: oakland
|
|
If you really did get 60 million general evals per second on a normal machine (3 MHz or so), I'm truly impressed. But you have to keep in mind that when it come to doing poker calculations there is more to it than just plain evaluation speed. The guy with the Poker Bolide software demonstrated that recently by making a fast monte carlo evaluator, but not much else.
It's unclear what you're striving for here, or what you've achieved. There is no world record for evals/second, but even at 60 million per second, interesting poker calculations can be done much faster. PokerStove can do about 2 billion evaluations per second in the optimized Enumeration code. In the unoptimized code, it does about 10 million evals per second when enumerating. That may seem slow, but many of the calculations finish with the exact solution nearly instantly. The same can't be said for monte carlo evaluation. While PokerStove does have various lookup tables, those consist mostly of things like bit counting tables. Admittedly, the monte carlo code is not it's strong point, but there's a reason for that.
Under monte carlo, once the code is fast enough, it doesn't really matter too much if you make it twice as fast. You'll get close to the exact value quickly, but you'll never get the exact value. Said another way, doubling the number of evals won't double the convergence of the result. There are also issues of what sort of RNG you use, and equally important how you use it. Getting the RNG problem wrong can mean that your monte carlo simulations converge to the wrong result.
If you can calculate the exact value, you're usually much better off, assuming you can actually calculate it quickly enough. The nice thing is that many of the interesting questions the people ask about poker are computable using exact enumeration, and can be done in a reasonable amount of time. In a lot of ways that was the whole point of PokerStove. If you know what you are trying to calculate, you can do it significantly faster by taking the whole calculation into consideration instead of just relying on fast 7 card hand evaluators. Unfortunatly, not all things can be calculated exactly in a short period of time, and for those you often have to resort to monte carlo, precomputation, or other methods.
If you look at the work of eastbay and bachfan, you can see that they've done a great job in actually answering questions that people want to ask. As for your software, until you make it public in some fashion, it'll mean the world to you, but not much to the world. That's certainly not a bad thing. I have a lot of software which isn't public either. And it does mean quite a bit to me.
On the other hand, all this thread has piqued my curiosity. I'v never really tried to make a fast general purpose evaluator. I wonder how fast I could make it?
- Andrew
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Well right now I use two lookup tables; and some on-the-fly processing. With 2 gigs of memory, you could just have one big lookup table, and reduce processing time to just retreiving a value from an array. Is that 1000x faster, maybe not, but it would be exponentially faster. Can modern CPU's retreive more than one integer value per CPU cycle? How about from a multi-dimensional Array? Well at any rate, that is the limiting factor, if memory wasn't an issue.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Hey Andrew,
First, for anybody else reading Andrew is the author of poker stove. Second, Andrew you may remember me. I wrote you about a year or so ago, inquiring about access to poker stove source code, offering to make some "improvements," sign a confidentiality agreement, etc. You declined, as I probably would have too in your situation, but you can't really fault me for trying.
At any rate, i wrote this code to be used with bots and junk that I was working on. I didn't have your code as a starting point, so maybe i needed to re-invent the wheel - to some extent. Certainly poker stove is great at what it's designed to do, and my intent was never to write a program to displace yours.
One thing i noticed is that with poker stove is that if you set all the players to distributions, the program slows considerably. Its not a big deal for Poker_stove, because rarely are there 9 other players on the flop with you, but Pstove does slow from about 100,000 million games a sec to about 1 game every 4 seconds (9 players on broadway, final player on random all). The slow down isn't as severe with 3 or 4 players on the flop, but it still slows by a magnitude of about 200.
Again, not a big deal for pokerstove, because few Pokerstove users need a question like this answered. But when you set a bot to search for the right strategy, u don't want it hit bottlenecks like that. So thats why my program tries to process everything at a more uniform speed. Besides, even if poker stove does do a better job than my program in answering these questions, i still didn't have access to the source to use in other programs. So at any rate, thats why I wrote my own...
Dirty & Litigious
-Dirty & Litigious
|
bachfan
member
Reged: 11/22/05
Posts: 196
|
|
Quote:
From what I just read it sounds like one of you is generating your timings with essentially 5 of the 7 cards statically pre-assigned, and the other is generating the timings with no static cards and possibly some are using a 52 element bitstring rather than 7 unordered integers (bachfan?).
Here's what I did (and it looks like I was right with my FIRST quote of approx 15mil/sec, not 30. Oh well. 
I just spewed out 15 million random 7-card poker hands and computed the ranking for each. I looked up the cpu utilization for the ranking function and extrapolated the rate from that (i thought 30 mil for a minute but i forget to include a part which in all fairness really is part of the evaluation function - long story...)
I've got some more comments coming. 
- bachfan
|
bachfan
member
Reged: 11/22/05
Posts: 196
|
|
Quote:
Under monte carlo, once the code is fast enough, it doesn't really matter too much if you make it twice as fast. You'll get close to the exact value quickly, but you'll never get the exact value. Said another way, doubling the number of evals won't double the convergence of the result. There are also issues of what sort of RNG you use, and equally important how you use it. Getting the RNG problem wrong can mean that your monte carlo simulations converge to the wrong result.
I'd just like to echo that an elaborate a bit. While computing exact results is very satisfying (and has a certain "gee whiz" appeal), in my case generating a fast exhaustive algorithm was a lot easier than generating a fast monte-carlo algorithm. Fortunately, it doesn't matter a lot in practice, at least not for me, because in almost all cases it is pretty easy to converge on a useful result.
There's another point I'd like to raise here - perhaps some other programmer types have some thoughts.
In developing propokertools, one of the biggest challenges has been generating random hands for monte carlo simulations such that the results correctly approach the same results from an exhaustive simulation. Now, I am fairly confident my algorithms work, and I have automated tests to "prove" it, but there are some situations (most of them academic and not useful in practice) where the random hand generation gets really bogged down. I don't want to get into too many technical details as that isn't my point (get to it, bachfan!). My point is, there may be a tradeoff to be made in certain situations between "correctness" and speed. In fact, you can think of monte-carlo as just such a tradeoff. There may well be some psycho-fast algorithm out there that is almost-but-not-quite-entirely-correct that, for purposes of mass generation (ie, for creating a bot using neural networks or whatever), might be the right way to go.
Just a thought, (babbling now) bachfan
|
Andrew Prock
enthusiast
Reged: 09/02/02
Posts: 346
Loc: oakland
|
|
Pstove does slow from about 100,000 million games a sec to about 1 game every 4 seconds (9 players on broadway, final player on random all).
Interesting behaviour. I looked at the code, and it seems that my method for selecting monte carlo hands is fairly inefficient. For those who are interested, in the monte carlo code I draw cards from an infinite deck for all the hands and then discard scenarios where two hands hold duplicate cards. Of course, when all the hands are drawn from a narrow distribution, this happens far too often.
Like I said, it's not how fast you evaluate...

Thanks for the info. I'll try and get a fix for that by the end of the year.
- Andrew
|
bachfan
member
Reged: 11/22/05
Posts: 196
|
|
Quote:
I'v never really tried to make a fast general purpose evaluator. I wonder how fast I could make it?
I must admit, the same thing occurred to me in the shower this morning. I even started coding up a new approach, but then I realized it was almost isomorphic to my existing one. Ooops.
I did have a fun idea for a new way of evaluating omaha hands, which i coded up, but it turned out to be just about as fast as my existing code in most cases. Ooops again. I may come back to that at some point. - bachfan
|
bachfan
member
Reged: 11/22/05
Posts: 196
|
|
Quote:
Interesting behaviour. I looked at the code, and it seems that my method for selecting monte carlo hands is fairly inefficient. For those who are interested, in the monte carlo code I draw cards from an infinite deck for all the hands and then discard scenarios where two hands hold duplicate cards. Of course, when all the hands are drawn from a narrow distribution, this happens far too often.
This is exactly the problem I was referring to a few posts back (sounds like we are taking the same approach here). I found some tricks for making things a bit faster in various cases, but I have yet to find a fast algorithm that generates the hands with the correct probability in these kinds of cases. I described the problem to my brother (a math professor at a major university), and the best he could come up with was "it sounds hard." 
Cheers, bachfan
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Pretend we have two players that play only two hands:
AK or AA.
First player: AK - 16 combos, AA 6 combos, total 22 First Players chance of playing AK is 16/22 First Players chance of playing AA is 6/22
Now assign the second player:
If player 1 was assigned AK (p = 16/22), then Player 2: (Baysian updating) Plays AK with 9/12 probability Plays AA with 3/12 probability If player 1 was assigned AA (p = 6/22), then Player 2: (Baysian updating) Plays AK with 8/9 probability Plays AA with 1/9 probability Now what is Player 2's chance of playing AA (16/22)*(3/12) + (6/22)*(1/9) = 7/33
Now the problem: Player 1 and Player 2 had the same distributions. But 7/33 != 6/22. The problem is that assigning PLAYER 1 his distribution frist, gives player 2 an unequal chance of getting AA. The player most likely to be dealt pocket pairs, is the one where all the aces are in deck when he gets a card on his distribution. Notice that Player 2's chance of being distributed AA is less than Player 1, regardless of whether Player 1 was dealt AK or AA. (6/22 >> 3/12 or 1/9)
A correct algorithm, would at the very least, have to give both players (with equal distributions) an equal chance of getting AA. Thus, dealing everyone random hands, and discarding until everyone gets a hand on their distribution is an unbiased, but albeit extremely slow method of getting the right answer.
|
bachfan
member
Reged: 11/22/05
Posts: 196
|
|
Quote:
Pretend we have two players that play only two hands:
AK or AA.
First player: AK - 16 combos, AA 6 combos, total 22 First Players chance of playing AK is 16/22 First Players chance of playing AA is 6/22
Now assign the second player:
If player 1 was assigned AK (p = 16/22), then Player 2: (Baysian updating) Plays AK with 9/12 probability Plays AA with 3/12 probability If player 1 was assigned AA (p = 6/22), then Player 2: (Baysian updating) Plays AK with 8/9 probability Plays AA with 1/9 probability Now what is Player 2's chance of playing AA (16/22)*(3/12) + (6/22)*(1/9) = 7/33
Now the problem: Player 1 and Player 2 had the same distributions. But 7/33 != 6/22. The problem is that assigning PLAYER 1 his distribution frist, gives player 2 an unequal chance of getting AA. The player most likely to be dealt pocket pairs, is the one where all the aces are in deck when he gets a card on his distribution.
A correct algorithm, would at the very least, have to give both players (with equal distributions) an equal chance of getting AA. Thus, dealing everyone random hands, and discarding until everyone gets a hand on their distribution is an unbiased, but albeit extremely slow method of getting the right answer.
You are correct. If only you had written this earlier it would have saved me a few days of thought/work two years ago. 
I came up with a (relatively) fast way to generate random razz hands, but could not prove it was correct. So I in fact implemented the extremely boneheaded (but unambiguosly correct) algorithm you outlined above for comparison purposes. Took billions of hands to converge within 0.01% in some cases, but converge they did.
- bachfan
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
yeah, took me longer than 2 days to figure out why player 1 kept beating player 2 through 10, when i assigned them all the same distributions
|
Phil153
Notable Twit
Reged: 10/18/05
Posts: 4905
|
|
Quote:
Quote:
Phil why are you hostile? Did I ever say the world needed a poker stove that was 10,000 times faster? In fact, you gloss over the fact I said the exact opposite, because you want to pick a fight.
Phil153 + Idiots = Flames
Juk
FYP.
BTW, hand evaulation algorithms are not poker AI.
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Please could you all run this test on your hand evaluators and let me know the results:
int c1, c2, c3, c4, c5, c6, c7; int handSetCounters[8] = {0,0,0,0,0,0,0,0};
for(c1 = 0; c1 < 52; c1++) { for(c2 = c1 + 1; c2 < 52; c2++) { for(c3 = c2 + 1; c3 < 52; c3++) { for(c4 = c3 + 1; c4 < 52; c4++) { for(c5 = c4 + 1; c5 < 52; c5++) { for(c6 = c5 + 1; c6 < 52; c6++) { for(c7 = c6 + 1; c7 < 52; c7++) {
CALCULATE RANK CALCULATE SET ID (HIGH CARD TO STRAIGHT FLUSH) INCREMENT HANDSETCOUNTERS[SET ID]
}}}}}}}
PRINT HANDSETCOUNTERS
If you could time the speed of your code in that scenario it would be most interesting! Also with a copy of your results (number of flushes etc incase your code is not designed to be 100% accurate).
Thanks for anyone that participates!
Tom
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
I've done the tests. It took some rewriting of my code to make your tests work, and i haven't yet checked them for accuracy.
Without the checksums i was getting 117,354,877 games per second. It took 1.14 seconds to process all 133,784,560 games in the loops.
With the check sums, which i didn't spend time optimizing it took 1.71 seconds, for a speed of 78,236,584 games per second.
The result of the check sums are as follows: In order from lowest hand to highest (hi-card, 1 pair, 2 pair, 3 kind, straight, flush, full house, four kind, to straight flush).
23294460 58627800 31433400 6461620 6180020 4047644 3473184 224848 41584
Let me know if you see errors in the checksum. I actually had to look at code that is no longer in my program to interpret my lookup tables, cause all my program knows now is that higher integer > better cards. I wrote a compression algorithm to make all the rankings fit on one integer, and had to decompress the results for your test, so its possible i could have made a mistake. Its also why the checksums almost double the time it takes for my program to resolve values.
P.s. i know 117 million is faster than the 60 million i said earlier. My program is designed to do a lot more than just resolve hand values. I took out some of the overhead, for this test.
|
Phil153
Notable Twit
Reged: 10/18/05
Posts: 4905
|
|
With these speeds you obviously have all your precalculated results in a single (very large) table, and nothing is calculated. The time for your algorithm is basically the time to increment the 7 counters(c1..c7), do one bitwise operation, look up the value in a a humongous table, and add the results.
This is incredibly simple to code if you don't mind a 200MB file on your hard file. It's actually easier to code than a 15 million hand/second algorithm, and far easier to code than an advanced and complex algorithm like PokerStove. If you used the same massive table in PokerStove, it'd probably run 5x faster than your code.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Phil - do i have lookup tables yes. The same way you have a calculator to look up multiplication, while you perform calculus. But you'd be kidding yourself if you think you could put all the results in one look up table. As I've already said such a table would be several Gigs in size - and extremely slow if you used a hardrive. Mine is 15 MB.
If you think i'm just pushing 7 counters, i'd be resolving about 6 to 9 billion games a second. Why is it you assume I'm only capable of coding a simple algorithms. My programming skills may be sub-par, but i've spent years studying game theory - so why is it u assume my algorithms "must" be simple? More specifically, why do you respond to everything with "BS" "idiot" an the like, is the fact the someone else can code something like this a "threat" to you?
|
A.Nironen
member
Reged: 10/29/06
Posts: 118
|
|
Quote:
If you could time the speed of your code in that scenario it would be most interesting! Also with a copy of your results (number of flushes etc incase your code is not designed to be 100% accurate).
Gullanian,
Here is my demo (210 Kb).
http://www.pokerbolide.com/files/handeval7cards.exe
Andrzej Nironen
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
D&L, congratulations, your numbers are correct and I am very impressed.
PokerBolide.com, wow. I am amazed. I thought I had the optimum solution, but you have out done me and months of work, im truly impressed.
Congratulations both of you, I don't know how you do it but they seem to work superbly.
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
PokerBolide.com, wow, just running again and it's superb. My solution used look up tables, some faily large in size, and yours does not seem to at all. Superb.
|
A.Nironen
member
Reged: 10/29/06
Posts: 118
|
|
Quote:
PokerBolide.com, wow, just running again and it's superb. My solution used look up tables, some faily large in size, and yours does not seem to at all. Superb.
My algorithm does use lookup tables, but they are not big (no more than a couple of megabytes) and they are calculated in a moment at program start.
Andrzej Nironen
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Dear Andrzej,
Yeah I had kind of guessed that, a no LUT solution would be impossible.
Anyway, I've thought of a new solution that could potentially be incredibly fast without the need of (large) LUT's, keep an eye on this post and I will let you know how it gets on.
Tom
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Pretend we have two players that play only two hands:
AK or AA.
First player: AK - 16 combos, AA 6 combos, total 22 First Players chance of playing AK is 16/22 First Players chance of playing AA is 6/22
Now assign the second player:
If player 1 was assigned AK (p = 16/22), then Player 2: (Baysian updating) Plays AK with 9/12 probability Plays AA with 3/12 probability If player 1 was assigned AA (p = 6/22), then Player 2: (Baysian updating) Plays AK with 8/9 probability Plays AA with 1/9 probability Now what is Player 2's chance of playing AA (16/22)*(3/12) + (6/22)*(1/9) = 7/33
Now the problem: Player 1 and Player 2 had the same distributions. But 7/33 != 6/22. The problem is that assigning PLAYER 1 his distribution frist, gives player 2 an unequal chance of getting AA. The player most likely to be dealt pocket pairs, is the one where all the aces are in deck when he gets a card on his distribution. Notice that Player 2's chance of being distributed AA is less than Player 1, regardless of whether Player 1 was dealt AK or AA. (6/22 >> 3/12 or 1/9)
A correct algorithm, would at the very least, have to give both players (with equal distributions) an equal chance of getting AA. Thus, dealing everyone random hands, and discarding until everyone gets a hand on their distribution is an unbiased, but albeit extremely slow method of getting the right answer.
Your Baysian process ignores the times where one/both of the players have an unplayable hand.
Of the times both players have playable cards:
Player 1 will have AA, and Player B will have AA with probability 1/41. Player 1 will have AA, and Player B will have AK with probability 8/41. Player 1 will have AK, and Player B will have AA with probability 8/41. Player 1 will have AK, and Player B will have AK with probability 24/41.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
how fast is pokerboldies?
NM - 35Mil a second.
Anyways I see you are all really smart people (exception Phil). If someone could figure out a way to assign a unique index value between 1 and 133784560 for 7 unordered cards, you could dump everything into a 133 MB lookup table.
I wouldn't mind doing that, since my code isn't really for commercial use, and I think the throughput would go up from my 117 million a second, to maybe a few billion.
But, i haven't figured out a way to assign a unique ID to 7 unordered, (up to 52 - noduplicating) numbers. At least not a method that doesn't require putting them in order first, which might negate the speed advantages.
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
how fast is pokerboldies?
NM - 35Mil a second.
Anyways I see you are all really smart people (exception Phil). If someone could figure out a way to assign a unique index value between 1 and 133784560 for 7 unordered cards, you could dump everything into a 133 MB lookup table.
I wouldn't mind doing that, since my code isn't really for commercial use, and I think the throughput would go up from my 117 million a second, to maybe a few billion.
But, i haven't figured out a way to assign a unique ID to 7 unordered, (up to 52 - noduplicating) numbers. At least not a method that doesn't require putting them in order first, which might negate the speed advantages.
I tried something similar to this as a method to speed existing hand evaluators: Using the idea of Combinadics it's possible to assign a unique lexicographical index for each ordered combination.
If the hand is represented by a 52 bit number with 7 bits set true to indicate which cards you have in your hand (ie: like pokersource's "packed" format), then you can calculate the lexicographical index as though the cards are ordered (ie: the bitstring representation orders them implicitly) by traversing the bitstring from the least(/most) significant bit.
The naive approach would require testing and summation of each of the 52 bits individually, but using a lookup table it is possible to create partial sums of C() and do whole blocks of 16 bits at a time and thus just do 4 summations rather than all 52 (actually it's possible to do just 3 if you use 18 bits, but this requires 4x more memory to hold the lookups).
The idea is then that we can use the lexicographic index we created to then index into another lookup of 133784560 precomputed 16bit hand evaluations (this means you can use any old [unoptimized] hand evaluator and just concentrate on optimizing the lookup accessing code...).
I tested the idea out very quickly just using the pokersource hand evaluator to create the lookup, and managed to get aprroximately a 2x speedup (21 million hands/sec -> 42 million hands/sec on a 3GHz P4), at the expense of using about 500MB of lookup tables.
I haven't yet tried optimizing the generated assembler (which looked fairly suboptimal) nor have I yet tried using the x86's special BSF/BSR instructions to scan the bitstring (using BSF/BSR we can do just 7 summations and without the need for 2d/3d index dereferencing used by the partial sum method outlined above).
Code:
// FastHandEval.cpp : Defines the entry point for the console application. //
#include "stdafx.h" #include "random.h" #include <Crtdbg.h> #include <time.h>
#include <iostream>
#ifdef __cplusplus extern "C" { #include "poker_defs.h" #include "deck_std.h" #include "inlines/eval.h" } #else #include "poker_defs.h" #include "inlines/eval.h" #endif
using namespace std;
// How mnay iterations do we want to run the etsts on. #define ITERS 1000000000
// How many tests shall we generate. #define NUM_TESTS 10000
// Precomputed combination lookups (huge factorials like 52! are a pain to generate here...). int C[52][8]={ {0,0,0,0,0,0,0,0}, {0,1,0,0,0,0,0,0}, {0,2,1,0,0,0,0,0}, {0,3,3,1,0,0,0,0}, {0,4,6,4,1,0,0,0}, {0,5,10,10,5,1,0,0}, {0,6,15,20,15,6,1,0}, {0,7,21,35,35,21,7,1}, {0,8,28,56,70,56,28,8}, {0,9,36,84,126,126,84,36}, {0,10,45,120,210,252,210,120}, {0,11,55,165,330,462,462,330}, {0,12,66,220,495,792,924,792}, {0,13,78,286,715,1287,1716,1716}, {0,14,91,364,1001,2002,3003,3432}, {0,15,105,455,1365,3003,5005,6435}, {0,16,120,560,1820,4368,8008,11440}, {0,17,136,680,2380,6188,12376,19448}, {0,18,153,816,3060,8568,18564,31824}, {0,19,171,969,3876,11628,27132,50388}, {0,20,190,1140,4845,15504,38760,77520}, {0,21,210,1330,5985,20349,54264,116280}, {0,22,231,1540,7315,26334,74613,170544}, {0,23,253,1771,8855,33649,100947,245157}, {0,24,276,2024,10626,42504,134596,346104}, {0,25,300,2300,12650,53130,177100,480700}, {0,26,325,2600,14950,65780,230230,657800}, {0,27,351,2925,17550,80730,296010,888030}, {0,28,378,3276,20475,98280,376740,1184040}, {0,29,406,3654,23751,118755,475020,1560780}, {0,30,435,4060,27405,142506,593775,2035800}, {0,31,465,4495,31465,169911,736281,2629575}, {0,32,496,4960,35960,201376,906192,3365856}, {0,33,528,5456,40920,237336,1107568,4272048}, {0,34,561,5984,46376,278256,1344904,5379616}, {0,35,595,6545,52360,324632,1623160,6724520}, {0,36,630,7140,58905,376992,1947792,8347680}, {0,37,666,7770,66045,435897,2324784,10295472}, {0,38,703,8436,73815,501942,2760681,12620256}, {0,39,741,9139,82251,575757,3262623,15380937}, {0,40,780,9880,91390,658008,3838380,18643560}, {0,41,820,10660,101270,749398,4496388,22481940}, {0,42,861,11480,111930,850668,5245786,26978328}, {0,43,903,12341,123410,962598,6096454,32224114}, {0,44,946,13244,135751,1086008,7059052,38320568}, {0,45,990,14190,148995,1221759,8145060,45379620}, {0,46,1035,15180,163185,1370754,9366819,53524680}, {0,47,1081,16215,178365,1533939,10737573,62891499}, {0,48,1128,17296,194580,1712304,12271512,73629072}, {0,49,1176,18424,211876,1906884,13983816,85900584}, {0,50,1225,19600,230300,2118760,15890700,99884400}, {0,51,1275,20825,249900,2349060,18009460,115775100} };
// Used to store the precomuted 16bit partial sums of C(a,b) along with the current set-bit count. typedef struct LookupType { unsigned int Index16; unsigned char Count16; } LookupType;
// The main lookup for the combinadic partial sums. LookupType LookupTable[4][0x10000][8];
// Used to pregenerate the tests. unsigned long long TestL[NUM_TESTS]; unsigned int Tests[NUM_TESTS][2];
// The eval lookup indexed using the lexicographical index. unsigned int Evals[133784560];
// -------------------------------------------------------------------------------------
// Had to put this here to avoid problems with nested macros in the pokersource lib... void GenLookup(CardMask &PossibleExtraCards) {
unsigned int InputA,InputB; int Index; int J;
// This holds all of the cards we have used so far. CardMask AllUsedCards; CardMask_RESET(AllUsedCards);
uint64 T=(uint64)PossibleExtraCards.cards.spades |((uint64)PossibleExtraCards.cards.clubs<<13) |((uint64)PossibleExtraCards.cards.diamonds<<26) |((uint64)PossibleExtraCards.cards.hearts<<39);
InputA=T; InputB=(T>>32)&(0x100000-1);
Index=LookupTable[0][InputA&0xffff][0].Index16; J=LookupTable[0][InputA&0xffff][0].Count16; Index+=LookupTable[1][InputA>>16][J].Index16; J=LookupTable[1][InputA>>16][J].Count16; Index+=LookupTable[2][InputB&0xffff][J].Index16; J=LookupTable[2][InputB&0xffff][J].Count16; Index+=LookupTable[3][InputB>>16][J].Index16;
// Use the pokersource lib to create the evaluation value for the lookup. Evals[Index]=Hand_EVAL_N(PossibleExtraCards,7); }
// -------------------------------------------------------------------------------------
void InitLookups(void) {
// This hold the emumerated turn and river card combos. CardMask PossibleExtraCards;
// Clear the lookups. for (int InputX=0;InputX<4;InputX++) { for (int InputY=0;InputY<0x10000;InputY++) { for (int Count=0;Count<8;Count++) { LookupTable[InputX][InputY][Count].Index16=0; LookupTable[InputX][InputY][Count].Count16=0; } } }
// Create the lookups. for (int InputX=0;InputX<4;InputX++) { for (int InputY=0;InputY<0x10000;InputY++) { int CurrentInput=InputY; for (int StartCount=0;StartCount<7;StartCount++) { int CurrentCount=StartCount; for (int I=0,J=1;I<16 && CurrentCount<7;I++) { if ((CurrentInput>>I)&1LL) { LookupTable[InputX][InputY][StartCount].Index16+=C[(InputX*16)+I][(CurrentCount++)+1]; } } LookupTable[InputX][InputY][StartCount].Count16=CurrentCount; } } }
// Clear to be sure (no need really)... for (int I=0;I<133784560;I++) { Evals[I]=0; }
// Make the ranking lookups. // ### NOTE: This will mess up if Hand_EVAL_N is nested in it... DECK_ENUMERATE_N_CARDS(StdDeck,PossibleExtraCards,7, { GenLookup(PossibleExtraCards); }); // End Enumerate all combos.
}
// -------------------------------------------------------------------------------------
void InitTests(void) {
// lets make the tests. for (int I=0;I<NUM_TESTS;I++) { TestL[I]=0; for (int T=0;T<7;T++) { int R; do { R=RandInt(51); } while ((TestL[I]>>R)&1LL); TestL[I]|=(1LL<<R); } Tests[I][0]=TestL[I]&0xffffffff; Tests[I][1]=(TestL[I]>>32)&0xffffffff; }
}
// -------------------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[]) { int J;
unsigned int InputA,InputB; int Index;
unsigned int MyEval;
// Make the looups and the test data. cout << "Creating Lookups & Tests... "; cout.flush(); InitLookups(); InitTests(); cout << "Done." << endl;
// Store the start time. clock_t StartTime=clock();
for (int K=0;K<ITERS;K++) {
// Get the lexicographical index. InputA=Tests[K%NUM_TESTS][0]; InputB=Tests[K%NUM_TESTS][1];
Index=LookupTable[0][InputA&0xffff][0].Index16; J=LookupTable[0][InputA&0xffff][0].Count16; Index+=LookupTable[1][InputA>>16][J].Index16; J=LookupTable[1][InputA>>16][J].Count16; Index+=LookupTable[2][InputB&0xffff][J].Index16; J=LookupTable[2][InputB&0xffff][J].Count16; Index+=LookupTable[3][InputB>>16][J].Index16;
// Use the evaluation index by the lexicographical index. MyEval=Evals[Index];
/********* UNOPTIMIZED COMPARISON (HACKY CRAP...) ********** CardMask TestCards; TestCards.cards.spades=TestL[K%NUM_TESTS]; TestCards.cards.clubs=TestL[K%NUM_TESTS]>>13; TestCards.cards.diamonds=TestL[K%NUM_TESTS]>>26; TestCards.cards.hearts=TestL[K%NUM_TESTS]>>39;
// Use the original pokersource eval function fior camparison. MyEval=Hand_EVAL_N(TestCards,7); ***********************************************************/
} // Print this heare to force the optimizing compiler to not ignore the above loop... cout << MyEval << endl;
// Print the timings. cout << (double)(clock()-StartTime)/(double)CLOCKS_PER_SEC << " seocnds" << endl; cout << (int)((double)ITERS/((double)(clock()-StartTime)/(double)CLOCKS_PER_SEC)) << " hands per second" << endl;
return 0; }
NOTE: To use the above code you need both the pokersource hand evaluation library (or you can use ANY hand eval library so long as you set the Evals[133784560] correctly) and a function "RandInt(X)" which generates a random integer in the range {0..X}.
Juk 
PS: Sorry the code is very hacky, but I just wrote this to test it out quickly today and didn't spend much time on it. Hopefully it should help explain the method I outlined above.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Great post Juk. I gotta book mark this one, so i can come back and study it. I don't know C++ (or combinadics theory) so its like learnign two things at once .
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Just a note D&L, you don't need to return a number between 1 and 130 million for the strength of a hand, you can collapse the hands into unique equivalence classes, which there turn out to be about 7,500 unqiue hands.
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Also, because my evaluator has been beaten hands down, I will publish my technique very soon. I'll post a link in this thread when it is done.
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Jukofyork has basically done what I have done in my technique, looking at the lexographical index of all precomputing hand ranks. The ordering part of my code is the best, classical sorting algorithms are too slow for this job.
|
A.Nironen
member
Reged: 10/29/06
Posts: 118
|
|
Quote:
If someone could figure out a way to assign a unique index value between 1 and 133784560 for 7 unordered cards, you could dump everything into a 133 MB lookup table.
Single byte for hand's strength is not enough, so the table must be 266 MB at least.
Andrzej Nironen
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Quote:
If someone could figure out a way to assign a unique index value between 1 and 133784560 for 7 unordered cards, you could dump everything into a 133 MB lookup table.
Single byte for hand's strength is not enough, so the table must be 266 MB at least.
Andrzej Nironen
Actually it can be done in under 133 MB...
I've got an array of approx 30 million integers.
One way of accessing a hand ranking is:
Rank := Eval[Eval[Eval[Eval[Eval[Eval[Eval[c1]+c2]+c3]+c4]+c5]+c6]+c7];
The general idea is there are:
1 card hands. 52x52 2 card hands. 1326x52 3 card hands. 22100x52 4 card hands. 84448x52 5 card hands. 152607x52 6 card hands. 352443x52
For each N card hand, there are 52 vectors to the appropriate N+1 card hand.
For 6 card hands, instead of vectors to the 7 card hand, the array holds the Hand Ranking of the 7 card hand.
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Please could you all run this test on your hand evaluators and let me know the results:
int c1, c2, c3, c4, c5, c6, c7; int handSetCounters[8] = {0,0,0,0,0,0,0,0};
for(c1 = 0; c1 < 52; c1++) { for(c2 = c1 + 1; c2 < 52; c2++) { for(c3 = c2 + 1; c3 < 52; c3++) { for(c4 = c3 + 1; c4 < 52; c4++) { for(c5 = c4 + 1; c5 < 52; c5++) { for(c6 = c5 + 1; c6 < 52; c6++) { for(c7 = c6 + 1; c7 < 52; c7++) {
CALCULATE RANK CALCULATE SET ID (HIGH CARD TO STRAIGHT FLUSH) INCREMENT HANDSETCOUNTERS[SET ID]
}}}}}}}
PRINT HANDSETCOUNTERS
If you could time the speed of your code in that scenario it would be most interesting! Also with a copy of your results (number of flushes etc incase your code is not designed to be 100% accurate).
Thanks for anyone that participates!
Tom
Code:
133784560 hands 2.640 secs 50675964 hands/sec hc 23294460 pr 58627800 tp 31433400 th 6461620 st 6180020 fl 4047644 fh 3473184 fr 224848 sf 41584
using
procedure TForm1.Button2Click(Sender: TObject); var Totals : array[0..9] of integer; c1, c2, c3, c4, c5, c6, c7 : integer; Count : integer; Start, Stop : TDateTime; secs : double; Score : integer; begin Start := Now; Fillchar(Totals,sizeof(Totals),0); Count := 0; for c1 := 0 to 45 do for c2 := c1+1 to 46 do for c3 := c2+1 to 47 do for c4 := c3+1 to 48 do for c5 := c4+1 to 49 do for c6 := c5+1 to 50 do for c7 := c6+1 to 51 do begin inc(Totals[Table[Table[Table[Table[Table[Table[Table[c1+52]+c2]+c3]+c4]+c5]+c6]+c7] shr 20]); inc(Count); end; Stop := Now; secs := (Stop-Start)*86400.0; memo1.lines.add(format('%d hands',[Count])); memo1.lines.add(format('%5.3f secs',[secs])); memo1.lines.add(format('%d hands/sec',[trunc(Count/secs)])); memo1.lines.add(format('hc %d',[Totals[1]])); memo1.lines.add(format('pr %d',[Totals[2]])); memo1.lines.add(format('tp %d',[Totals[3]])); memo1.lines.add(format('th %d',[Totals[4]])); memo1.lines.add(format('st %d',[Totals[5]])); memo1.lines.add(format('fl %d',[Totals[6]])); memo1.lines.add(format('fh %d',[Totals[7]])); memo1.lines.add(format('fr %d',[Totals[8]])); memo1.lines.add(format('sf %d',[Totals[9]])); end;
and
133784560 hands 1.188 secs 112613302 hands/sec hc 23294460 pr 58627800 tp 31433400 th 6461620 st 6180020 fl 4047644 fh 3473184 fr 224848 sf 41584
using
procedure TForm1.Button1Click(Sender: TObject); var Totals : array[0..9] of integer; c1, c2, c3, c4, c5, c6, c7 : integer; u1, u2, u3, u4, u5, u6, u7 : integer; Count : integer; Start, Stop : TDateTime; secs : double; Score : integer; begin Start := Now; Fillchar(Totals,sizeof(Totals),0); Count := 0; for c1 := 0 to 45 do begin U1 := Table[52+c1]; for c2 := c1+1 to 46 do begin U2 := Table[U1+c2]; for c3 := c2+1 to 47 do begin U3 := Table[U2+c3]; for c4 := c3+1 to 48 do begin U4 := Table[U3+c4]; for c5 := c4+1 to 49 do begin U5 := Table[U4+c5]; for c6 := c5+1 to 50 do begin U6 := Table[U5+c6]; for c7 := c6+1 to 51 do begin inc(Totals[Table[U6+c7] shr 20]); inc(Count); end; end; end; end; end; end; end; Stop := Now; secs := (Stop-Start)*86400.0; memo1.lines.add(format('%d hands',[Count])); memo1.lines.add(format('%5.3f secs',[secs])); memo1.lines.add(format('%d hands/sec',[trunc(Count/secs)])); memo1.lines.add(format('hc %d',[Totals[1]])); memo1.lines.add(format('pr %d',[Totals[2]])); memo1.lines.add(format('tp %d',[Totals[3]])); memo1.lines.add(format('th %d',[Totals[4]])); memo1.lines.add(format('st %d',[Totals[5]])); memo1.lines.add(format('fl %d',[Totals[6]])); memo1.lines.add(format('fh %d',[Totals[7]])); memo1.lines.add(format('fr %d',[Totals[8]])); memo1.lines.add(format('sf %d',[Totals[9]])); end;
This was on an AMD 3000+ with 1 gig memory running Win2k
This computer produced
133784560 hands 5.156 secs 25947355 hands/sec hc 23294460 pr 58627800 tp 31433400 th 6461620 st 6180020 fl 4047644 fh 3473184 fr 224848 sf 41584
using Andrzej Nironen's "Hand Evaluator Speed Demo"
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
Quote:
Please could you all run this test on your hand evaluators and let me know the results:
int c1, c2, c3, c4, c5, c6, c7; int handSetCounters[8] = {0,0,0,0,0,0,0,0};
for(c1 = 0; c1 < 52; c1++) { for(c2 = c1 + 1; c2 < 52; c2++) { for(c3 = c2 + 1; c3 < 52; c3++) { for(c4 = c3 + 1; c4 < 52; c4++) { for(c5 = c4 + 1; c5 < 52; c5++) { for(c6 = c5 + 1; c6 < 52; c6++) { for(c7 = c6 + 1; c7 < 52; c7++) {
CALCULATE RANK CALCULATE SET ID (HIGH CARD TO STRAIGHT FLUSH) INCREMENT HANDSETCOUNTERS[SET ID]
}}}}}}}
PRINT HANDSETCOUNTERS
If you could time the speed of your code in that scenario it would be most interesting! Also with a copy of your results (number of flushes etc incase your code is not designed to be 100% accurate).
Thanks for anyone that participates!
Tom
Code:
133784560 hands 2.640 secs 50675964 hands/sec hc 23294460 pr 58627800 tp 31433400 th 6461620 st 6180020 fl 4047644 fh 3473184 fr 224848 sf 41584
using
procedure TForm1.Button2Click(Sender: TObject); var Totals : array[0..9] of integer; c1, c2, c3, c4, c5, c6, c7 : integer; Count : integer; Start, Stop : TDateTime; secs : double; Score : integer; begin Start := Now; Fillchar(Totals,sizeof(Totals),0); Count := 0; for c1 := 0 to 45 do for c2 := c1+1 to 46 do for c3 := c2+1 to 47 do for c4 := c3+1 to 48 do for c5 := c4+1 to 49 do for c6 := c5+1 to 50 do for c7 := c6+1 to 51 do begin inc(Totals[Table[Table[Table[Table[Table[Table[Table[c1+52]+c2]+c3]+c4]+c5]+c6]+c7] shr 20]); inc(Count); end; Stop := Now; secs := (Stop-Start)*86400.0; memo1.lines.add(format('%d hands',[Count])); memo1.lines.add(format('%5.3f secs',[secs])); memo1.lines.add(format('%d hands/sec',[trunc(Count/secs)])); memo1.lines.add(format('hc %d',[Totals[1]])); memo1.lines.add(format('pr %d',[Totals[2]])); memo1.lines.add(format('tp %d',[Totals[3]])); memo1.lines.add(format('th %d',[Totals[4]])); memo1.lines.add(format('st %d',[Totals[5]])); memo1.lines.add(format('fl %d',[Totals[6]])); memo1.lines.add(format('fh %d',[Totals[7]])); memo1.lines.add(format('fr %d',[Totals[8]])); memo1.lines.add(format('sf %d',[Totals[9]])); end;
and
133784560 hands 1.188 secs 112613302 hands/sec hc 23294460 pr 58627800 tp 31433400 th 6461620 st 6180020 fl 4047644 fh 3473184 fr 224848 sf 41584
using
procedure TForm1.Button1Click(Sender: TObject); var Totals : array[0..9] of integer; c1, c2, c3, c4, c5, c6, c7 : integer; u1, u2, u3, u4, u5, u6, u7 : integer; Count : integer; Start, Stop : TDateTime; secs : double; Score : integer; begin Start := Now; Fillchar(Totals,sizeof(Totals),0); Count := 0; for c1 := 0 to 45 do begin U1 := Table[52+c1]; for c2 := c1+1 to 46 do begin U2 := Table[U1+c2]; for c3 := c2+1 to 47 do begin U3 := Table[U2+c3]; for c4 := c3+1 to 48 do begin U4 := Table[U3+c4]; for c5 := c4+1 to 49 do begin U5 := Table[U4+c5]; for c6 := c5+1 to 50 do begin U6 := Table[U5+c6]; for c7 := c6+1 to 51 do begin inc(Totals[Table[U6+c7] shr 20]); inc(Count); end; end; end; end; end; end; end; Stop := Now; secs := (Stop-Start)*86400.0; memo1.lines.add(format('%d hands',[Count])); memo1.lines.add(format('%5.3f secs',[secs])); memo1.lines.add(format('%d hands/sec',[trunc(Count/secs)])); memo1.lines.add(format('hc %d',[Totals[1]])); memo1.lines.add(format('pr %d',[Totals[2]])); memo1.lines.add(format('tp %d',[Totals[3]])); memo1.lines.add(format('th %d',[Totals[4]])); memo1.lines.add(format('st %d',[Totals[5]])); memo1.lines.add(format('fl %d',[Totals[6]])); memo1.lines.add(format('fh %d',[Totals[7]])); memo1.lines.add(format('fr %d',[Totals[8]])); memo1.lines.add(format('sf %d',[Totals[9]])); end;
This was on an AMD 3000+ with 1 gig memory running Win2k
This computer produced
133784560 hands 5.156 secs 25947355 hands/sec hc 23294460 pr 58627800 tp 31433400 th 6461620 st 6180020 fl 4047644 fh 3473184 fr 224848 sf 41584
using Andrzej Nironen's "Hand Evaluator Speed Demo"
Very impressive! (I think you have the results mixed up though - 112.5mil when doing extra work of incrementing the hand type counts, 50mil when not... )
Have you considered posting this method on the pokersource yahoo group?
Juk 
EDIT: Sorry, I just noticed you print the hand type counts in both procedures... BTW: How fast does it run without doing this (ie: just assigning an integer the hand evaluation rather than incrementing totals[]?)
Edited by jukofyork (12/27/06 01:58 PM)
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Quote:
Code:
133784560 hands 1.188 secs 112613302 hands/sec hc 23294460 pr 58627800 tp 31433400 th 6461620 st 6180020 fl 4047644 fh 3473184 fr 224848 sf 41584
This was on an AMD 3000+ with 1 gig memory running Win2k
This computer produced
133784560 hands 5.156 secs 25947355 hands/sec hc 23294460 pr 58627800 tp 31433400 th 6461620 st 6180020 fl 4047644 fh 3473184 fr 224848 sf 41584
using Andrzej Nironen's "Hand Evaluator Speed Demo"
Very impressive! (I think you have the results mixed up though - 112.5mil when doing extra work of incrementing the hand type counts, 50mil when not... )
Have you considered posting this method on the pokersource yahoo group?
Juk 
EDIT: Sorry, I just noticed you print the hand type counts in both procedures... BTW: How fast does it run without doing this (ie: just assigning an integer the hand evaluation rather than incrementing totals[]?)
0.953 secs 140382574 hands/sec
However I'm doubting the granularity of the timing process used.
I'll switch to the RDTSC method and repost relative values.
Using RDTSC as a timer
133784560 hands 2.640 secs 50675964 hands/sec
becomes
133784560 hands 5752663748 cycles 42.999 cycles/hand
133784560 hands 1.188 secs 112613302 hands/sec
becomes
133784560 hands 2645152642 cycles 19.772 cycles/hand
and
133784560 hands 0.953 secs 140382574 hands/sec
becomes
133784560 hands 2077269818 cycles 15.527 cycles/hand
Edited by mykey1961 (12/27/06 03:39 PM)
|
A.Nironen
member
Reged: 10/29/06
Posts: 118
|
|
I've made some revision of my code. Enumeration wasn't optimal. Now it runs 30% faster with the same hand evaluation function. http://www.pokerbolide.com/files/handeval7cards.exe
Andrzej Nironen
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
how big are the LU tables combined under this method?
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Follow up. Trying to figure out how u map from table 5 to 6, or table 6 to a final hand value without large memory requirements. There about 2.5 Million combinations 5 cards, thust your table would seem to require 2.5 million combinations, yielding a table 6 that is lil over 100 MB.
The one way i thought around this is that your tables may already be condensed using equivalence classes, so the numbers could be smaller. That is, maybe if I wrote the big tables, and backwards eliminated duplicates, it would condense.
Did you do something like that, or totally different?
P.s. your tables lookups for c1 through c3 would probably be a pretty small part of your table(s).
Why not just write them as an array (52,52,52) and put duplicate values in the array to that (1,2,3) = (3,2,1). Looking up a single value from a multi dimensional array is usually much faster than looking up values from 3 different arrays, and performing addition between each lookup.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Made some adjustments to my code, am currently getting 279 Million games per second. Although I still think your algorithm may have greater potential (possibly if I can combine it with mine).
Still not ready to share my algorithm...but i'll provide the following sketch of it...i ommitted some parts, changed descriptive names, etc...so that it can't just be copied (at least not easily).
As you can see its speed is primarily due to being able to solve for many hands in one lookup, skipping to more complex analysis as needed. Whether more complex analysis is needed is also determined by a lookup table. For c1 = 1 To 46 nFTR(1) = CN(c1) For c2 = c1 + 1 To 47 nFTR(2) = CN(c2) For c3 = c2 + 1 To 48 nFTR(3) = CN(c3) For c4 = c3 + 1 To 49 nFTR(4) = CN(c4) For c5 = c4 + 1 To 50 nFTR(5) = CN(c5) rec_number = vRec_Num(non-lexographic indexing algorithm...nftr 1 through 5 as input) With vType(nftr(1 through 5) as input) If .Type = 0 Then
For c6 = c5 + 1 To 51 For c7 = c6 + 1 To 52 rank = DW_Array(rec_number + sub_index(c6,c7)) ' Select Case rank ' Case 0 To 3000 ' h1 = h1 + 1 ' Case 3001 To 7000 ' h2 = h2 + 1 ' Case 7001 To 9000 ' h3 = h3 + 1 ' Case 9001 To 11000 ' h4 = h4 + 1 ' Case 11001 To 12000 ' h5 = h5 + 1 ' Case 12001 To 15000 ' h6 = h6 + 1 ' Case 15001 To 16000 ' h7 = h7 + 1 ' Case 16001 To 17000 ' h8 = h8 + 1 ' Case Else ' h9 = h9 + 1 ' End Select Next c7 Next c6
Else ...omitted... For c6 = c5 + 1 To 51 For c7 = c6 + 1 To 52 vType = .Type vnumber = .tnumber If SN(c6) = vType Then vnumber = vnumber + 1: adjust(vnumber) = CN(c6) If SN(c7) = vType Then vnumber = vnumber + 1: adjust(vnumber) = CN(c7) Select Case vnumber Case Is condition1 rank = DW_Array(rec_number + cindex(c6, c7)) Case condition2 rank = SW_Array(Rec_number2 (another non-lexographic indexing algorithm) * XX) Case condition3 rank = SW_Array(Rec_number2 * XX + fn1(adjust(6))) Case Else rank = SW_Array(Rec_number2 * XX + cindex(c6, c7)) End Select ' Select Case rank ' Case 0 To 3000 ' h1 = h1 + 1 ' Case 3001 To 7000 ' h2 = h2 + 1 ' Case 7001 To 9000 ' h3 = h3 + 1 ' Case 9001 To 11000 ' h4 = h4 + 1 ' Case 11001 To 12000 ' h5 = h5 + 1 ' Case 12001 To 15000 ' h6 = h6 + 1 ' Case 15001 To 16000 ' h7 = h7 + 1 ' Case 16001 To 17000 ' h8 = h8 + 1 ' Case Else ' h9 = h9 + 1 ' End Select Next c7 Next c6 End If End With Next c5 Next c4 Next c3 Next c2 Next c1
'dump counters
'Output.Text = h1 & vbNewLine & h2 & vbNewLine & h3 & vbNewLine & h4 & vbNewLine & h5 & vbNewLine & h6 & vbNewLine & h7 & vbNewLine & h8 & vbNewLine & h9 & vbNewLine
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Follow up. Trying to figure out how u map from table 5 to 6, or table 6 to a final hand value without large memory requirements. There about 2.5 Million combinations 5 cards, thust your table would seem to require 2.5 million combinations, yielding a table 6 that is lil over 100 MB.
The one way i thought around this is that your tables may already be condensed using equivalence classes, so the numbers could be smaller. That is, maybe if I wrote the big tables, and backwards eliminated duplicates, it would condense.
Did you do something like that, or totally different?
P.s. your tables lookups for c1 through c3 would probably be a pretty small part of your table(s).
Why not just write them as an array (52,52,52) and put duplicate values in the array to that (1,2,3) = (3,2,1). Looking up a single value from a multi dimensional array is usually much faster than looking up values from 3 different arrays, and performing addition between each lookup.
The table I use is for best 5 of 7 cards, not best 5 of 6, or just the only 5 given. Therefore I take advantage of that information when identifing the states.
The "compression" occurs starting at the 4th card.
If the first 4 cards of a 7 card hand are all of different suits, there is no way we could make a flush.
Therefore AsKcQdJh is given the State ID of AxKxQxJx.
There are a total of 24 combinations that would map to the same State ID.
A hand like 9c3sTh5c is given the State ID of Tx9c5c3x
The State ID needs to indicate that 2 of the cards are clubs, in case the next 3 cards are also clubs.
5hJdKdAh would get the StateID AhKdJd5h
Since we could end up with either a heart flush or a diamond flush.
If we are at State ID AhKdJd5h and the next card is a 6s, the new State ID would be AxKxJx6x5x since a flush is no longer possible.
The 270725 4-card combos map down to 84448
With 5 cards you need 3 of the same suit, otherwise all suits can be ignored.
Using an array of (52,52,52) probably wouldn't be faster.
Multi-dimension arrays tend to involve a multiply or two internally.
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Made some adjustments to my code, am currently getting 279 Million games per second. Although I still think your algorithm may have greater potential (possibly if I can combine it with mine).
This is what happens when you let lawyers near compilers 
I'm not 100% sure about VB, but it's possible that since you commented out where you use 'rank', the optimizer eliminated the code that compute/lookup the value for rank.
You might just be timing "empty" for - next loops.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
gotchya, my ".type" uses the similar type of suit compression that you are doing in your lookup tables. Basically signalling when suits can be ignored.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
nah, i ran it with the checksum counters to make sure i got the same checksum results. Promise my timings are legit. I did comment out the checksums though to get the faster time. I did test empty loops too, i get about 4.2-8.5 biilion a second. Hard to get an exact read, because seems timer increments in .015XXXX intervals
Only commented out the "select case" portion to determine what type of hand was made. but still have an assignment to the variable "rank".
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
how big are the LU tables combined under this method?
Just one large table... Exactly 127,499,424 bytes used as 31,874,856 32-bit integers.
The 1st 206 bytes were for error catching. (entering the same card twice) but since they don't catch all of the possibilities, they are pretty much wasted space.
|
Andrew Prock
enthusiast
Reged: 09/02/02
Posts: 346
Loc: oakland
|
|
As you can see its speed is primarily due to being able to solve for many hands in one lookup
Yeah,
There's a distinct difference between lumping evals together and a fast generic evaluator. You can certainly do evals rather quickly if you are able to "share results" across cases. Under specific circumstances this is a fairly straightforard process. In general, it's rather difficult. How fast can you get the generic evaluator to go?
- Andrew
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
nah, i ran it with the checksum counters to make sure i got the same checksum results. Promise my timings are legit. I did comment out the checksums though to get the faster time. I did test empty loops too, i get about 4.2-8.5 biilion a second. Hard to get an exact read, because seems timer increments in .015XXXX intervals
Only commented out the "select case" portion to determine what type of hand was made. but still have an assignment to the variable "rank".
This is exactly what I was talking about.
Just becuase you have code that assigns a value to rank, doesn't mean that code will be executed. Optimizers can detect that you aren't using the value assigned to rank, and therefore eliminate the code that does the assigning.
"I did test empty loops too, i get about 4.2-8.5 biilion a second."
Ok I see what's happening... since you "looped" 133 millions times in 1 time unit (approx 0.01573936) you divide 133 million by 0.01573936 giving the 8.5 billion.
That makes the code being eliminated by the optimizer even more likely.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
I get 4.2 to 8.5 billion with empty loops on an AMD Athlon 64 fx-60 dual 2gigs.
Anyways, when i add back in the commented out checksums i drop from 279 million a second, to 102 million a second. Now is that because rank was being optimized out...hard to say, because right now most calculations are simply a array lookups. Comparisons such as If X > Y only occur when a problem gets flagged as difficult. Adding 9 range comparisons (if x between 0 and 3000) should definately slow it down a bit...so i'm thinking that the 239 million is correct as is. But at any rate, 102 million with time to process checksums included.
I hate select case, and if..then operations in vbasic b/c they are slow....which is why i avoid them whenever i can.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Quote:
There's a distinct difference between lumping evals together and a fast generic evaluator. You can certainly do evals rather quickly if you are able to "share results" across cases. Under specific circumstances this is a fairly straightforard process. In general, it's rather difficult. How fast can you get the generic evaluator to go?
This is true, my program "cheats." But for pretty much all my purposes I need a cheating algorithm cause its faster. Game theory is all working backwards from specific flops-turns-rivers (FTRs) and unknown strategies or distributions. Thus, rarely will i just want to know the value of one two-card holding for any given flop, but how one distribution compares to another. Even normal applications, such as calculating rankings for multiple players on the same flop can benefit from a cheating algorithm. I mean do we really need to look up hand values if all your opponents have already folded? So cheating algorithms are faster for me.... But your point is understood.
Anyways, if i put everything inside all 7 for loops i get about 65 million games a second. It could probably be optimized a bit more for these conditions. For instance, if five cards on the board, my code will look up a corresponding array for those 5 cards, and then compute values using only the final two cards. No need to do this if that 5 card array is only going to be used once.
|
Andrew Prock
enthusiast
Reged: 09/02/02
Posts: 346
Loc: oakland
|
|
This is true, my program "cheats."
I wouldn't call that cheating. But it's an important distinction. If people want to make comparisons, it's useful to agree on what you're comparing. PokerStove can do over 1.5 billion evals/sec by grouping evals together. But that's different from a generic evaluation.
- Andrew
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Tried my luck at a Lexographical version: This one i'll share all ...b/c largely inspired by posts here. I don't yet have a sorting algorithm for it yet. Gull you said you had a good one? Pre-sorted, its 209 million a second (140 million with checksums), but i suspect most the work will be done sorting it once someone adds that feature. It so happens that the for loops gull wanted us to use for the test...are pre-sorted in ascending order, which is what this lexographic index requires.
Once you include sort time, i would think Mykey's would have to be faster. His puts the cards in order, and reduce by equivalence classes with each lookup, as well as assigning a unique index. If i get time i'll try to add that feature, but sounds tough.
Mykey, if i make those improvements, do u want me to post em or no? Probably wouldn't have thought of em, if not for u, so let me know if you'd prefer me not posting those methods.
Main Function:
Dim index As Long Dim rank As Integer Dim c1 As Integer Dim c2 As Integer Dim c3 As Integer Dim c4 As Integer Dim c5 As Integer Dim c6 As Integer Dim c7 As Integer
date1 = Timer For c1 = 1 To 46 For c2 = c1 + 1 To 47 For c3 = c2 + 1 To 48 For c4 = c3 + 1 To 49 For c5 = c4 + 1 To 50 For c6 = c5 + 1 To 51 For c7 = c6 + 1 To 52
'insert a sort algorithm here c1....through c7 index = A_Sum1(c1) + A_Sum2(c1, c2) + A_Sum3(c2, c3) + A_Sum4(c3, c4) + A_Sum5(c4, c5) + A_Sum6(c5, c6) + (c7 - c6) rank = Rank_Array(index)
Next c7 Next c6 Next c5 Next c4 Next c3 Next c2 Next c1 date2 = Timer
This is code necessary to generate the arrays: Run it once, store the arrays in a file, and load them.
Open "C:\Program Files\DevStudio\VB\Poker5\Rank_Array.bin" For Binary As #5 Get #5, , Rank_Array
vindex = 51 For i = 1 To 46 P_Sum1(i) = Combin(vindex, 6) vindex = vindex - 1 Next i
vindex = 50 For i = 2 To 47 P_Sum2(i) = Combin(vindex, 5) vindex = vindex - 1 Next i
vindex = 49 For i = 3 To 48 P_Sum3(i) = Combin(vindex, 4) vindex = vindex - 1 Next i
vindex = 48 For i = 4 To 49 P_Sum4(i) = Combin(vindex, 3) vindex = vindex - 1 Next i
vindex = 47 For i = 5 To 50 P_Sum5(i) = Combin(vindex, 2) vindex = vindex - 1 Next i
vindex = 46 For i = 6 To 51 P_Sum6(i) = Combin(vindex, 1) vindex = vindex - 1 Next i
For i = 1 To 46 For j = 1 To i - 1 A_Sum1(i) = A_Sum1(i) + P_Sum1(j) Next j Next i
For i = 1 To 46 For j = i + 2 To 47 For k = i + 1 To j - 1 A_Sum2(i, j) = A_Sum2(i, j) + P_Sum2(k) Next k Next j Next i
For i = 2 To 47 For j = i + 2 To 48 For k = i + 1 To j - 1 A_Sum3(i, j) = A_Sum3(i, j) + P_Sum3(k) Next k Next j Next i
For i = 3 To 48 For j = i + 2 To 49 For k = i + 1 To j - 1 A_Sum4(i, j) = A_Sum4(i, j) + P_Sum4(k) Next k Next j Next i
For i = 4 To 49 For j = i + 2 To 50 For k = i + 1 To j - 1 A_Sum5(i, j) = A_Sum5(i, j) + P_Sum5(k) Next k Next j Next i
For i = 5 To 50 For j = i + 2 To 51 For k = i + 1 To j - 1 A_Sum6(i, j) = A_Sum6(i, j) + P_Sum6(k) Next k Next j Next i
//////Need this function to calculate permutations////
Private Function Combin(n As Byte, r As Byte) As Long
Dim t As Byte Dim u As Double Dim v As Double
If r > n Then Combin = 0 Else If r = n Then Combin = 1 Else u = 1 For t = (n - r) + 1 To n u = u * t Next t Rem Permutation(n,r) is calculated as above v = 1 For t = 1 To r v = v * t Next t Rem r! is calculated as above Rem r!= 1*...*(r-1)*r Combin = (u / v) Rem Combination = n! / ((n-r)! * r!) Rem Combination(n,r) = Permutation(n,r) / r! Rem (n!)/(n-r!) = ((n-r)+1)*((n-r)+2)*....*n End If End If
End Function
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
I know poker stove pre-flop is all tabled out...extremely fast. But once u specify part of the flop, it starts working off tables (or at least not as fast)...from about 1.7 billion(on my comp) to between 3 to 16 million (with wide player distributions so not as to run into the narrow distribution problem). So i do think these algorithm's do have a lot of merit for partial flop analysis.
Again not knocking p-stove, but i think these algorithms shine in the right conditions.
|
Andrew Prock
enthusiast
Reged: 09/02/02
Posts: 346
Loc: oakland
|
|
I know poker stove pre-flop is all tabled out...
I'm not sure what you mean by this, but I'm pretty sure that you don't understand what PokerStove is doing. It doesn't use extensive lookup tables. In fact, it does quite the opposite. If you look at the memory footprint of the program, you'll see that it's right around 6 MB.
But once u specify part of the flop, it starts working off tables (or at least not as fast)...
I dunno, I get it running at over 200 evals/sec with a flop.
So i do think these algorithm's do have a lot of merit for partial flop analysis.
I certainly agree with you on that point. PokerStove certainly isn't a complete solution to the evaluation problem. There is lots of improvement available all over the place.
- Andrew
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
This is true, my program "cheats."
I wouldn't call that cheating. But it's an important distinction. If people want to make comparisons, it's useful to agree on what you're comparing. PokerStove can do over 1.5 billion evals/sec by grouping evals together. But that's different from a generic evaluation.
- Andrew
Andrew,
I'm so disapointed. 
I set up 10 players with "Any Broadway", and the Board to "Ac Kc Qc Jc Tc"
What I expected from pokerstove:
Text results appended to pokerstove.txt
2,375,880,867,360,000 games 0.005 secs 475,176,173,472,000,000 games/sec
Board: Ac Kc Qc Jc Tc Dead:
equity (%) win (%) tie (%) Hand 1: 10.0000 % 00.00% 10.00% { TT+, ATs+, KTs+, QTs+, JTs, ATo+, KTo+, QTo+, JTo} Hand 2: 10.0000 % 00.00% 10.00% { TT+, ATs+, KTs+, QTs+, JTs, ATo+, KTo+, QTo+, JTo} Hand 3: 10.0000 % 00.00% 10.00% { TT+, ATs+, KTs+, QTs+, JTs, ATo+, KTo+, QTo+, JTo} Hand 4: 10.0000 % 00.00% 10.00% { TT+, ATs+, KTs+, QTs+, JTs, ATo+, KTo+, QTo+, JTo} Hand 5: 10.0000 % 00.00% 10.00% { TT+, ATs+, KTs+, QTs+, JTs, ATo+, KTo+, QTo+, JTo} Hand 6: 10.0000 % 00.00% 10.00% { TT+, ATs+, KTs+, QTs+, JTs, ATo+, KTo+, QTo+, JTo} Hand 7: 10.0000 % 00.00% 10.00% { TT+, ATs+, KTs+, QTs+, JTs, ATo+, KTo+, QTo+, JTo} Hand 8: 10.0000 % 00.00% 10.00% { TT+, ATs+, KTs+, QTs+, JTs, ATo+, KTo+, QTo+, JTo} Hand 9: 10.0000 % 00.00% 10.00% { TT+, ATs+, KTs+, QTs+, JTs, ATo+, KTo+, QTo+, JTo} Hand 10: 10.0000 % 00.00% 10.00% { TT+, ATs+, KTs+, QTs+, JTs, ATo+, KTo+, QTo+, JTo}
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
I'll be honest guys, I'm incredibly dissapointed! I thought I had something near optimum, but I must be missing something obvious! My meak 15 million per second is not much compared to your dozens of millions!
Anyway, incase any of you are interested, this is the best way to sort 7 cards. Please note, this sort algorithm I wrote beats all the classic sorting algorithms (quick sort, merge, insertion, std::Sort). It's basically a precomputed LUT for a pigeonhole sort.
// Represent the hand sevenCardHand = ((__int64)1 << c1) | ((__int64)1 << c2) | ((__int64)1 << c3) | ((__int64)1 << c4) | ((__int64)1 << c5) | ((__int64)1 << c6) | ((__int64)1 << c7);
// Split 64 bit int into 4 chunks of 16 bits firstChunk = (sevenCardHand & 0xFFFF); secondChunk = (sevenCardHand & 0xFFFF0000) >> 16; thirdChunk = (sevenCardHand & 0xFFFF00000000) >> 32; fourthChunk = (sevenCardHand & 0xFFFF000000000000) >> 48;
// Reset pointer to sorted hand array sortedPointer = 0;
// Retrieve records numToLoop = intToIndexArr_L1[firstChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[firstChunk][anotherCounter];
numToLoop = intToIndexArr_L1[secondChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[secondChunk][anotherCounter] + 16;
numToLoop = intToIndexArr_L1[thirdChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[thirdChunk][anotherCounter] + 32;
numToLoop = intToIndexArr_L1[fourthChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[fourthChunk][anotherCounter] + 48;
Edited by Gullanian (12/28/06 02:18 PM)
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Where intToIndexArr_L1 holds 8 integers. The first holding the number of indexes to return, and the next 7 the indexes of on bits.
Then the way mine works is by applying an algorithm on the sorted cards into a combination ID to look up in O(1) access time. Algorithm works by summating precomputed choose summations to work out the index.
Also please note for the test I gave, I apply the sorting algorithm even though I don't need to, I could just keep a combination counter incrementing on each iteration and use that for an index lookup, this speeds it up hugely. However, in real world cases the hand needs to be sorted, which is why I have left it in.
Edited by Gullanian (12/28/06 02:21 PM)
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Just checked out PokerStove as well, incredible. I would love to know how that works, it's baffling for me. 4 cycles per hand!
Edited by Gullanian (12/28/06 02:33 PM)
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Without sorting and just using combination index it gives me around 150 million per second. The sort is slowing it down a lot.
|
Andrew Prock
enthusiast
Reged: 09/02/02
Posts: 346
Loc: oakland
|
|
Yeah, that's a tough one. With only 20 broadway cards, we're five cards short of a feasible scenario.
- Andrew
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
I'll be honest guys, I'm incredibly dissapointed! I thought I had something near optimum, but I must be missing something obvious! My meak 15 million per second is not much compared to your dozens of millions!
Anyway, incase any of you are interested, this is the best way to sort 7 cards. Please note, this sort algorithm I wrote beats all the classic sorting algorithms (quick sort, merge, insertion, std::Sort). It's basically a precomputed LUT for a pigeonhole sort.
// Represent the hand sevenCardHand = ((__int64)1 << c1) | ((__int64)1 << c2) | ((__int64)1 << c3) | ((__int64)1 << c4) | ((__int64)1 << c5) | ((__int64)1 << c6) | ((__int64)1 << c7);
// Split 64 bit int into 4 chunks of 16 bits firstChunk = (sevenCardHand & 0xFFFF); secondChunk = (sevenCardHand & 0xFFFF0000) >> 16; thirdChunk = (sevenCardHand & 0xFFFF00000000) >> 32; fourthChunk = (sevenCardHand & 0xFFFF000000000000) >> 48;
// Reset pointer to sorted hand array sortedPointer = 0;
// Retrieve records numToLoop = intToIndexArr_L1[firstChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[firstChunk][anotherCounter];
numToLoop = intToIndexArr_L1[secondChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[secondChunk][anotherCounter] + 16;
numToLoop = intToIndexArr_L1[thirdChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[thirdChunk][anotherCounter] + 32;
numToLoop = intToIndexArr_L1[fourthChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[fourthChunk][anotherCounter] + 48;
I'm not fluent in the different flavors of C but here goes.
sevenCardHand = ((__int64)1 << c1) | ((__int64)1 << c2) | ((__int64)1 << c3) | ((__int64)1 << c4) | ((__int64)1 << c5) | ((__int64)1 << c6) | ((__int64)1 << c7); int n = 1; int64 bit = 1; for (int x = 0; x < 52; x++, bit <<= 1) { if (sevenCardHand && bit) { switch (n) { case 1 : c1 = x; case 2 : c2 = x; case 3 : c3 = x; case 4 : c4 = x; case 5 : c5 = x; case 6 : c6 = x; case 7 : c7 = x; } n++; } } wouldn't this sort c1..c7 as fast?
This could probably also be switch/cased to death and make a large, but fast solution.
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Yeah, that's a tough one. With only 20 broadway cards, we're five cards short of a feasible scenario.
- Andrew
What a brain fart.. when I selected any broadway it lit up a group 5x5.. My brain observed that 5x5 = 25 and then subtracted the 5 board cards giving 20 cards left over.
I hesitated a moment realizing that Royal holdem only exists for 6 people max for a reason, but I let that line of thought fade.
Ok Redo.
7 people with Any Broadway, and a board of Ac Kc Qc Jc Tc.
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
mykey1961: I've tried looping through each bit individually and dumping it in the array, and it's significantly slower than the solution I proposed. This is a type of pigeon hole sort, and because of the low cards to empty slot ratio looping each bit is too inefficient. The precomputed tables are significantly faster.
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
I'll be honest guys, I'm incredibly dissapointed! I thought I had something near optimum, but I must be missing something obvious! My meak 15 million per second is not much compared to your dozens of millions!
Anyway, incase any of you are interested, this is the best way to sort 7 cards. Please note, this sort algorithm I wrote beats all the classic sorting algorithms (quick sort, merge, insertion, std::Sort). It's basically a precomputed LUT for a pigeonhole sort.
// Represent the hand sevenCardHand = ((__int64)1 << c1) | ((__int64)1 << c2) | ((__int64)1 << c3) | ((__int64)1 << c4) | ((__int64)1 << c5) | ((__int64)1 << c6) | ((__int64)1 << c7);
// Split 64 bit int into 4 chunks of 16 bits firstChunk = (sevenCardHand & 0xFFFF); secondChunk = (sevenCardHand & 0xFFFF0000) >> 16; thirdChunk = (sevenCardHand & 0xFFFF00000000) >> 32; fourthChunk = (sevenCardHand & 0xFFFF000000000000) >> 48;
// Reset pointer to sorted hand array sortedPointer = 0;
// Retrieve records numToLoop = intToIndexArr_L1[firstChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[firstChunk][anotherCounter];
numToLoop = intToIndexArr_L1[secondChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[secondChunk][anotherCounter] + 16;
numToLoop = intToIndexArr_L1[thirdChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[thirdChunk][anotherCounter] + 32;
numToLoop = intToIndexArr_L1[fourthChunk][0]; for(anotherCounter = 1; anotherCounter < numToLoop; anotherCounter++) sortedHand[sortedPointer++] = intToIndexArr_L1[fourthChunk][anotherCounter] + 48;
There seems to be alot of dereferences and increments in the above method (ie: even though pigeonhole sort is O(n) there are alot of constant factors to be added for such a small n...).
Have you compared it to a hard compiled Sorting Network, eg:
Quote:
Network for N=7, using Bose-Nelson Algorithm: SWAP(1, 2); SWAP(0, 2); SWAP(0, 1); SWAP(3, 4); SWAP(5, 6); SWAP(3, 5); SWAP(4, 6); SWAP(4, 5); SWAP(0, 4); SWAP(0, 3); SWAP(1, 5); SWAP(2, 6); SWAP(2, 5); SWAP(1, 3); SWAP(2, 4); SWAP(2, 3);
Even though it takes 16 compare and swap operations, it can actually be done in 6 parallel steps (ie: it's depth) and a modern compiler should be able to get close to this by utilizing CPU pipelines. Overall, for very small n I've always found them to be the most efficient sort in terms of CPU cycles.
Juk
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
Quote:
Here is my demo (210 Kb).
http://www.pokerbolide.com/files/handeval7cards.exe
Andrzej Nironen
2.53 secs on my 3.4GHz P4.
My Hand_7_Eval, the source code for which is at http://www.stevebrecher.com/Software/software.html takes 3.69 secs when exercised as shown below. It uses some lookup tables whose total size is 254KB.
Code:
//deck is a 52-element array of __int64 //with one bit set per element int main(void) { clock_t timer;
int ix1, ix2, ix3, ix4, ix5, ix6, ix7; Hand_T board1, board2, board3, board4, board5, board6; int handTypeSum[9];
Init_Hand_Eval(); InitDeck(); for (ix1 = 0; ix1 < 9; ix1++) handTypeSum[ix1] = 0;
timer = clock();
for (ix1 = 0; ix1 < 46; ix1++) { board1 = deck[ix1]; for (ix2 = ix1+1; ix2 < 47; ix2++) { board2 = board1 | deck[ix2]; for (ix3 = ix2+1; ix3 < 48; ix3++) { board3 = board2 | deck[ix3]; for (ix4 = ix3+1; ix4 < 49; ix4++) { board4 = board3 | deck[ix4]; for (ix5 = ix4+1; ix5 < 50; ix5++) { board5 = board4 | deck[ix5]; for (ix6 = ix5+1; ix6 < 51; ix6++) { board6 = board5 | deck[ix6]; for (ix7 = ix6+1; ix7 < 52; ix7++) { handTypeSum[Hand_7_Eval(board6 | deck[ix7]) >> 24]++; } } } } } } }
timer = clock() - timer;
for (ix1 = 0; ix1 < 9; ix1++) printf("%d\n", handTypeSum[ix1]); printf("seconds = %.2f\n", (float)timer/CLOCKS_PER_SEC); }
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Code:
// Test7SortingNet.cpp : Defines the entry point for the console application. //
#include "stdafx.h" #include "random.h" #include <iostream>
using namespace std;
inline unsigned long long RDTSC(void) { _asm rdtsc; }
#define NUM_TESTS 100000 #define NUM_ITERS 100000000
int _tmain(int argc, _TCHAR* argv[]) {
// Create the tests. char Tests[NUM_TESTS][7]; for (int I=0;I<NUM_TESTS;I++) { for (int J=0;J<7;J++) { Tests[I][J]=RandInt(51); } } cout << "Tests created." << endl;
// Read start cycle. unsigned long long StartTime=RDTSC();
// Run the tests. for (int I=0;I<NUM_ITERS;I++) { char* V=Tests[I%NUM_TESTS];
// Using XOR. //#define SWAP(I,J) {if (V[I]>V[J]) {V[I]^=V[J]; V[J]^=V[I]; V[I]^=V[J];}}
// Using swaps. int T; #define SWAP(I,J) {if (V[I]>V[J]) {T=V[I]; V[I]=V[J]; V[J]=T;}}
SWAP(0, 4); SWAP(1, 5); SWAP(2, 6); SWAP(0, 2); SWAP(1, 3); SWAP(4, 6); SWAP(2, 4); SWAP(3, 5); SWAP(0, 1); SWAP(2, 3); SWAP(4, 5); SWAP(1, 4); SWAP(3, 6); SWAP(1, 2); SWAP(3, 4); SWAP(5, 6);
}
// cout << (double)(RDTSC()-StartTime)/(double)NUM_ITERS << " cycles per sort." << endl;
return 0; }
Runs at about 70 cycles per sort atm, but the MS VC compiler isn't generating what I want:
Code:
;SWAP(1, 5): mov cl, BYTE PTR [eax+1] mov dl, BYTE PTR [eax+5] cmp cl, dl jle SHORT $LN15@wmain movsx ecx, cl mov BYTE PTR [eax+1], dl mov BYTE PTR [eax+5], cl
The idea is that you can fit the 7 elements in 2 (or more) registers and then you don't need the "mov xx, BYTE PTR [eax+1]" instructions, but the compiler can't seem to see this and insists on storing as a vector (even if you create a 64 bit structure of 8x8 bytes and use the "register" definition prefix it does this too...).
This messing about using just cl and dl is also causing pipelines to stall which wouldn't happen as much if it were using registers to hold each of the 7 elements.
If I get more time I'll try and get it working as I'm pretty sure it should be possible to do in 30-40 cycles when optimized.
One other thing to try is to see if the XOR swap is any faster for an AMD, as for a P4 it is exactly the same even though it compiled down to an extra instruction per SWAP().
Juk
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
that won't run in VB will it darn.
|
A.Nironen
member
Reged: 10/29/06
Posts: 118
|
|
Guys you discuss different "weight categories". It's obvious if not to care about RAM used a huge lookup table works best. And it's much harder to create a fast algorithm using any reasonable amount of RAM.
Andrzej Nironen
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
Quote:
Quote:
Code:
133784560 hands 1.188 secs 112613302 hands/sec hc 23294460 pr 58627800 tp 31433400 th 6461620 st 6180020 fl 4047644 fh 3473184 fr 224848 sf 41584
This was on an AMD 3000+ with 1 gig memory running Win2k
This computer produced
133784560 hands 5.156 secs 25947355 hands/sec hc 23294460 pr 58627800 tp 31433400 th 6461620 st 6180020 fl 4047644 fh 3473184 fr 224848 sf 41584
using Andrzej Nironen's "Hand Evaluator Speed Demo"
Very impressive! (I think you have the results mixed up though - 112.5mil when doing extra work of incrementing the hand type counts, 50mil when not... )
Have you considered posting this method on the pokersource yahoo group?
Juk 
EDIT: Sorry, I just noticed you print the hand type counts in both procedures... BTW: How fast does it run without doing this (ie: just assigning an integer the hand evaluation rather than incrementing totals[]?)
0.953 secs 140382574 hands/sec
However I'm doubting the granularity of the timing process used.
I'll switch to the RDTSC method and repost relative values.
Using RDTSC as a timer
133784560 hands 2.640 secs 50675964 hands/sec
becomes
133784560 hands 5752663748 cycles 42.999 cycles/hand
133784560 hands 1.188 secs 112613302 hands/sec
becomes
133784560 hands 2645152642 cycles 19.772 cycles/hand
and
133784560 hands 0.953 secs 140382574 hands/sec
becomes
133784560 hands 2077269818 cycles 15.527 cycles/hand
I've kinda lost track of this thread now, but so far (out of all the algorithms listed here), which are the current best algorithms (in cycles per second & assuming no "cheating" - ie: algorithm speed measured against a 7-card hand selected uniformly randomly from all possible 7-card hands) for:
a) A "packed" 64bit integer representation. b) A unordered 7-integer representation.
Most of my work has used a packed representation in the past, but after seeing mykey1961's nested lookup idea running at 15 cycles per hand, I am seriously considering moving everything over to an unpacked representation to take advantage of this (~20mil hands/sec -> ~200mil hands/sec can't be ignored!).
Overall this thread has opened my eyes; I'd always just taken foregranted that the pokersource lib was highly optimized and assumed only marginal speed improvements would be possible in the future...
Great thread - Juk
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Jukofyork: Thank you for the sorting network idea! Never seen them before, very interesting! It sped my code up from 15million per second to 35 million per second, I think I can squeeze a little more out of it as well!
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
D&L, you can use the sorting network idea as well, just copy the defines into the SWAP statment.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Hey mykey question on your evaluator
Quote:
Totals[Table[Table[Table[Table[Table[Table[Table[c1+52]+c2]+c3]+c4]+c5]+c6]+c7] shr 20]);
not good with C++, but what are you representing hand strength as? a digit between 1 and 9? I notice your counters are highly efficient, because you can just use the hand rank as an index to your array. But to do that, is your hand rank just a number 1 through 9? as opposed to say, defining wich "2 pair" beat another "2 pair." Perhaps i'm just misunderstand, cause I don't know c++
P.s. how fast was your mapped array when incrementing counters, and when not? i think you flipped your times around..reporting a higher time with counters?
At anyrate, i'm 2/3 of the way done making your mapped table array for comparison purposes. Mapping one 4MB array to another takes like trillions of calculations (maybe you had some shortcut)...my computers grinding away.... Only one more array to map.
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Just realised however, in the test given the cards are all sorted, therfore it would run faster. I'm doing a real world test now with unsorted hands to test performance.
Edit: Tests sucesful! The precomputed sorting network works 2-3x faster. Thanks!
Edited by Gullanian (12/29/06 10:31 AM)
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Heres a better test for our evaluators:
holeCard1 = 0; holeCard2 = 1;
for(opCard1 = 0; opCard1 < 52; opCard1++) { if(opCard1 == holeCard1 || opCard1 == holeCard2) continue;
cout<<"."; cout.flush();
for(opCard2 = opCard1 + 1; opCard2 < 52; opCard2++) {
if(opCard2 == holeCard1 || opCard2 == holeCard2) continue;
// Loop through all the deck cards for(c1 = 0; c1 < 52; c1++) { if(c1 == holeCard1 || c1 == holeCard2 || c1 == opCard1 || c1 == opCard2) continue;
for(c2 = c1 + 1; c2 < 52; c2++) { if(c2 == holeCard1 || c2 == holeCard2 || c2 == opCard1 || c2 == opCard2) continue;
for(c3 = c2 + 1; c3 < 52; c3++) { if(c3 == holeCard1 || c3 == holeCard2 || c3 == opCard1 || c3 == opCard2) continue;
for(c4 = c3 + 1; c4 < 52; c4++) { if(c4 == holeCard1 || c4 == holeCard2 || c4 == opCard1 || c4 == opCard2) continue;
for(c5 = c4 + 1; c5 < 52; c5++) { if(c5 == holeCard1 || c5 == holeCard2 || c5 == opCard1 || c5 == opCard2) continue;
WORK OUT OUR HANDS STRENGTH (holeCard1, holeCard2, C1...C5) WORK OUT OPPONENTS HANDS STRENGTH (opCard1, opCard2, C1...C5)
// Increment totals counters if(ourHandStrength < oppHandStrength) totalWins++; else if (ourHandStrength > oppHandStrength) totalLosses++; else totalDraws++; }}}}}}}
cout<<endl<<endl<<"Wins : "<<totalWins<<" ("<<((float)totalWins/2097572400)*100<<"%)"<<endl; cout<<"Draws : "<<totalDraws<<" ("<<((float)totalDraws/2097572400)*100<<"%)"<<endl; cout<<"Losses : "<<totalLosses<<" ("<<((float)totalLosses/2097572400)*100<<"%)"<<endl<<endl; cout<<"Total Combinations : "<<totalWins + totalDraws + totalLosses<<endl; cout<<"Time MS : "<<(endTime-startTime)<<endl; cout<<"Time Secs : "<<(endTime-startTime)/1000<<endl; cout<<"Time Mins : "<<((endTime-startTime)/1000)/60<<endl; cout<<"Iters per second : "<<(2097572400/(endTime-startTime))*1000<<endl<<endl;
My evaluator can run through the 2.1 billion games in 2 minutes. Pretty fast in my opinion, but from the numbers other people are getting im sure it can be beaten flat on it's face!
Edited by Gullanian (12/29/06 10:40 AM)
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
Quote:
Heres a better test for our evaluators:
(For a constant pair of hole cards, evaluate all combos of one opponent and board). Why is this better? Also, while it won't make a significant difference, one might as well avoid unnecessary loop executions and conditions (untested modification): Code:
holeCard1 = 0; holeCard2 = 1;
for(opCard1 = holeCard2 + 1; opCard1 < 51; opCard1++) {
cout<<"."; cout.flush();
for(opCard2 = opCard1 + 1; opCard2 < 52; opCard2++) {
// Loop through all the deck cards > holecard2 for(c1 = holeCard2 + 1; c1 < 48; c1++) { if(c1 == opCard1 || c1 == opCard2) continue;
for(c2 = c1 + 1; c2 < 49; c2++) { if(c2 == opCard1 || c2 == opCard2) continue;
for(c3 = c2 + 1; c3 < 50; c3++) { if(c3 == opCard1 || c3 == opCard2) continue;
for(c4 = c3 + 1; c4 < 51; c4++) { if(c4 == opCard1 || c4 == opCard2) continue;
for(c5 = c4 + 1; c5 < 52; c5++) { if(c5 == opCard1 || c5 == opCard2) continue;
// evaluate and tally here }}}}}}}
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
I believe it to be a better test bed because with the other test, the cards are presented ordered already, which means that some sort algorithms by nature will out perform others. In this scenario, the two sets of 7 cards are not always ordered (but partially ordered). I think it's a fairer test.
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Hey mykey question on your evaluator
Quote:
Totals[Table[Table[Table[Table[Table[Table[Table[c1+52]+c2]+c3]+c4]+c5]+c6]+c7] shr 20]);
not good with C++, but what are you representing hand strength as? a digit between 1 and 9? I notice your counters are highly efficient, because you can just use the hand rank as an index to your array. But to do that, is your hand rank just a number 1 through 9? as opposed to say, defining wich "2 pair" beat another "2 pair." Perhaps i'm just misunderstand, cause I don't know c++
P.s. how fast was your mapped array when incrementing counters, and when not? i think you flipped your times around..reporting a higher time with counters?
At anyrate, i'm 2/3 of the way done making your mapped table array for comparison purposes. Mapping one 4MB array to another takes like trillions of calculations (maybe you had some shortcut)...my computers grinding away.... Only one more array to map.
I'm not using the rank as an index.
Think of my 1 big table as broken down into 7 smaller tables.
I start at state 0, where I have no knowledge about the cards to be ranked.
When I get my first card to be ranked, I use my current state, and the 1st card as a vector to my new state.
State_1 = Table0[State_0,Card_1] State_2 = Table1[State_1,Card_2] State_3 = Table2[State_2,Card_3] State_4 = Table3[State_3,Card_4] State_5 = Table4[State_4,Card_5] State_6 = Table5[State_5,Card_6]
This last step is a little different
Rank = Table6[State_6,Card_7]
The values in Table6 are not states like in Table0 thru Table5, they are actual rankings.
My ranking is a 24 bit value. it's best viewed in Hex format.
If my 7 cards are
AcQh3c8hTcTd5d = 0x2AEC80
A = Ten B = Jack C = Queen D = King E = Ace
6s5s4h3sJs2dTc = 0x465432
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Quote:
Heres a better test for our evaluators:
(For a constant pair of hole cards, evaluate all combos of one opponent and board). Why is this better? Also, while it won't make a significant difference, one might as well avoid unnecessary loop executions and conditions (untested modification): Code:
holeCard1 = 0; holeCard2 = 1;
for(opCard1 = holeCard2 + 1; opCard1 < 51; opCard1++) {
cout<<"."; cout.flush();
for(opCard2 = opCard1 + 1; opCard2 < 52; opCard2++) {
// Loop through all the deck cards > holecard2 for(c1 = holeCard2 + 1; c1 < 48; c1++) { if(c1 == opCard1 || c1 == opCard2) continue;
for(c2 = c1 + 1; c2 < 49; c2++) { if(c2 == opCard1 || c2 == opCard2) continue;
for(c3 = c2 + 1; c3 < 50; c3++) { if(c3 == opCard1 || c3 == opCard2) continue;
for(c4 = c3 + 1; c4 < 51; c4++) { if(c4 == opCard1 || c4 == opCard2) continue;
for(c5 = c4 + 1; c5 < 52; c5++) { if(c5 == opCard1 || c5 == opCard2) continue;
// evaluate and tally here }}}}}}}
I think the choice of
holeCard1 = 0; holeCard2 = 1;
was unfortunate.
If it had been
holeCard1 = x; holeCard2 = y;
where x and y are defines elsewhere then the loop collision checking is required.
even
holeCard1 = 1; holeCard2 = 10;
would require loop checking.
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
I just gave the primitive loop as a base to work on, I've just managed to speed mine up a lot by creating arrays holding values to loop through, eliminating the need of all the ORs.
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
I just gave the primitive loop as a base to work on, I've just managed to speed mine up a lot by creating arrays holding values to loop through, eliminating the need of all the ORs.
I think an updating double linked list of unused cards might allow for faster looping.
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
I've optimised my code a lot more, it's looping all 2.1 billion games in around 103-106 seconds.
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
Jukofyork: Thank you for the sorting network idea! Never seen them before, very interesting! It sped my code up from 15million per second to 35 million per second, I think I can squeeze a little more out of it as well!
Hey, np and glad it was helpful. I think this is one of the most interesting threads we've ever had running here!
Juk
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
MyKey,
Am I right in saying that your procedure doesn't need a sort for the input cards. If you "train" your array with all the permutations, it would give the correct handrank no matter what order the cards come in (True?).
Another thought is to reduce the size of the array by half by using the 7462 (i.e. unsigned short) possible hand ranks (ref cactus key). Most possibilities for one hand rank group (pairs) is 2860 (12 possible pairs + Combin(12,3) for the other 3 cards) for the bottom 12 bits, while using the top 4 bits for the 9 main hand ranks (high card, pairs, 2pair...). A sixty meg array feels twice as good as a 120 meg one. 
Thank You for the interesting posts, I am definitely watching with interest.
Thank You, Ray
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
MyKey,
Am I right in saying that your procedure doesn't need a sort for the input cards. If you "train" your array with all the permutations, it would give the correct handrank no matter what order the cards come in (True?).
This is correct.
Quote:
Another thought is to reduce the size of the array by half by using the 7462 (i.e. unsigned short) possible hand ranks (ref cactus key). Most possibilities for one hand rank group (pairs) is 2860 (12 possible pairs + Combin(12,3) for the other 3 cards) for the bottom 12 bits, while using the top 4 bits for the 9 main hand ranks (high card, pairs, 2pair...). A sixty meg array feels twice as good as a 120 meg one. 
Thank You for the interesting posts, I am definitely watching with interest.
Thank You, Ray
I lumped everything into one big table for ease of use.
If I used a different table for each # of cards, and sized them to the most efficent 1-byte/2-byte/4-byte, usage would drop from 120Mb down to 90Mb.
I'm not sure that would be the most efficient in terms of speed though.
There may be some time lost when the CPU loads a 16 bit value from an address that isn't a multiple of 4.
Since I was using just 32 bit values, everything was aligned "properly".
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
I believe it to be a better test bed because with the other test, the cards are presented ordered already, which means that some sort algorithms by nature will out perform others. In this scenario, the two sets of 7 cards are not always ordered (but partially ordered). I think it's a fairer test.
This test wouldn't change my times significantly.
int ps1, ps2, ps3, ps4, ps5, ps6, pRank; int os1, os2, os3, os4, os5, os6, oRank;
holeCard1 = 0; holeCard2 = 1;
ps1 = Table[holeCard1]; ps2 = Table[ps1+holeCard2]; for(opCard1 = holeCard2 + 1; opCard1 < 51; pCard1++){ cout<<".";cout.flush(); os1 = Table[opCard1]; for(opCard2 = opCard1 + 1; opCard2 < 52; opCard2++) {// Loop through all the deck cards > holecard2 os2 = Table[os1+opCard1]; for(c1 = holeCard2 + 1; c1 < 48; c1++){ if(c1 == opCard1 || c1 == opCard2)continue; ps3 = Table[ps2+c1]; os3 = Table[os2+c1]; for(c2 = c1 + 1; c2 < 49; c2++){ if(c2 == opCard1 || c2 == opCard2)continue; ps4 = Table[ps3+c2]; os4 = Table[os3+c2]; for(c3 = c2 + 1; c3 < 50; c3++){ if(c3 == opCard1 || c3 == opCard2)continue; ps5 = Table[ps4+c3]; os5 = Table[os4+c3]; for(c4 = c3 + 1; c4 < 51; c4++){ if(c4 == opCard1 || c4 == opCard2)continue; ps6 = Table[ps5+c4]; os6 = Table[os5+c4]; for(c5 = c4 + 1; c5 < 52; c5++){ if(c5 == opCard1 || c5 == opCard2)continue; pRank = Table[ps6+c5]; oRank = Table[os6+c5]; // evaluate and tally here}}}}}}} compare pRank to oRank
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
I modified a version that becomes a large quantity of pointers.
Rank = State0^[c1]^[c2]^[c3]^[c4]^[c5]^[c6]^[c7]
Ain't that pretty?
It also is just a bit faster.
Now a "State" is actually a pointer, to an array of pointers.. etc ... Lions, Tigers, and Bears... oh my.
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Quote:
I modified a version that becomes a large quantity of pointers.
Rank = State0^[c1]^[c2]^[c3]^[c4]^[c5]^[c6]^[c7]
Ain't that pretty?
It also is just a bit faster.
Now a "State" is actually a pointer, to an array of pointers.. etc ... Lions, Tigers, and Bears... oh my.
Interesting tweak,
How are you able to save your trained array? You were saying that it is faster... How long does it take for the thread's test now?
Ray...
|
Mogobu The Fool
addict
Reged: 09/10/05
Posts: 617
Loc: On teh internets.
|
|
Agreed, Juk... most interesting thread I've seen for a long time. I've been out of poker and coding for months now - have a Corp job in Manhattan again - but I decided to peek back here, and found the most interesting thread by looking for where you've been commenting! 
Some of the techniques here have been making my head flex, which feels good... don't really understand all of them at first read, but thanks, all, for contributing.
Wanted to throw one more wrench in the works for you all; it's what I was planning to look into next time I was revisiting my hand eval code, which will probably never happen now. (Actually, I had dropped my hand-eval code, which was an optimized version of pokeval ported to VB, and started using Keith Rule's C# version, which you can find at codeproject.com.)
Anyway, as soemone noted, there are very few actual hand rankings in poker, because so many hands reduce to the same ranking. In 5-card poker, there are 7462 different rankings. In 7-card poker, there are only 4824 of them. (Yeah, fewer hands with 7 cards. Think of it this way - give yourself the lowest ranking 5-card hand. Now add two cards without making a better hand. Can't. Therefore, a whole set of 5-card hands are impossible in 7-card poker.)
Anyway, since there are only 4824 results, it seems that mapping the 52!/45! different random poker hands to the 4824 results is a natural fit for a perfect hash problem.
There are a couple of well-described perfect hash generators out there (see, for example, http://burtleburtle.net/bob/hash/perfect.html). Anybody take a stab at playing with them for the poker evaluation problem? Run time on finding the perfect hash might be pretty intensive, but once a solution is found, you've got a VERY fast evaluator. You don't even need a minimal perfect hash - a fast hash which maps to a table with 64k entries will still be plenty small in memory, and will potentially allow a great many more solutions.
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Agreed, Juk... most interesting thread I've seen for a long time. I've been out of poker and coding for months now - have a Corp job in Manhattan again - but I decided to peek back here, and found the most interesting thread by looking for where you've been commenting! 
Some of the techniques here have been making my head flex, which feels good... don't really understand all of them at first read, but thanks, all, for contributing.
Wanted to throw one more wrench in the works for you all; it's what I was planning to look into next time I was revisiting my hand eval code, which will probably never happen now. (Actually, I had dropped my hand-eval code, which was an optimized version of pokeval ported to VB, and started using Keith Rule's C# version, which you can find at codeproject.com.)
Anyway, as soemone noted, there are very few actual hand rankings in poker, because so many hands reduce to the same ranking. In 5-card poker, there are 7462 different rankings. In 7-card poker, there are only 4824 of them. (Yeah, fewer hands with 7 cards. Think of it this way - give yourself the lowest ranking 5-card hand. Now add two cards without making a better hand. Can't. Therefore, a whole set of 5-card hands are impossible in 7-card poker.)
Anyway, since there are only 4824 results, it seems that mapping the 52!/45! different random poker hands to the 4824 results is a natural fit for a perfect hash problem.
There are a couple of well-described perfect hash generators out there (see, for example, http://burtleburtle.net/bob/hash/perfect.html). Anybody take a stab at playing with them for the poker evaluation problem? Run time on finding the perfect hash might be pretty intensive, but once a solution is found, you've got a VERY fast evaluator. You don't even need a minimal perfect hash - a fast hash which maps to a table with 64k entries will still be plenty small in memory, and will potentially allow a great many more solutions.
A perfect hash is not what you want.
Since there are 133784560 combos, a perfect hash would produce a hash in the range of 0..133784559 or larger.
If you could devise a hash that wasn't perfect so it overlapped just the right combo of cards, that's still not enough, you also need the value of the hash is in the right order.
If you notice in creating the hash, for each card, there would be a lookup, shifts, xor's etc. That's gotta be slower than one lookup per card.
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
MyKey,
Am I right in saying that your procedure doesn't need a sort for the input cards. If you "train" your array with all the permutations, it would give the correct handrank no matter what order the cards come in (True?).
Another thought is to reduce the size of the array by half by using the 7462 (i.e. unsigned short) possible hand ranks (ref cactus key). Most possibilities for one hand rank group (pairs) is 2860 (12 possible pairs + Combin(12,3) for the other 3 cards) for the bottom 12 bits, while using the top 4 bits for the 9 main hand ranks (high card, pairs, 2pair...). A sixty meg array feels twice as good as a 120 meg one. 
Thank You for the interesting posts, I am definitely watching with interest.
Thank You, Ray
Since D&L talked about trillions of calculations, and I've been asked how I generate the tables. Here is a partial example of the process I chose to use.
This calculates a StateID for any given 4 card combo. It doesn't have to be lightneing fast, because this is only used to build the tables, not while they are in use.
function Make4(c1, c2, c3, c4 : integer) : int64; var ID : int64; t : integer; sc : array[1..4] of integer; begin
// In my "world" 2s = 0, 3s = 1,.....Kc = 50, Ac = 51. // However in the ID process I prefered that // 2s = 0x21, 3s = 0x31,.... Kc = 0xD4, Ac = 0xE4 // This allows me to sort in Rank then Suit order
c1 := (((c1 mod 13)+2) shl 4) + (c1 div 13)+1; c2 := (((c2 mod 13)+2) shl 4) + (c2 div 13)+1; c3 := (((c3 mod 13)+2) shl 4) + (c3 div 13)+1; c4 := (((c4 mod 13)+2) shl 4) + (c4 div 13)+1;
// sc[1..4] is a counter of suits (how many spades do I have, etc)
fillchar(sc,sizeof(sc),0); inc(sc[c1 and $F]); inc(sc[c2 and $F]); inc(sc[c3 and $F]); inc(sc[c4 and $F]);
// if we don't have at least 2 cards of the same suit, we make this card suit 0.
if sc[c1 and $F] < 2 then c1 := c1 and $F0; if sc[c2 and $F] < 2 then c2 := c2 and $F0; if sc[c3 and $F] < 2 then c3 := c3 and $F0; if sc[c4 and $F] < 2 then c4 := c4 and $F0;
// my Q&D sort in Rank then Suit order
repeat if c1 < c2 then begin t := c1; c1 := c2; c2 := t; end; if c2 < c3 then begin t := c2; c2 := c3; c3 := t; end; if c3 < c4 then begin t := c3; c3 := c4; c4 := t; end; until (c1 >= c2) and (c2 >= c3) and (c3 >= c4);
// the resulting ID is a 64 bit value with each card represented by 8 bits.
ID := c1; ID := (ID shl 8) + c2; ID := (ID shl 8) + c3; ID := (ID shl 8) + c4; Make4 := ID; end;
// I cycle thru the 270725 combinations of 4 card hands, // and save all of the ID's to an array. I sort the array, // then remove all duplicate ID's. This gives me an array // of 84448 4-card IDs in sorted order. The value of a // state, is the index into that array to holds a matching // StateID.
// EL0 is a pointer to an array of integers // EL0^[c1] = state1; // EL1 thru EL6 is an pointer to an array of arrays of integers // EL1^[state1][c2] = state2;
for c1 := 0 to 48 do for c2 := c1+1 to 49 do for c3 := c2+1 to 50 do for c4 := c3+1 to 51 do begin
// Check to see if this combo has already been assigned
s4 := EL3^[EL2^[EL1^[EL0^[c1]][c2]][c3]][c4]; if s4 = -1 then begin
StateID := Make4(c1,c2,c3,c4);
// Binary search for the stateID in the list of StateID's // keeping the index where we find it
hi := MaxState4; lo := -1; d := -1; while d <> 0 do begin s4 := (hi + lo) shr 1; d := StateID - StateList4^[s4]; if d < 0 then hi := s4; if d > 0 then lo := s4; end; end;
// We don't need to do all the permutations of 4 cards
EL3^[EL2^[EL1^[EL0^[c1]][c2]][c3]][c4] := s4; EL3^[EL2^[EL1^[EL0^[c1]][c2]][c4]][c3] := s4; EL3^[EL2^[EL1^[EL0^[c1]][c4]][c3]][c2] := s4; EL3^[EL2^[EL1^[EL0^[c4]][c2]][c3]][c1] := s4; end; assignfile(f,'Eval3.dat'); rewrite(f,sizeof(TEvalVector)); blockwrite(f,EL3^,MaxState3); closefile(f);
Edited by mykey1961 (12/30/06 03:12 PM)
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
My evaluator uses a perfect minimal hashing technique, and I can get 45 million evaluations per second, which comes down to about 45 cycles per hand.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
I would try using one of the no-sorting algorithms. My "259 mill" a sec algorithm (which i would like to partially retract mykey was right part of the code was optimized out in that test - its more like 145 mill a second with a trivial checksum counter (if rank <> 0 then count ++) - anyways, that algorithm doesn't sort. It rights values in multiple locations in arrays, because flush space (4^7) and number space (13^7) are small tables, and combines them to figure out straight flushes, and flush values. In fact 13^7 and 4^7 are overstatements, because it also has its own version of mapped arrays, and reduction by equivalent or impossible classe...just one mapped array though from 5 card to 7 card (thats my index + subindex).
I'm writing a 52 card mapped array for comparison purposes. The tables will be much bigger, so we see if size of LU tables slow things down when there is a large difference in size.
|
Phil153
Notable Twit
Reged: 10/18/05
Posts: 4905
|
|
Quote:
My evaluator uses a perfect minimal hashing technique, and I can get 45 million evaluations per second, which comes down to about 45 cycles per hand.
Sounds like your speed is equivalent to D&L's. He's getting between 4.5 and 8 (lol - just put it in a for loop you dummy and get an exact number, you don't have divide by 0.015) billion cycles per second on his machine. Which equates to about 200 million hands/sec under your code.
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Quote:
My evaluator uses a perfect minimal hashing technique, and I can get 45 million evaluations per second, which comes down to about 45 cycles per hand.
Sounds like your speed is equivalent to D&L's. He's getting between 4.5 and 8 (lol - just put it in a for loop you dummy and get an exact number, you don't have divide by 0.015) billion cycles per second on his machine. Which equates to about 200 million hands/sec under your code.
He's a lawyer, he's expected to make those kinds of mistakes. I'll have to send him a bill for $600 to cover the 3 minutes it took to point that out.
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Quote:
Quote:
I just gave the primitive loop as a base to work on, I've just managed to speed mine up a lot by creating arrays holding values to loop through, eliminating the need of all the ORs.
I think an updating double linked list of unused cards might allow for faster looping.
One fairly fast and easy Way to ignore the used cards: int cardused[52]; // keep track of cards used
memset(cardused, 0, sizeof(cardused)); // init first
as you use a card if (cardused[card]) continue; // forget this one... cardused[card] = 1; // remember it is used
when completed using a card cardused[card] = 0; // mark this card as available
Ray...
|
Mogobu The Fool
addict
Reged: 09/10/05
Posts: 617
Loc: On teh internets.
|
|
Quote:
Quote:
Anyway, as soemone noted, there are very few actual hand rankings in poker, because so many hands reduce to the same ranking. In 5-card poker, there are 7462 different rankings. In 7-card poker, there are only 4824 of them.
A perfect hash is not what you want.
Since there are 133784560 combos, a perfect hash would produce a hash in the range of 0..133784559 or larger.
If you could devise a hash that wasn't perfect so it overlapped just the right combo of cards, that's still not enough, you also need the value of the hash is in the right order.
I'm using the hash to index a lookup table, so order has no impact here.
Since the solution set has less than 5k different values, you can reduce to a hash table which is far smaller than your source set. It's simply a matter of refining the code used to find the perfect hash such that hash collisions are not considered collisions if the value stored in the hash table the same for both inputs. In other words, if two equivalent hands hash to the same element in the hash table, it is not a collision.
Quote:
If you notice in creating the hash, for each card, there would be a lookup, shifts, xor's etc. That's gotta be slower than one lookup per card.
Actually, it would be against a compressed format where the entire card set is represented in a single 52-bit value. So there would be one "round" of bit manipulations per evaluation. In theory, it could also accomodate 5, 6, and 7 card hands in one function - but then you would need the larger result set of all 7462 possible hands (no big difference, but an efficient function would be harder to find.)
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
Agreed, Juk... most interesting thread I've seen for a long time. I've been out of poker and coding for months now - have a Corp job in Manhattan again - but I decided to peek back here, and found the most interesting thread by looking for where you've been commenting! 
Hey, glad to hear the new job is going well. I've also been having been having a break from coding (and poker) over the last month or so, but as always have a whole lot of new ideas built up ready for 2007.
Quote:
Some of the techniques here have been making my head flex, which feels good... don't really understand all of them at first read, but thanks, all, for contributing.
Wanted to throw one more wrench in the works for you all; it's what I was planning to look into next time I was revisiting my hand eval code, which will probably never happen now. (Actually, I had dropped my hand-eval code, which was an optimized version of pokeval ported to VB, and started using Keith Rule's C# version, which you can find at codeproject.com.)
Anyway, as soemone noted, there are very few actual hand rankings in poker, because so many hands reduce to the same ranking. In 5-card poker, there are 7462 different rankings. In 7-card poker, there are only 4824 of them. (Yeah, fewer hands with 7 cards. Think of it this way - give yourself the lowest ranking 5-card hand. Now add two cards without making a better hand. Can't. Therefore, a whole set of 5-card hands are impossible in 7-card poker.)
Anyway, since there are only 4824 results, it seems that mapping the 52!/45! different random poker hands to the 4824 results is a natural fit for a perfect hash problem.
There are a couple of well-described perfect hash generators out there (see, for example, http://burtleburtle.net/bob/hash/perfect.html). Anybody take a stab at playing with them for the poker evaluation problem? Run time on finding the perfect hash might be pretty intensive, but once a solution is found, you've got a VERY fast evaluator. You don't even need a minimal perfect hash - a fast hash which maps to a table with 64k entries will still be plenty small in memory, and will potentially allow a great many more solutions.
I remember you mentioning this along time ago, but up until recently I never really put much thought into improving hand evaluators; expecting only minimal performance increases possible in the evaluators themselves (how wrong I was!!!).
Anyway, off back to nurse the hangover... Happy new year! Juk
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Nothing to do with a "perfect hash" (or hand evaluators), but just wondered if anybody knew of a better/faster way to do the following.
I use this function to index into a hashtable (of size "NumEllementsPow2") when passed a 64bit key.
Code:
int FoldKey(unsigned long long N,int NumEllementsPow2) { // Get the required (index) key from the large 64-bit key. // This uses the folding method, as the low/high bits of the random number // generator ar poor on their own. // NOTE: Do NOT simply and out the requires bit or use modulous as this will // give a very poor distribution of indexes using the current scheme.
int IndexKey=0; int I;
// Make sure all bits are used for the index to get a good distribution. for (I=64-NumEllementsPow2;I>0;I-=NumEllementsPow2) IndexKey^=(N>>I); if (I!=0) IndexKey^=(N&((1<<(I+NumEllementsPow2+1))-1));
// Take just the bits we need. return IndexKey&((1<<NumEllementsPow2)-1);
} // End FoldKey.
In theory I wish I could just use the much faster version (which fails to distribute the hash entries properly...):
Code:
int GetKey(unsigned long long N,int NumEllementsPow2) { // Get the required (index) key from the large 64-bit key. return N&((1<<NumEllementsPow2)-1); }
Since I only have to make the hash codes once, I could use a much more computationally expensive random number generator than I currently use, but so far I can't seem to find one which lets me get away with the code above, always requiring the slow FoldKey() function every time I want to map from 64bit key to the hash table index itself...
Does anybody know of a RNG which will let me just take the bottom N bits and still give a good distribution, and/or a faster method by which I could fold the keys?
Juk
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
Quote:
Does anybody know of a RNG which will let me just take the bottom N bits and still give a good distribution, and/or a faster method by which I could fold the keys?
Juk
George Marsaglia, who devoted his professional (academic) life to RNGs, posted the following articles to the usenet sci.math group a few years ago. I think you might find them of interest:
http://groups.google.com/group/sci.math/msg/9959175f66dd138f?hl=en
http://groups.google.com/group/sci.math/msg/2a5329cdf4d574a5?hl=en
|
TheCardGeek
journeyman
Reged: 07/09/06
Posts: 54
|
|
I don't know if my hand evaluator is faster than yours (We would have to exchange benchmark code wouldn't we?), but I've spent a long time writing evaluators for different games too. I find it faster to use a 64 bit int as a bit mask for my evaluator than using seperate ints to represent the cards. Card order is irrelevant when represented in a bit mask, and you can represent a hand of any number of cards in a single 52 (64) bit integer.
|
Phil153
Notable Twit
Reged: 10/18/05
Posts: 4905
|
|
I believe this is what poker-eval (aka pokersource) uses. The trouble is, those 5 or 7 bits shifts to create the mask, and then the bitwise operations on that mask to determine duplicates, flushedness, and the if...else operations etc all take CPU cycles. Even with lookup tables it's hard to get the CPU cycles down. Every table lookup for bit counts or straight evaluation or whatever is more time gone.
The optimizations in this thread are a step up. Packing a hand into one ulong simply isn't an optimal strategy.
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
Quote:
Does anybody know of a RNG which will let me just take the bottom N bits and still give a good distribution, and/or a faster method by which I could fold the keys?
Juk
George Marsaglia, who devoted his professional (academic) life to RNGs, posted the following articles to the usenet sci.math group a few years ago. I think you might find them of interest:
http://groups.google.com/group/sci.math/msg/9959175f66dd138f?hl=en
http://groups.google.com/group/sci.math/msg/2a5329cdf4d574a5?hl=en
Thanks, I'll give them a try.
Juk
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi,
I finished a consolidated version of the "7 Card Hand Evaluators" written in C. I will comment it tonight and see if I can post it tomorrow... Can I attach 300-400 lines of code to a message (or if it is reasonable to do so?) I did modify things a bit, using a 53 card layout to use the 0 card for current hand strength at 5 and 6 cards. Order doesn't matter, and it gets it all done with an average of less then 10 clock cycles per lookup, about .6 seconds on an amd x2 machine (using one processor for this run) to run the full test at the beginning of this list. File size is: 129,951,336 bytes and the setup program writes out the HandRank file in about a minute. If you folks know a better way to get the code let me know... BTW. I have been a lurker on this board for almost 2 years, but this thread is the best I have seen... Keep it goin'!
Ray...
|
pygmyhipo
journeyman
Reged: 04/26/05
Posts: 80
|
|
Quote:
I will comment it tonight and see if I can post it tomorrow ... BTW. I have been a lurker on this board for almost 2 years, but this thread is the best I have seen... Keep it goin'!
I totally agree about this thread. It's exciting to see this kind of progress. I'm one of the old pokersource / poker-eval maintainers and I didn't imagine that such gains were possible. In fact, I was coming around to the opinion that the Age of Lookup Tables had passed, thanks to the increasing gap between the time for one instruction and the latency of a cache miss. I thought the next generation of poker-eval would drop all memory accesses and just concentrate on a fast, totally cached instruction stream.
Anyway, if any of you feel like sharing code, I'd be happy to add it to the pokersource library. Although all the current code is GPL, you would be free to apply a different license like Apache or whatever to your contributions.
As for Ray's C version, if you post that I volunteer to port it to Java. It would be nice to have a superfast java evaluator that people could use without the hassles that come with JNI in the current pokersource code.
By the way, the pokersource discussion group is at: http://tech.groups.yahoo.com/group/pokersource/
-pyg
|
_dave_
_Pooh-Bah_
Reged: 02/17/05
Posts: 2628
Loc: UK
|
|
WAY over my head, but I have to agree, one of the most interesting threads in a long time... from the OP:
Quote:
Hi all, new user but was told this was a good poker forum!
Not sure if this is the right place to post, please move if it is not.
So glad you did 
dave.
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
RayW:Quote:
... Can I attach 300-400 lines of code to a message (or if it is reasonable to do so?)
I don't see why not (although I hope replies to it won't quote the whole thing )
...Not to discourage you from contributing to the pokersource library c/o pygmyhipo.
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
I'd like to show my code, but firstly this is partly for my dissertation so I think my university owns part of it, and secondly, 40 clock cycles and a 250mb lookup is a lot worse than what some other people here have!
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Well, Results are in:
BAD!! = 0 High Card = 23294460 Pair = 58627800 Two Pair = 31433400 Three of a Kind = 6461620 Straight = 6180020 Flush = 4047644 Full House = 3473184 Four of a Kind = 224848 Straight Flush = 41584 Total Hands = 133784560
Validation seconds = 0.5620 Total HighPrecision Clocks = 1127079718 HighPrecision clocks per lookup = 8.424587
This uses Cactus Kev's Eval Routine ref http://www.suffecool.net/poker/evaluator.html Which I moved Paul D. Senzee's Optimized Hand Evaluator ref http://www.geocities.com/psenzee/code/fast_eval.c eval_5hand_fast(int c1, int c2, int c3, int c4, int c5) routine into Cactus Kevs eval_5hand routine.
You will need to add the routines in PokerLib.c to poker.h file.
Here is the main code which I called HandRankSetup.cpp: Code:
// HandRankSetup.cpp : Sets up the HandRank File for VERY fast Lookups // by Ray Wotton and the 2+2 list My code is GPL, use it as you like
#include "stdafx.h" #include "windows.h" #include "poker.h" #include <stdlib.h> #include <string.h> #include <time.h>
const char HandRanks[][16] = {"BAD!!","High Card","Pair","Two Pair","Three of a Kind","Straight","Flush","Full House","Four of a Kind","Straight Flush"};
__int64 IDs[612978]; int HR[32487834];
int numIDs = 1; int numcards = 0; int maxHR = 0; __int64 maxID = 0;
__int64 MakeID(__int64 IDin, int newcard) // adding a new card to this ID { __int64 ID = 0; int suitcount[4 + 1]; int rankcount[13 + 1]; int workcards[8]; // intentially keeping one as a 0 end int cardnum; int getout = 0; memset(workcards, 0, sizeof(workcards)); memset(rankcount, 0, sizeof(rankcount)); memset(suitcount, 0, sizeof(suitcount)); for (cardnum = 0; cardnum < 6; cardnum++) { // can't have more than 6 cards! workcards[cardnum + 1] = (int) ((IDin >> (8 * cardnum)) & 0xff); // leave the 0 hole for new card }
// my cards are 2c = 1, 2d = 2 ... As = 52 newcard--; // make 0 based!
workcards[0] = (((newcard >> 2) + 1) << 4) + (newcard & 3) + 1; // add next card formats card to rrrr00ss
for (numcards = 0; workcards[numcards]; numcards++) { suitcount[workcards[numcards] & 0xf]++; // need to see if suit is significant rankcount[(workcards[numcards] >> 4) & 0xf]++; // and rank to be sure we don't have 4! if (numcards) { if (workcards[0] == workcards[numcards]) { // can't have the same card twice getout = 1; // if so need to get out after counting numcards } } }
if (getout) { return 0; // duplicated another card (ignore this one) }
int needsuited = numcards - 2; // for suit to be significant - need to have n-2 of same suit if (numcards > 4) { for (int rank = 1; rank < 14; rank++) { if (rankcount[rank] > 4) { // if I have more than 4 of a rank then I shouldn't do this one!! return 0; // can't have more than 4 of a rank so return an ID that can't be! } } } // However in the ID process I prefered that // 2s = 0x21, 3s = 0x31,.... Kc = 0xD4, Ac = 0xE4 // This allows me to sort in Rank then Suit order // if we don't have at least 2 cards of the same suit for 4, we make this card suit 0. if (needsuited > 1) { for (cardnum = 0; cardnum < numcards; cardnum++) { // for each card if (suitcount[workcards[cardnum] & 0xf] < needsuited) { // check suitcount to the number I need to have suits significant workcards[cardnum] &= 0xf0; // if not enough - 0 out the suit - now this suit would be a 0 vs 1-4 } } }
// Sort Using XOR. Network for N=7, using Bose-Nelson Algorithm: Thanks to the thread! #define SWAP(I,J) {if (workcards[I] < workcards[J]) {workcards[I]^=workcards[J]; workcards[J]^=workcards[I]; workcards[I]^=workcards[J];}}
SWAP(0, 4); SWAP(1, 5); SWAP(2, 6); SWAP(0, 2); SWAP(1, 3); SWAP(4, 6); SWAP(2, 4); SWAP(3, 5); SWAP(0, 1); SWAP(2, 3); SWAP(4, 5); SWAP(1, 4); SWAP(3, 6); SWAP(1, 2); SWAP(3, 4); SWAP(5, 6);
// long winded way to put the pieces into a __int64 // cards in bytes --66554433221100 // the resulting ID is a 64 bit value with each card represented by 8 bits. ID = (__int64) workcards[0] + ((__int64) workcards[1] << 8) + ((__int64) workcards[2] << 16) + ((__int64) workcards[3] << 24) + ((__int64) workcards[4] << 32) + ((__int64) workcards[5] << 40) + ((__int64) workcards[6] << 48); return ID; }
int SaveID(__int64 ID) { if (ID == 0) return 0; // don't use up a record for a 0!
if (ID >= maxID) { // take care of the most likely first goes on the end... if (ID > maxID) { // greater than create new else it was the last one! IDs[numIDs++] = ID; // add the new ID maxID = ID; } return numIDs - 1; }
// find the slot I will find it (by a pseudo bsearch algorithm) int low = 0; int high = numIDs - 1; __int64 testval; int holdtest;
while (high - low > 1) { holdtest = (high + low + 1) / 2; testval = IDs[holdtest] - ID; if (testval > 0) high = holdtest; else if (testval < 0) low = holdtest; else return holdtest; // got it!! } // I guess it couldn't be found so must be added to the current location (high) // make space... // don't expect this much! memmove(&IDs[high + 1], &IDs[high], (numIDs - high) * sizeof(IDs[0]));
IDs[high] = ID; // do the insert into the hole created numIDs++; return high; }
int DoEval(__int64 IDin) { // I guess I have some explaining to do here... I used the Cactus Kevs Eval ref http://www.suffecool.net/poker/evaluator.html // I Love the pokersource for speed, but I needed to do some tweaking to get it my way // and Cactus Kevs stuff was easy to tweak ;-) int handrank = 0; int cardnum; int workcard; int rank; int suit; int mainsuit = 20; // just something that will never hit... need to eliminate the main suit from the iterator int suititerator = 0; int holdrank; int workcards[8]; // intentially keeping one as a 0 end int holdcards[8]; int numevalcards = 0;
// See Cactus Kevs page for explainations for this type of stuff... const int primes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41 }; memset(workcards, 0, sizeof(workcards)); memset(holdcards, 0, sizeof(holdcards));
if (IDin) { // if I have a good ID then do it... for (cardnum = 0; cardnum < 7; cardnum++) { // convert all 7 cards (0s are ok) holdcards[cardnum] = (int) ((IDin >> (8 * cardnum)) & 0xff); if (holdcards[cardnum] == 0) break; // once I hit a 0 I know I am done numevalcards++; // if not 0 then count the card if (suit = holdcards[cardnum] & 0xf) { // find out what suit (if any) was significant mainsuit = suit; // and remember it } }
for (cardnum = 0; cardnum < numevalcards; cardnum++) { // just have numcards... workcard = holdcards[cardnum];
// convert to cactus kevs way!! ref http://www.suffecool.net/poker/evaluator.html // +--------+--------+--------+--------+ // |xxxbbbbb|bbbbbbbb|cdhsrrrr|xxpppppp| // +--------+--------+--------+--------+ // p = prime number of rank (deuce=2,trey=3,four=5,five=7,...,ace=41) // r = rank of card (deuce=0,trey=1,four=2,five=3,...,ace=12) // cdhs = suit of card // b = bit turned on depending on rank of card
rank = (workcard >> 4) - 1; // my rank is top 4 bits 1-13 so convert suit = workcard & 0xf; // my suit is bottom 4 bits 1-4, order is different, but who cares? if (suit == 0) { // if suit wasn't significant though... suit = suititerator++; // Cactus Kev needs a suit! if (suititerator == 5) // loop through available suits suititerator = 1; if (suit == mainsuit) { // if it was the sigificant suit... Don't want extras!! suit = suititerator++; // skip it if (suititerator == 5) // roll 1-4 suititerator = 1; } } // now make Cactus Keys Card workcards[cardnum] = primes[rank] | (rank << 8) | (1 << (suit + 11)) | (1 << (16 + rank)); }
switch (numevalcards) { // run Cactus Keys routines case 5 : holdrank = eval_5cards(workcards[0],workcards[1],workcards[2],workcards[3],workcards[4]); break; // if 6 cards I would like to find HandRank for them // Cactus Key is 1 = highest - 7362 lowest I need to get the min for the permutations case 6 : holdrank = eval_5cards(workcards[0],workcards[1],workcards[2],workcards[3],workcards[4]); holdrank = __min(holdrank, eval_5cards(workcards[0],workcards[1],workcards[2],workcards[3],workcards[5])); holdrank = __min(holdrank, eval_5cards(workcards[0],workcards[1],workcards[2],workcards[4],workcards[5])); holdrank = __min(holdrank, eval_5cards(workcards[0],workcards[1],workcards[3],workcards[4],workcards[5])); holdrank = __min(holdrank, eval_5cards(workcards[0],workcards[2],workcards[3],workcards[4],workcards[5])); holdrank = __min(holdrank, eval_5cards(workcards[1],workcards[2],workcards[3],workcards[4],workcards[5])); break; case 7 : holdrank = eval_7hand(workcards); break; default : // problem!! shouldn't hit this... printf(" Problem with numcards = %d!!\n", numcards); break; }
// I would like to change the format of Catus Kev's ret value to: // hhhhrrrrrrrrrrrr hhhh = 1 high card -> 9 straight flush // r..r = rank within the above 1 to max of 2861 handrank = 7463 - holdrank; // now the worst hand = 1 if (handrank < 1278) handrank = handrank - 0 + 4096 * 1; // 1277 high card else if (handrank < 4138) handrank = handrank - 1277 + 4096 * 2; // 2860 one pair else if (handrank < 4996) handrank = handrank - 4137 + 4096 * 3; // 858 two pair else if (handrank < 5854) handrank = handrank - 4995 + 4096 * 4; // 858 three-kind else if (handrank < 5864) handrank = handrank - 5853 + 4096 * 5; // 10 straights else if (handrank < 7141) handrank = handrank - 5863 + 4096 * 6; // 1277 flushes else if (handrank < 7297) handrank = handrank - 7140 + 4096 * 7; // 156 full house else if (handrank < 7453) handrank = handrank - 7296 + 4096 * 8; // 156 four-kind else handrank = handrank - 7452 + 4096 * 9; // 10 straight-flushes
} return handrank; // now a handrank that I like }
int _tmain(int argc, _TCHAR* argv[]) { int count = 0; int card;
__int64 ID; int IDslot;
clock_t timer = clock(); // remember when I started
int handTypeSum[10]; memset(handTypeSum, 0, sizeof(handTypeSum)); // init memset(IDs, 0, sizeof(IDs)); memset(HR, 0, sizeof(HR));
// step through the ID array - always shifting the current ID and adding 52 cards to the end of the array. // when I am at 7 cards put the Hand Rank in!! // stepping through the ID array is perfect!! int IDnum; int holdid;
printf("\nGetting Card IDs!\n"); // as this loops through and find new combinations it adds them to the end // I need this list to be stable when I set the handranks (next set) (I do the insertion sort on new IDs these) // so I had to get the IDs first and then set the handranks for (IDnum = 0; IDs[IDnum] || IDnum == 0; IDnum++) { // start at 1 so I have a zero catching entry (just in case) for (card = 1; card < 53; card++) { // the ids above contain cards upto the current card. Now add a new card ID = MakeID(IDs[IDnum], card); // get the new ID for it if (numcards < 7) holdid = SaveID(ID); // and save it in the list if I am not on the 7th card } printf("\rID - %d", IDnum); // just to show the progress -- this will count up to 612976 } printf("\nSetting HandRanks!\n"); // this is as above, but will not be adding anything to the ID list, so it is stable for (IDnum = 0; IDs[IDnum] || IDnum == 0; IDnum++) { // start at 1 so I have a zero catching entry (just in case) for (card = 1; card < 53; card++) { ID = MakeID(IDs[IDnum], card); if (numcards < 7) IDslot = SaveID(ID) * 53 + 53; // when in the index mode (< 7 cards) get the id to save else IDslot = DoEval(ID); // if I am at the 7th card, get the HandRank to save maxHR = IDnum * 53 + card + 53; // find where to put it HR[maxHR] = IDslot; // and save the pointer to the next card or the handrank }
if (numcards == 6 || numcards == 7) { // an extra, If you want to know what the handrank when there is 5 or 6 cards // you can just do HR[u3] or HR[u4] from below code for Handrank of the 5 or 6 card hand HR[IDnum * 53 + 53] = DoEval(IDs[IDnum]); // this puts the above handrank into the array } printf("\rID - %d", IDnum); // just to show the progress -- this will count up to 612976 same as above! }
printf("\nNumber IDs = %d\nmaxHR = %d\n", numIDs, maxHR); // for warm fuzzys
timer = clock() - timer; // end the timer printf("Training seconds = %.2f\n", (float)timer/CLOCKS_PER_SEC); // display training time
LARGE_INTEGER timings, endtimings; // for high precision timing
timer = clock(); // now get current time for Testing!
// another algorithm right off the thread
int c0, c1, c2, c3, c4, c5, c6; int u0, u1, u2, u3, u4, u5;
QueryPerformanceCounter(&timings); // start High Precision clock
for (c0 = 1; c0 < 53; c0++) { u0 = HR[53+c0]; for (c1 = c0+1; c1 < 53; c1++) { u1 = HR[u0+c1]; for (c2 = c1+1; c2 < 53; c2++) { u2 = HR[u1+c2]; for (c3 = c2+1; c3 < 53; c3++) { u3 = HR[u2+c3]; for (c4 = c3+1; c4 < 53; c4++) { u4 = HR[u3+c4]; for (c5 = c4+1; c5 < 53; c5++) { u5 = HR[u4+c5]; for (c6 = c5+1; c6 < 53; c6++) { handTypeSum[HR[u5+c6] >> 12]++; count++; } } } } } } } QueryPerformanceCounter(&endtimings); // end the high precision clock
timer = clock() - timer; // get the time in this
for (int i = 0; i <= 9; i++) // display the results printf("\n%16s = %d", HandRanks[i], handTypeSum[i]); printf("\nTotal Hands = %d\n", count); __int64 clocksused = (__int64)endtimings.QuadPart - (__int64) timings.QuadPart; // calc clocks used from the High Precision clock
// and display the clock results printf("\nValidation seconds = %.4lf\nTotal HighPrecision Clocks = %I64d\nHighPrecision clocks per lookup = %lf\n", (double)timer/CLOCKS_PER_SEC, clocksused, (double) clocksused / 133784560.0) ;
// output the array now that I have it!! FILE * fout = fopen("HandRanks.dat", "wb"); if (!fout) { printf("Problem creating the Output File!\n"); return 1; } fwrite(HR, sizeof(HR), 1, fout); // big write, but quick fclose(fout);
return 0; }
///////////////////////////////// end code!!
Wow!! I don't give any promises, but I verified it to the best of my ability over the last few days... As I said earlier, with the setups mentioned above, you will run the setup in about a minute, and poker hands (hand card order doesn't make any difference by the way!!) VERY quickly!!
Any questions let me know... As I mentioned in the code, my stuff can all be GPL'd. pygmyhipo, you guys with the pokersource are absolutely incredible what you can do!! Whenever I had a general purpose evaluator to do, that is what I used! I know I should have used the pokersource for the evaluator for this, but perhaps you could rewrite the DoEval section to stub out the pokersource lib... This was a thread project, and it works GREAT!!
Have a Profitable Day... Ray...
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Just Thought I would put out the entire report for the code:
Getting Card IDs! ID - 612976 Setting HandRanks! ID - 612976 Number IDs = 612977 maxHR = 32487833 Training seconds = 57.71
BAD!! = 0 High Card = 23294460 Pair = 58627800 Two Pair = 31433400 Three of a Kind = 6461620 Straight = 6180020 Flush = 4047644 Full House = 3473184 Four of a Kind = 224848 Straight Flush = 41584 Total Hands = 133784560
Validation seconds = 0.5780 Total HighPrecision Clocks = 1141158660 HighPrecision clocks per lookup = 8.529823
Also some code to get you started using the file: Code:
// TestHandRank.cpp : Defines the entry point for the console application. //
#include "stdafx.h" #include "windows.h" #include <stdlib.h> #include <string.h> #include <time.h>
const char HandRanks[][16] = {"BAD!!","High Card","Pair","Two Pair","Three of a Kind","Straight","Flush","Full House","Four of a Kind","Straight Flush"};
int HR[32487834]; // hold the handranks LARGE_INTEGER timings, endtimings; // for good timing
int _tmain(int argc, _TCHAR* argv[]) { int count = 0; int handTypeSum[10]; int c0, c1, c2, c3, c4, c5, c6; int u0, u1, u2, u3, u4, u5;
memset(handTypeSum, 0, sizeof(handTypeSum)); // do init.. memset(HR, 0, sizeof(HR));
FILE * fin = fopen("..\\HandRanks.dat", "rb"); // get the hand array saved by HandRankSetup if (!fin) { // problem say so printf("Problem Opening Input File!\n"); return 1; } size_t bytesread = fread(HR, sizeof(HR), 1, fin); // get the HandRank Array fclose(fin); // close up
clock_t timer = clock(); // start regular clock
QueryPerformanceCounter(&timings); // start High Precision clock
for (c0 = 1; c0 < 53; c0++) { // run the test u0 = HR[53+c0]; for (c1 = c0+1; c1 < 53; c1++) { u1 = HR[u0+c1]; for (c2 = c1+1; c2 < 53; c2++) { u2 = HR[u1+c2]; for (c3 = c2+1; c3 < 53; c3++) { u3 = HR[u2+c3]; for (c4 = c3+1; c4 < 53; c4++) { u4 = HR[u3+c4]; for (c5 = c4+1; c5 < 53; c5++) { u5 = HR[u4+c5]; for (c6 = c5+1; c6 < 53; c6++) { handTypeSum[HR[u5+c6] >> 12]++; count++; } } } } } } }
QueryPerformanceCounter(&endtimings); // end the high precision clock
timer = clock() - timer; // end the regular clock
for (int i = 0; i <= 9; i++) // display results printf("\n%16s = %d", HandRanks[i], handTypeSum[i]); printf("\nTotal Hands = %d\n", count);
__int64 clocksused = (__int64)endtimings.QuadPart - (__int64) timings.QuadPart; // calc clocks used from the High Precision clock
// and display the clock results printf("\nValidation seconds = %.4lf\nTotal HighPrecision Clocks = %I64d\nHighPrecision clocks per lookup = %lf\n", (double)timer/CLOCKS_PER_SEC, clocksused, (double) clocksused / 133784560.0) ;
return 0; }
Which returns:
BAD!! = 0 High Card = 23294460 Pair = 58627800 Two Pair = 31433400 Three of a Kind = 6461620 Straight = 6180020 Flush = 4047644 Full House = 3473184 Four of a Kind = 224848 Straight Flush = 41584 Total Hands = 133784560
Validation seconds = 0.5620 Total HighPrecision Clocks = 1127908727 HighPrecision clocks per lookup = 8.430784
Not Bad  Have a Profitable Day! Ray...
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
This is soooo sweet! 
I just gave it a quick test and it's running at 233 million evals per second on my machine (~12.7 clocks per lookup). That totally blows away the old 21 million evals per second I was getting with pokersource... Time to convert my old code to use unpacked hands me thinks!!!
I'm assuming the only benefit to using the perfect hash version faster lookup creation? (I just used Cactus Kev's source).
Thanks - Juk 
PS: One other (unrelated) small thing I just worked out was how to get the code from within code-tags without losing all of the indentation: just hit "quote" and copy the text from within the edit box...
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
I'd like to show my code, but firstly this is partly for my dissertation so I think my university owns part of it, and secondly, 40 clock cycles and a 250mb lookup is a lot worse than what some other people here have!
Hey think of it this way: if you hadn't of made the original post, then this thread would never have started, so we have alot to thank you for!
Juk
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi Juk! Quote:
I'm assuming the only benefit to using the perfect hash version faster lookup creation? (I just used Cactus Kev's source).
Yes, the only reason I suggested Paul D. Senzee's Optimized Hand Evaluator was to keep the initial creation under a minute (and my debugging sanity )... You don't need it (you only run this once anyway).
Quote:
PS: One other (unrelated) small thing I just worked out was how to get the code from within code-tags without losing all of the indentation: just hit "quote" and copy the text from within the edit box...
Thank You for that Tip!! I was trying to figure out how to keep the formatting going on and coming out of this BBS. 
Sounds like you are Blazin' so I guess the instructions weren't toooo bad!
Have a Profitable Day! Ray...
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
This is quite possibly the only time I've looked at someone else's code and knew what they were doing the whole way.
I like the additional vector [0] to give 5, and 6 card evaluations as well.
My code peaked out at 12.5 cycles. Since our access method is the same, I've got to believe the difference is due to my 10 year old compiler.
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
Quote:
Just Thought I would put out the entire report for the code:
Thanks for sharing -- very interesting!
On my 3.4G P4: Validation seconds = 0.6410 Total HighPrecision Clocks = 2201553640 HighPrecision clocks per lookup = 16.455962
In testing order dependence, I found there was indeed none w/r correctness, but there is one w/r timing:
Code:
// reverse the loop limits for (c0 = 52; c0 > 6 ; c0--) { u0 = HR[53+c0]; for (c1 = c0-1; c1 > 5; c1--) { u1 = HR[u0+c1]; for (c2 = c1-1; c2 > 4; c2--) { u2 = HR[u1+c2]; for (c3 = c2-1; c3 > 3; c3--) { u3 = HR[u2+c3]; for (c4 = c3-1; c4 > 2; c4--) { u4 = HR[u3+c4]; for (c5 = c4-1; c5 > 1; c5--) { u5 = HR[u4+c5]; for (c6 = c5-1; c6 > 0; c6--) { handTypeSum[HR[u5+c6] >> 12]++; count++; } } } } } } }
Validation seconds = 0.7660 Total HighPrecision Clocks = 2622109016 HighPrecision clocks per lookup = 19.599489
I just ran the tests, and haven't yet looked into why there is a difference.
Separately... It makes no perceivable timing difference on our reasonably fast hardware, but I hope this thread is not locking us into this benchmark paradigm, e.g.,
for (c0 = 0; c0 < 52; c0++) for (c1 = c0+1; c0 < 52; c0++) ... for (c6 = c5+1; c6 < 52; c6++)
rather than
for (c0 = 0; c0 < 46; c0++) for (c1 = c0+1; c0 < 47; c0++) ... for (c6 = c5+1; c6 < 52; c6++)
Something in my twisted cycle-saving brain wants to see the latter
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi,
I noticed that I can increment faster than decrement as well (by about two cycles per lookup). I didn't consider it a killer since I was within one system clock tick for the full run anyway. Honestly, worst case this seems better then most others best case, and you don't have to worry about sending the cards in the wrong order. You have the Hand Ranking at the 5 or 6 card levels also. You can hit these pretty much any order you find available. Your cards, Opponent possible cards, flop...
I was thinking why I might be getting better numbers that you folks, I set the SSE2 code generation and fully optimizing all compile options, along with instrumenting and optimizing the outcome on VC2005. Other than that it is just a release run. Good Luck! Ray...
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
RayW:Quote:
I noticed that I can increment faster than decrement as well (by about two cycles per lookup). I didn't consider it a killer since I was within one system clock tick for the full run anyway. Honestly, worst case this seems better then most others best case, and you don't have to worry about sending the cards in the wrong order. ...
No worries, just curiosity! I thought maybe that there were more cache misses with the downward-counting loops, so I measured the mean absolute distance between successive table targets -- u1-u0, u2-u1, ...:
diffs with up loops: 34,556 599,907 2,769,070 6,278,534 12,873,344 46
diffs with down loops: 42,453 730,512 2,908,161 5,481,569 11,114,621 7
...doesn't seem significant.
|
MrPokerToad
stranger
Reged: 12/15/04
Posts: 12
|
|
(oops)
|
pygmyhipo
journeyman
Reged: 04/26/05
Posts: 80
|
|
Thanks for posting the code, Ray. I ported it to GCC (using -O3, on a linux box) with a few minor tweaks. Success! Some benchmarks on the 7-card hands validation time:
750 MHz Athlon (gcc 4.1.1): 1.84 secs 3.0 GHz Xeon (gcc 3.4.6): 0.79 secs
Not sure why the Athlon is more than 25% faster, it's hard to say given the different gcc versions.
Porting to Java looks straightforward. I'll let you know how that turns out.
-pyg
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
And now the down side.
make an array
int Hands[1000000][7], or int64 Hands[1000000]
and fill each line with 7 unique random cards.
Start your timer, and find the hand "rank" for each hand.
Due to cache misses, you'll probably get a cycle/hand value that is ugly.
Edited by mykey1961 (01/04/07 08:32 PM)
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi pyg!
Quote:
I ported it to GCC (using -O3, on a linux box) with a few minor tweaks. Some benchmarks on the 7-card hands validation time:
750 MHz Athlon (gcc 4.1.1): 1.84 secs
Wow! I am excited that it worked that easily for you! Your validation times for an Athlon 750 is only 3x slower than my AMD x2, which I would have thought would have been a bigger difference than that... I guess that new GCC compiler has a real good optimiser! Perhaps the memory bus speed isn't that different...
I am looking forward to seeing how the Java conversion goes, thank you for doing these conversions (Linux/Java) so others can have access to this...
Ray...
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
mykey1961 said,Quote:
And now the down side.
make an array
int Hands[1000000][7], or int64 Hands[1000000]
and fill each line with 7 unique random cards.
Start your timer, and find the hand "rank" for each hand.
Due to cache misses, you'll probably get a cycle/hand value that is ugly.
Yikes!
BAD!! = 0 High Card = 174274 Pair = 438503 Two Pair = 234621 Three of a Kind = 48395 Straight = 46295 Flush = 30097 Full House = 25870 Four of a Kind = 1634 Straight Flush = 311 Total Hands = 1000000
Validation seconds = 0.5940 Total HighPrecision Clocks = 2014806880 HighPrecision clocks per lookup = 2014.806880
And with my ancient Hand_7_Eval, which uses 284KB [sic] of tables:
BAD!! = 0 High Card = 174303 Pair = 438408 Two Pair = 235438 Three of a Kind = 47901 Straight = 46004 Flush = 30026 Full House = 26001 Four of a Kind = 1632 Straight Flush = 287 Total Hands = 1000000
Validation seconds = 0.0310 Total HighPrecision Clocks = 117501456 HighPrecision clocks per lookup = 117.501456
Here is the relevant portion of the test code. The commented-out lines were used for the first test above -- the one using RayW's tables based on your approach. The code as shown was used for the second test -- the one using my HandEval code, which uses a 52-bit input within a 64-bit integer: Code:
int deck[52], virginDeck[52]; for (int d = 0; d < 52; d++) //virginDeck[d] = d + 1; // lookup table wants cards in 1..52 virginDeck[d] = d;
for (int i = 0; i < 1000000; i++) { printf("\rDealing hand: %d", i); memmove(deck, virginDeck, sizeof(deck)); int r = RNG_C::Random_Under(52); //hands[i][0] = deck[r]; Hand_T h = ((Hand_T)1 << deck[r]); for (int c = 1; c < 7; c++) { memmove(&deck[r], &deck[r+1], (52-c-r)*sizeof(int)); // remove dealt card r = RNG_C::Random_Under(52-c); //hands[i][c] = deck[r]; h |= ((Hand_T)1 << deck[r]); } hands[i] = h; } printf("\n"); Init_Hand_Eval();
clock_t timer = clock(); // start regular clock
QueryPerformanceCounter(&timings); // start High Precision clock
for (int i = 0; i < 1000000; i++) { //int* h = hands[i]; //int p = 53; //for (int c = 0; c < 7; c++) // p = HR[p + *h++]; //handTypeSum[p >> 12]++; handTypeSum[(Hand_7_Eval(hands[i]) >> 24)+1]++; count++; }
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi mykey!
Quote:
And now the down side.
make an array
int Hands[1000000][7], or int64 Hands[1000000]
and fill each line with 7 unique random cards.
Start your timer, and find the hand "rank" for each hand.
Due to cache misses, you'll probably get a cycle/hand value that is ugly.
As you know, the main reason that this idea is so fast is that most of the "overhead" for a card is done is done in a previous stage where it only has to do 1/48th (or whatever) the work, so the total overhead for the average lookup is tiny. If you incur the entire overhead for every card for each individual lookup, of course it would take significantly longer, but wouldn't it take significantly longer in any library you go with given a random array of 7 cards 1-52 or 0-51? Honestly, I don't think that the overhead to HandRank for random cards is all that slow compared to any other library out there (its only one lookup and one sum per card) and of course by doing those operations you get the final handrank for this hand.
Even on worst case that other libraries can beat this routine for a random hand every now and then is OK by me. I don't look for a random hand very often, where I do the most work with an evaluator is doing an enumeration on a wider scale, and that is where I need the performance. Even if it is slower for random hands (and I don't think it is), it wouldn't be a big problem for my normal evaluation needs.
Thank You for checking that out, what kind of numbers were you getting? Have you tried the same test starting with the same set of input cards (as close as possible) with another library?
Ray...
|
jbs
newbie
Reged: 11/01/03
Posts: 32
|
|
Ray, as you can see from Steve's post above, the big table approach is indeed a lot slower for individual hand lookups, because memory accesses are really slow. (I posted something similar on the pokersource list.)
The big table approach is never going to work very well for arbitrary evaluation (as opposed to enumeration) for the simple reason that even a single memory access is either slower than Steve's entire evaluator, or if not then very close. Actually I think even Steve's might be improved a little by using tables less.
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi Steve!!
Quote:
h |= ((Hand_T)1 << deck[r]);
Interesting you had this outside the eval routine... The equivelent statement for me is: Code:
h = HR[h + deck[r]];
Please initialize h to 53 before the beginning of the hand loop. The lookup loop for me will be: Code:
handTypeSum[hands[i] >> 12]++;
I think you will find that this might be a little quicker... 
Of course if this isn't a good alternative, put the hand init (quoted code above) into the same for loop in the eval section, I think you will find similar results. I think the for loop inside the eval area is definitely a killer in this...
Either way, even if there really was a speed difference (and I don't think it is as big as it looks), how often would you do a million random hands? If you had to do them, would the half a sec make a big difference? How does your evaluator do for the big enumerations (that at least I seem to always be running)?
What evaluator did you use to do the comparison?
Thank you for the info, definitely had something interesting there...
Ray...
|
TheCardGeek
journeyman
Reged: 07/09/06
Posts: 54
|
|
RayW,
Thanks for your very interesting post. I successfully ported your code into .Net 2.0 (C#) to test it side by side with the Hand Evaluator I am using. I am using a derivative work of poker source port bye Keith Rule (http://www.codeproject.com/csharp/pokerhandevaldoc.asp).
Anyway, on my Pentium M 2.0G I get the following comparison benchmarks using the algorithm in your test method:
Code:
***** PokerOddsCalc.HandEvaluatorTest.Test30_Throughput_HandEvaluator (Keith Rule Hand Evaluator)
Iterations: 369052320 Execution Time: 15000ms Throughput: 24603.488 eps
Code:
***** PokerOddsCalc.HandEvaluatorTest.Test31_Throughput_FiveCardEvaluator_Fast (Paul Senzee's optimization of Cactus Kev Evaluator)
Iterations: 470411760 Execution Time: 15000ms Throughput: 31360.784 eps
Code:
***** PokerOddsCalc.HandEvaluatorTest.Test32_Throughput_HandRanks (RayW HandRank Evaluator port)
Iterations: 2675691200 Execution Time: 15218.75ms Throughput: 175815.438193018 eps
Using the standard nested loop algorithm in all three, your evaluator certainly blows the others away which is excellent for complete equity comparisons.
When I benchmarked using random cards during each iteration the scores reversed themselves with the Poker Src port performing much better than the other two, and yours slowest (But your previous post acknowledges this).
Your algorithm is better suited for the kinds of appications that need this kind of performance, so I'll be implementing it more widely soon.
Mostly, I just wanted to let you know your algorithm is up and running in C#, and its working great!
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi!
This is getting translated like crazy and it is only a day old... Was it easy to move to C#? I gotta say that I love this thread!
Ray...
|
TheCardGeek
journeyman
Reged: 07/09/06
Posts: 54
|
|
Yeah, it was a cinch to port to C#. I imagine Java will be easy.
|
jbs
newbie
Reged: 11/01/03
Posts: 32
|
|
Ray,
Random hands are useful for sampling situations where its impractical to do a full enumeration. While its true that a million hands isn't that much slower in absolute terms, think about what happens with a billion hands or more.
This is not to take anything away from your algorithm. Its a huge improvement for enumeration, just not appropriate for every application. Nothing wrong with that.
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
RayW said,Quote:
Quote:
h |= ((Hand_T)1 << deck[r]);
Interesting you had this outside the eval routine... The equivelent statement for me is: Code:
h = HR[h + deck[r]];
Hmm. I'm not sure we're going to agree on this. For each evaluator, as part of the pre-test initialization I set up 1M hand specifications in the form that the evaluator takes: 7 cards (ints in 1..52) for yours, and a 52-bit string for mine.Quote:
Please initialize h to 53 before the beginning of the hand loop. The lookup loop for me will be: Code:
handTypeSum[hands[i] >> 12]++;
(The init to 53 was done -- it's 6 lines up from the closing brace in my post.) But continuing on the theme... where does the test harness end and the test begin? With respect to your evaluator, I think it has to begin with the first of seven lookups, not the last. With respect to mine, I did what I think is reasonable.Quote:
I think you will find that this might be a little quicker... 
Of course if this isn't a good alternative, put the hand init (quoted code above) into the same for loop in the eval section, I think you will find similar results. I think the for loop inside the eval area is definitely a killer in this...
With this code...Code:
int* h = hands[0]; for (int i = 0; i < 1000000; i++) { int p = HR[53 + *h++]; p = HR[p + *h++]; p = HR[p + *h++]; p = HR[p + *h++]; p = HR[p + *h++]; p = HR[p + *h++]; handTypeSum[HR[p + *h++] >> 12]++; //handTypeSum[(Hand_7_Eval(hands[i]) >> 24)+1]++; count++; }
The result is: Validation seconds = 0.5940 Total HighPrecision Clocks = 1991107088 HighPrecision clocks per lookup = 1991.107088
...i.e., no change in timing.Quote:
Either way, even if there really was a speed difference (and I don't think it is as big as it looks), how often would you do a million random hands? If you had to do them, would the half a sec make a big difference? How does your evaluator do for the big enumerations (that at least I seem to always be running)?
What evaluator did you use to do the comparison?
Sometimes research -- stuff that may run for hours or days -- requires large numbers of evaluations of random hands. (More to the point, I theorize, is large numbers of evaluations with chronologically close references to large data tables that are not part of the evaluator.) My evaluator is, I think, in the same ballpark as the pokersource EvalN routine. Mine is about a dozen years old, with a few interim changes. Its source is available at http://www.stevebrecher.com/Software/software.html. The only reason I used mine as a comparison was that it was a low-RAM alternative that I have handy. My point was only to confirm by experiment mykey1961's claim that the table-lookup eval degrades under some conditions.
Quote:
Thank you for the info, definitely had something interesting there...
Hey, obviously we're all sick enough to love this stuff! But seriously... I think the lesson here is that it's good to have a kit, rather than just one tool.
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi Steve!!
Quote:
I think the lesson here is that it's good to have a kit, rather than just one tool.
You are absolutely right. I know that the code I posted wasn't everything to all people (just good enough for me ) Everything has good and not as good points, and it would help us to find ways to get around them. I really think if we work together, we can come up with a good set of routines that will work for everyone. Perhaps if we integrate this code with pokersource it would be the cat's meow. Pokersource could handle the random evals, and the thread's routine could handle the enumerations (which is the way they are set-up anyway). We could definitely use the pokersource eval routine for the training run...
What do you folks think? Ray...
|
mykey1961
enthusiast
Reged: 10/18/05
Posts: 249
|
|
Quote:
Hi Steve!!
Quote:
I think the lesson here is that it's good to have a kit, rather than just one tool.
You are absolutely right. I know that the code I posted wasn't everything to all people (just good enough for me ) Everything has good and not as good points, and it would help us to find ways to get around them. I really think if we work together, we can come up with a good set of routines that will work for everyone. Perhaps if we integrate this code with pokersource it would be the cat's meow. Pokersource could handle the random evals, and the thread's routine could handle the enumerations (which is the way they are set-up anyway). We could definitely use the pokersource eval routine for the training run...
What do you folks think? Ray...
I tried to look at Pokersource a while back but since I'm not fluent in C, and what I saw appeared to use every trick in the C language I couldn't make heads or tails of it.
If someone can give measure the cycles/hand that pokersource acheives for random hands, we can possibly improve on that method too.
|
TheCardGeek
journeyman
Reged: 07/09/06
Posts: 54
|
|
Quote:
If someone can give measure the cycles/hand that pokersource acheives for random hands, we can possibly improve on that method too.
The Keith Rule evaluator I benchmarked above is a derivative of Poker Source. I think some other posters in the thread posted their timings of poker source also.
In my testing RayW's code performed 752% over the poker source based code for a complete enumeration whereas it trailed by 22% when performing random evaluations.
Code:
***** PokerOddsCalc.HandEvaluatorTest.Test30_Throughput_HandEvaluator (Keith Rule Hand Evaluator)
Iterations: 369052320 Execution Time: 15000ms Throughput: 24603.488 eps
|
New York Jet
addict
Reged: 08/27/04
Posts: 546
Loc: I collect money
|
|
Quote:
I successfully ported your code into .Net 2.0 (C#)...
Could you please post it? Thanks in advance!!!
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
I modified RayW's TestHandRank.cpp to do either random hands or enumeration, each with any of three evaluators.
LOOKUP is the mykey1961 big-table evaluator as implemented by RayW; HANDEVAL is mine; EVALN is the pokersource Eval_N.
RANDOM1M is 1,000,000 random hands; ENUMERATE is all 52c7 hands.
For each evaluator/test, the best of three consecutive runs:
LOOKUP/RANDOM1M: HighPrecision clocks per lookup = 1977.461472
HANDEVAL/RANDOM1M: HighPrecision clocks per lookup = 112.314720
EVALN/RANDOM1M: HighPrecision clocks per lookup = 91.655640
LOOKUP/ENUMERATE: HighPrecision clocks per lookup = 16.126806
HANDEVAL/ENUMERATE: HighPrecision clocks per lookup = 79.533031
EVALN/ENUMERATE: HighPrecision clocks per lookup = 81.567737
The source code is at http://www.stevebrecher.com/misc/TestHandRank.cpp
|
Smelly Cat
stranger
Reged: 01/07/07
Posts: 2
|
|
I have a pure Java 7-card evaluator with 12 million/sec on 2.2GHz Athlon 3500+. Check it out at: http://www.jbridge.net/jimmy
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Just a thought, my evaluator uses the 250MB LUT which is one of it's main downsides, but zipped up it reduces to around 7.5mb due to the large amount of repeating data in it. I know a little about zipping, but don't know how slow it would be to calculate the region you are trying to read in the compressed file. Probably too slow, but it makes a solution that is similar to mine more scalable.
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
Hey guys, I found this thread fascinating. I am not very fluent in C++ so my attempt to convert the code to Java didn't seem to work. Did anyone have an attempt to convert the code to other languages yet to see how it performs.
|
darse
stranger
Reged: 10/06/04
Posts: 6
|
|
For those who haven't achieved full clarity yet, here is a post I made to the Poker Academy forums on the same topic (http://forums.poker-academy.com/viewtopic.php?t=4787).
----
Here is a high-level explanation of the nested table approach, with an example. This is a description of the system I use, which I think is the same as the one in the 2+2 thread (there might be some minor differences, but they should be pretty close since it is the natural thing to do, for some value of "natural").
Imagine six separate tables, corresponding to the number of cards we have currently processed. Each table holds an index value (or a pointer to a particular location) into the next table. It's like a network of pointers, expanding at each stage, but having a branching factor much less than 52 because each set of cards is sorted and reduced to only the essential information.
Let's suppose the first card is the 9h. It is represented as the 1-card hand "9h". The table entry for the 9h contains 52 indices (or pointers) corresponding to the 52 cards in the deck. The pointer for 9h[9h] might point to -1, indicating an error, since a legal hand can't have two 9h.
The second card is the Kc. The hand ID is the full hand sorted by rank and suit, so we currently have the hand "Kc9h". This entry in the second table is pointed to from two places in the first table, namely 9h[Kc] and Kc[9h]. There are 52 out-going pointers for this hand, two of which point to an error state.
The third card is the Kh. Kc9h[Kh] => KhKc9h. There are six different paths through the network leading to this state.
The fourth card is the Qc. KhKc9h[Qc] => KhKcQc9h. We retain the suit information because both hearts and clubs have a potential flush after 7 cards. If this card had been the Qd, then neither diamonds nor clubs could make a potential flush. They would be mapped to a "meta-suit" for no possible flush, which I will call "o" for "offsuit" or "other". Thus, the Qd would have produced KhKc9h[Qd] => KhKoQo9h.
The fifth card is the 2h. KhKcQc9h[2h] => KhKoQo9h2h. Clubs are now irrelevant with respect to possible flushes.
The sixth card is the 5h. KhKoQo9h2h[5h] => KhKoQo9h5h2h. Each entry of the sixth (and largest) table also has 52 out-going pointers, but instead of an index, they contain hand values.
The seventh card is the 7s. KhKoQo9h5h2h[7s] = value(KKQ97) which is 3844. If this had been the 7h, the table entry would contain the value of K9752f, which is 6367.
Note that a 6-card hand with five flush cards can ignore the offsuit card, as it cannot have a bearing on the final value. Thus, the large 6-card table can be reduced in size a little by using a "meta-rank" for "don't care", merging many cases into one. We cannot eliminate the smallest rank of a 6-card flush, however, because it might be part of a straight flush after the seventh card is known.
Note also that the sequential composition of hand IDs uses a lot less memory than more simplistic approaches. This can be further reduced if we dispense with the duplicate cards pointing to an error state, but the indexing gets trickier.
There are two main reasons this method is much faster for systematic enumeration tasks. First, we move most of the work out of the inner-most loops, and do it once instead of repeating it many times. Second, we ensure cache coherence to minimize the number of expensive accesses to main memory. For example, each fetch of an 8K block contains exactly the information that will be needed for the upcoming enumeration cases.
This technique might also be useful for hybrid Monte Carlo simulations over sets of cases, such as all possible river cards for a given 4-card board, or all possible 2-card hands in connection with a random board.
- Darse.
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
I have converted the code to Java but when running it, I am getting an out of bounds exception at one point.
I am not an expert on the differences between c++ and java but I think my conversion of the following line may be incorrect.
Code:
if (suit = holdcards[cardnum] & 0xf) { // find out what suit (if any) was significant mainsuit = suit; // and remember it }
Is suit being assigned to holdcards[cardnum] & 0xf, or being compared to it? Also the problem may lie with the SWAP function. I have created a swap function as follows
Code:
public static void SWAP(int I, int J) { if (workcards[I] < workcards[J]) { workcards[I]^=workcards[J]; workcards[J]^=workcards[I]; workcards[I]^=workcards[J]; } }
but I am pretty sure this can be converted to
Code:
Arrays.sort(workcards);
in java as this should sort the array fine.... I'm lost as to where else c++ differs as my code is compiling fine.
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi!
Quote:
Is suit being assigned to holdcards[cardnum] & 0xf, or being compared to it? Also the problem may lie with the SWAP function.
The holdcards[cardnum] is bitwise anded with the lower 4 bits to get the suit, Then it is assigned to the variable suit. Then if suit is non-zero it will do the mainsuit = suit (the if)
I shouldn't do equalities in IF statements, but it is a weakness of mine...
Out of bounds wouldn't hit you here though... Most likely it is in the MakeID routine, either the sorting or creating the new ID itself. If you can debug and see where the out of bounds error occurs, I might be able to tell you what went wrong... I have a feeling, I seen every out of bounds that could happen in this code! 
Just know if that the Arrays.sort routine will return the psuedo cards in the lower order bits, and that should work, but if you SWAP routine works, isn't that good enough? Keep in mind that the cards will be pretty much in order already, so if the Arrays.sort routine uses qsort, it would be VERY inefficient (worse than a bubble sort).
Ray...
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
Thanks for that Ray. I have changed that line to reflect what it should do (just in more Java friendly terms).
Code:
suit=holdcards[cardnum] & 0xf; if (suit!=0){ // find out what suit (if any) was significant mainsuit = suit; // and remember it }
and the code runs without problems BUT there seems to be a difference between using Array.sort and the swap function as I get the array out of bounds exception.
Code:
public static void SWAP(int I, int J) { if (workcards[I] < workcards[J]) { workcards[I]^=workcards[J]; workcards[J]^=workcards[I]; workcards[I]^=workcards[J]; } }
The out of bounds exception occurs at
Code:
while (high - low > 1) { holdtest = (high + low + 1) / 2; testval = IDs[holdtest] - ID; if (testval > 0) high = holdtest; else if (testval < 0) low = holdtest; else return holdtest; // got it!! } // I guess it couldn't be found so must be added to the current location (high) // make space... // don't expect this much! // OUT OF BOUNDS BELOW, high reaches value 612978 IDs[high] = ID; // do the insert into the hole created numIDs++; return high; }
Its baffling me because there were very few changes needed in your code, mostly the addition of a few !=0 's...
Any ideas where the code is having problems?
Edited by mark007 (01/10/07 01:22 PM)
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi Mark,
I believe the best way to get this working is to put a bit of test code in the MakeID routine to notice when the numcards goes from one level to another and print out the numIDs when it changes. If the count is correct through the 3rd card, but is off on the 4th then you know that it is the grouping function of makeids (or the SWAP or Arrays.sort not working right).
So add just before in MakeID: int needsuited = numcards - 2; Code:
if (oldnumcards > numcards) { printf("numcards = %d\tnumIDs = %d\n", numcards, numIDs); oldnumcards = numcards; // break point here if you need it } compare the numIDs with the numbers that mkey had, that will tell you a lot. I would leave it with the SWAP routine if you have a choice.
Does it blow up with the swap in place?
Ray...
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
Hi Ray,
I have reverted to the old SWAP and it seems to work perfect (verified it was sorting correctly myself).
I did make the mistake of removing the line
Code:
memmove(&IDs[high + 1], &IDs[high], (numIDs - high) * sizeof(IDs[0]));
My conversion from this to java would be
Code:
System.arraycopy(IDs,high,IDs,(high+1),(numIDs - high) );
And all ID's seem to be created now (not sure if they are correct). Java uses the "number of items" when copying from one array to the other, not the amount of data as in C. In this case I got numIDs - high from your code, which seems correct?
The problem is that, now after creating the IDs, when DoEval is ran, after approximately 260000 iterations, I get an out of bounds exception in my eval_7hand routine (which I know for a fact works perfectly).
My eval_7hand sees that the first card it encounters with this ID, has a card value of more than 12 Ill do my best to investigate this but I assume it isn't creating the ID correctly at some point.
Edited by mark007 (01/10/07 05:23 PM)
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
Here is what my java code is outputting so far... I'm sure someone who knows c++ and java inside out could convert between easily 
Getting Card IDs!
ID - 0 ID - 1 ID - 2 ID - 3
. . . . ID - 612973 ID - 612974 ID - 612975 ID - 612976
Setting HandRanks!
ID - 0 ID - 1 ID - 2
. . . . ID - 260528 ID - 260529 ID - 260530 ID - 260531 ID - 260532 ID - 260533 < < OUT OF BOUNDS IN 7 Card Evaluator (value of 13 (max 12) in a card value)
Edit: I just realised, __int64 is not the equilivant of a simple long in java, as java doesn't use unsigned datatypes..yukk
Edited by mark007 (01/10/07 09:42 PM)
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
Code thats blowing up on me so far Code:
static long IDs[] = new long[612978]; static int numIDs = 1; static long maxID = 0; static int numcards = 0; static int maxHR = 0; static int HR[] = new int[32487834]; static int workcards[]; static int oldnumcards=1;
public static void main (String [] args) { doSetup(); }
public static int SaveID(long ID) { if (ID == 0) return 0; // don't use up a record for a 0! if (ID >= maxID) { // take care of the most likely first goes on the end... if (ID > maxID) { // greater than create new else it was the last one! IDs[numIDs++] = ID; // add the new ID maxID = ID; } return numIDs - 1; }
// find the slot I will find it (by a pseudo bsearch algorithm) int low = 0; int high = numIDs - 1; long testval; int holdtest; while (high - low > 1) { holdtest = (high + low + 1) / 2; testval = IDs[holdtest] - ID; if (testval > 0) high = holdtest; else if (testval < 0) low = holdtest; else return holdtest; // got it!! } // I guess it couldn't be found so must be added to the current location (high) // make space... // don't expect this much! System.arraycopy(IDs,high,IDs,(high+1),(numIDs - high) ); IDs[high] = ID; // do the insert into the hole created numIDs++; return high; } public static long MakeID(long IDin, int newcard) // adding a new card to this ID { int suitcount[] = new int[4 + 1]; int rankcount[] = new int[13 + 1]; workcards = new int [8]; // intentially keeping one as a 0 end int cardnum; int getout = 0; long ID = 0; for (cardnum = 0; cardnum < 6; cardnum++) { // can't have more than 6 cards! workcards[cardnum + 1] = (int)((IDin >>> (8 * cardnum)) & 0xff); // leave the 0 hole for new card } // my cards are 2c = 1, 2d = 2 ... As = 52 newcard--; // make 0 based! workcards[0] = (((newcard >>> 2) + 1) << 4) + (newcard & 3) + 1; // add next card formats card to rrrr00ss for (numcards = 0; workcards[numcards]!=0; numcards++) { suitcount[workcards[numcards] & 0xf]++; // need to see if suit is significant rankcount[(workcards[numcards] >>> 4) & 0xf]++; // and rank to be sure we don't have 4! if (numcards!=0) { if (workcards[0] == workcards[numcards]) { // can't have the same card twice getout = 1; // if so need to get out after counting numcards } } } if (getout!=0) { return 0; // duplicated another card (ignore this one) } int needsuited = numcards - 2; // for suit to be significant - need to have n-2 of same suit if (oldnumcards < numcards) { System.out.println("Changing to " + numcards + " " + numIDs); oldnumcards = numcards; // break point here if you need it } if (numcards > 4) { for (int rank = 1; rank < 14; rank++) { if (rankcount[rank] > 4) { // if I have more than 4 of a rank then I shouldn't do this one!! return 0; // can't have more than 4 of a rank so return an ID that can't be! } } } // However in the ID process I prefered that // 2s = 0x21, 3s = 0x31,.... Kc = 0xD4, Ac = 0xE4 // This allows me to sort in Rank then Suit order // if we don't have at least 2 cards of the same suit for 4, we make this card suit 0. if (needsuited > 1) { for (cardnum = 0; cardnum < numcards; cardnum++) { // for each card if (suitcount[workcards[cardnum] & 0xf] < needsuited) { // check suitcount to the number I need to have suits significant workcards[cardnum] &= 0xf0; // if not enough - 0 out the suit - now this suit would be a 0 vs 1-4 } } } // Sort Using XOR. Network for N=7, using Bose-Nelson Algorithm: Thanks to the thread! SWAP(0, 4); SWAP(1, 5); SWAP(2, 6); SWAP(0, 2); SWAP(1, 3); SWAP(4, 6); SWAP(2, 4); SWAP(3, 5); SWAP(0, 1); SWAP(2, 3); SWAP(4, 5); SWAP(1, 4); SWAP(3, 6); SWAP(1, 2); SWAP(3, 4); SWAP(5, 6); // long winded way to put the pieces into a long // cards in bytes --66554433221100 // the resulting ID is a 64 bit value with each card represented by 8 bits. ID = (long) workcards[0] + ((long) workcards[1] << 8) + ((long) workcards[2] << 16) + ((long) workcards[3] << 24) + ((long) workcards[4] << 32) + ((long) workcards[5] << 40) + ((long) workcards[6] << 48); return ID; } public static void SWAP(int I, int J) { if (workcards[I] < workcards[J]) { workcards[I]^=workcards[J]; workcards[J]^=workcards[I]; workcards[I]^=workcards[J]; } } public static int DoEval(long IDin) { // I guess I have some explaining to do here... I used the Cactus Kevs Eval ref http://www.suffecool.net/poker/evaluator.html // I Love the pokersource for speed, but I needed to do some tweaking to get it my way // and Cactus Kevs stuff was easy to tweak ;-) int handrank=9999; int cardnum; int workcard; int rank; int suit=0; int mainsuit = 20; // just something that will never hit... need to eliminate the main suit from the iterator int suititerator = 0; int holdrank=9999; workcards=new int[8]; // intentially keeping one as a 0 end int holdcards[]=new int[8]; int numevalcards = 0; // See Cactus Kevs page for explainations for this type of stuff... final int primes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41 }; if (IDin!=0) { // if I have a good ID then do it... for (cardnum = 0; cardnum < 7; cardnum++) { // convert all 7 cards (0s are ok) holdcards[cardnum] = (int) ((IDin >>> (8 * cardnum)) & 0xff); if (holdcards[cardnum] == 0) break; // once I hit a 0 I know I am done numevalcards++; // if not 0 then count the card if ((suit = holdcards[cardnum] & 0xf) != 0) { // find out what suit (if any) was significant mainsuit = suit; // and remember it } } for (cardnum = 0; cardnum < numevalcards; cardnum++) { // just have numcards... workcard = holdcards[cardnum]; // convert to cactus kevs way!! ref http://www.suffecool.net/poker/evaluator.html // +--------+--------+--------+--------+ // |xxxbbbbb|bbbbbbbb|cdhsrrrr|xxpppppp| // +--------+--------+--------+--------+ // p = prime number of rank (deuce=2,trey=3,four=5,five=7,...,ace=41) // r = rank of card (deuce=0,trey=1,four=2,five=3,...,ace=12) // cdhs = suit of card // b = bit turned on depending on rank of card rank = (workcard >>> 4) - 1; // my rank is top 4 bits 1-13 so convert
suit = workcard & 0xf; // my suit is bottom 4 bits 1-4, order is different, but who cares? if (suit == 0) { // if suit wasn't significant though... suit = suititerator++; // Cactus Kev needs a suit! if (suititerator == 5) // loop through available suits suititerator = 1; if (suit == mainsuit) { // if it was the sigificant suit... Don't want extras!! suit = suititerator++; // skip it if (suititerator == 5) // roll 1-4 suititerator = 1; } } // now make Cactus Keys Card workcards[cardnum] = primes[rank] | (rank << 8) | (1 << (suit + 11)) | (1 << (16 + rank)); } switch (numevalcards) { // run Cactus Keys routines case 5 : holdrank = eval_5cards(workcards[0],workcards[1],workcards[2],workcards[3],workcards[4]); break; // if 6 cards I would like to find HandRank for them // Cactus Key is 1 = highest - 7362 lowest I need to get the min for the permutations case 6 : holdrank = eval_5cards(workcards[0],workcards[1],workcards[2],workcards[3],workcards[4]); holdrank = min(holdrank, eval_5cards(workcards[0],workcards[1],workcards[2],workcards[3],workcards[5])); holdrank = min(holdrank, eval_5cards(workcards[0],workcards[1],workcards[2],workcards[4],workcards[5])); holdrank = min(holdrank, eval_5cards(workcards[0],workcards[1],workcards[3],workcards[4],workcards[5])); holdrank = min(holdrank, eval_5cards(workcards[0],workcards[2],workcards[3],workcards[4],workcards[5])); holdrank = min(holdrank, eval_5cards(workcards[1],workcards[2],workcards[3],workcards[4],workcards[5])); break; case 7 : holdrank = eval_7cards(workcards[0],workcards[1],workcards[2],workcards[3],workcards[4],workcards[5],workcards[6]); break; default : // problem!! shouldn't hit this... System.out.println(" Problem with numcards = " + numcards); break; } // I would like to change the format of Catus Kev's ret value to: // hhhhrrrrrrrrrrrr hhhh = 1 high card -> 9 straight flush // r..r = rank within the above 1 to max of 2861 handrank = 7463 - holdrank; // now the worst hand = 1 if (handrank < 1278) handrank = handrank - 0 + 4096 * 1; // 1277 high card else if (handrank < 4138) handrank = handrank - 1277 + 4096 * 2; // 2860 one pair else if (handrank < 4996) handrank = handrank - 4137 + 4096 * 3; // 858 two pair else if (handrank < 5854) handrank = handrank - 4995 + 4096 * 4; // 858 three-kind else if (handrank < 5864) handrank = handrank - 5853 + 4096 * 5; // 10 straights else if (handrank < 7141) handrank = handrank - 5863 + 4096 * 6; // 1277 flushes else if (handrank < 7297) handrank = handrank - 7140 + 4096 * 7; // 156 full house else if (handrank < 7453) handrank = handrank - 7296 + 4096 * 8; // 156 four-kind else handrank = handrank - 7452 + 4096 * 9; // 10 straight-flushes } return handrank; // now a handrank that I like } public static int min(int a, int b) { if (a<b) return a; else return b; } public static void doSetup() { int count = 0; int card; long ID; int IDslot; int handTypeSum[]=new int[10]; // step through the ID array - always shifting the current ID and adding 52 cards to the end of the array. // when I am at 7 cards put the Hand Rank in!! // stepping through the ID array is perfect!! int IDnum; int holdid; System.out.println("\nGetting Card IDs!\n"); // as this loops through and find new combinations it adds them to the end // I need this list to be stable when I set the handranks (next set) (I do the insertion sort on new IDs these) // so I had to get the IDs first and then set the handranks for (IDnum = 0; IDs[IDnum]!=0 || IDnum == 0; IDnum++) { // start at 1 so I have a zero catching entry (just in case) for (card = 1; card < 53; card++) { // the ids above contain cards upto the current card. Now add a new card ID = MakeID(IDs[IDnum], card); // get the new ID for it if (numcards < 7) holdid = SaveID(ID); // and save it in the list if I am not on the 7th card } System.out.println("\rID - " + IDnum); // just to show the progress -- this will count up to 612976 } System.out.println("\nSetting HandRanks!\n");
// this is as above, but will not be adding anything to the ID list, so it is stable for (IDnum = 0; IDs[IDnum]!=0 || IDnum == 0; IDnum++) { // start at 1 so I have a zero catching entry (just in case) for (card = 1; card < 53; card++) { ID = MakeID(IDs[IDnum], card); if (numcards < 7) IDslot = SaveID(ID) * 53 + 53; // when in the index mode (< 7 cards) get the id to save else IDslot = DoEval(ID); // if I am at the 7th card, get the HandRank to save maxHR = IDnum * 53 + card + 53; // find where to put it HR[maxHR] = IDslot; // and save the pointer to the next card or the handrank } if (numcards == 6 || numcards == 7) { // an extra, If you want to know what the handrank when there is 5 or 6 cards // you can just do HR[u3] or HR[u4] from below code for Handrank of the 5 or 6 card hand HR[IDnum * 53 + 53] = DoEval(IDs[IDnum]); // this puts the above handrank into the array } //if (IDnum%10000==0) System.out.println("\rID - " + IDnum); // just to show the progress -- this will count up to 612976 same as above! } System.out.println("\nNumber IDs = \nmaxHR = " + numIDs + " " + maxHR); // for warm fuzzys
int c0, c1, c2, c3, c4, c5, c6; int u0, u1, u2, u3, u4, u5; //Begin timer, to be done
System.out.println("Starting Test"); for (c0 = 1; c0 < 53; c0++) { u0 = HR[53+c0]; for (c1 = c0+1; c1 < 53; c1++) { u1 = HR[u0+c1]; for (c2 = c1+1; c2 < 53; c2++) { u2 = HR[u1+c2]; for (c3 = c2+1; c3 < 53; c3++) { u3 = HR[u2+c3]; for (c4 = c3+1; c4 < 53; c4++) { u4 = HR[u3+c4]; for (c5 = c4+1; c5 < 53; c5++) { u5 = HR[u4+c5]; for (c6 = c5+1; c6 < 53; c6++) { handTypeSum[HR[u5+c6] >>> 12]++; count++; } } } } } } } //stop timer, to be done for (int i = 0; i <= 9; i++) // display the results System.out.println(handTypeSum[i]); System.out.println("\nTotal Hands = " + count); //file output, to be done }
|
Mogobu The Fool
addict
Reged: 09/10/05
Posts: 617
Loc: On teh internets.
|
|
I'm still loving this thread when I get time to peek at it...
Just as an FYI, you should all feel free to post code, descriptions, etc. over that the overcards.com wiki pages: http://overcards.com/wiki/moin.cgi . The web pages and forums are somewhat dead, what with my retirement from poker, but the wiki pages have a life of their own as a centrum for AHK scripting nuts who play online poker. Feel free to bend the wiki to your own needs to post segments of code to discuss here - the wiki pages will keep an automatic version history of the code changes posted; just peek at the AHK pages for page layout ideas.
|
Brad Quick
stranger
Reged: 01/14/07
Posts: 1
|
|
This thread got me to register for these forums.
This evaluator is great. I got it running in C++ and with it, I'm able to exaustively calculate the chance of every starting hand winning after the flop in .035 seconds. After the flop is the worst case because there are the most combinations. It goes faster for other game stages.
BTW, this is on an Intel MacBook Pro. 
- Brad
|
Mogobu The Fool
addict
Reged: 09/10/05
Posts: 617
Loc: On teh internets.
|
|
Quote:
I'm assuming the only benefit to using the perfect hash version faster lookup creation? (I just used Cactus Kev's source).
No, the perfect hash version's advantage comes from faster actual lookups... The last step in Cactus Kev's approach, after using the product of primes to establish a unique code which represnts any version of a 7-card holding, is to look up that product in a table. In Cactus Kev's code, the lookup is done via binary search. In Senzee's version, doing a hash and lookup is considerable faster on average - resulting in evals about 2.7 times faster in Senzee's tests.
|
Mogobu The Fool
addict
Reged: 09/10/05
Posts: 617
Loc: On teh internets.
|
|
Quote:
I know a little about zipping, but don't know how slow it would be to calculate the region you are trying to read in the compressed file. Probably too slow, but it makes a solution that is similar to mine more scalable.
I don't think that's an option... the way to calculate the location is to decompress the file up to that point. The only way to get by the paging slowdowns is to keep the entire LUT in fast memory... Hence my obsession with finding a compact perfect hash. I'm virtually certain one can find a fast perfect hash that will have a lookup table of 32k entries.
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi!
I have run this code in Java (other than the eval routines which I stubbed out with a return 1). They worked great! I also put in within DoEval (after rank = (workcard >>> 4) - 1;): Code:
// my rank is top 4 bits 1-13 so convert if (rank > 12) { System.out.println(" Problem with rank = " + rank); } Which never was hit... I had to put in the heap memory allocator in: java -Xms130m -Xmx150m HandRankSetup to keep away from Heap errors. You did well doing the conversion... I believe that your code would work as is!
(Pretty speedy too even in Java!)
Ray...
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
Hi Ray,
Yes the java code "works" up until the point where the actual evaluations are done but breaks there. I do not think it is working correctly therefore as I know my old eval functions work given proper input. Any chance you could help us java folk by adding some printf 's in your c code to show, for example, the actual value of each id as it is created, as they seem to be created differently using my c code..
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi Mark,
Another thought...
If you aren't using Cactus Kev's routine to do the evaluation of the hand, you would have a problem with the translation of the cards in the DoEval routine!! Are you using Cactus Kev's Eval routines?
Ray...
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
Yep I am, I assume the problem would lie with the creation of the ID's, thanks alot by the way. I can't wait to see how fast this works in java compared to c. If you have a spare minute you might spend a moment to get your c code to print out the value or maybe even binary representation of the Id's as they are created.
Of course to be removed later when the differences are found.
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi Mark,
I am running your code in Java. It worked fine.... No rank problems as I mentioned (put the rank > 12 code in as above and see if you hit that line). I didn't hit it. Also you need the heap memory increase (I am sure you had that!!). I didn't see anything wrong, and it came through cleanly (including rank). Let me know how you do...
Ray...
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
Thanks Ray, I don't know where I am going wrong so.... I actually had that line in my code and it is never hit either, yet my 7 card evaluator blows up. Ill have a fiddle around but its strange it works fine for you
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
Just before the switch statement where the eval's are done, I added the following lines. This shows that one of the workcards being sent to my evaluator has a rank of >12. I don't know whats going on now...
My code gets the out of bounds first in the 7 card evaluator, and with 7 card evals commented out, the 6 card evaluator goes out of bounds soon afterwards. Code:
for (int cardNo=0; cardNo<5; cardNo++) { if (((workcards[cardNo]>>>8)& 0xF) > 12) { System.out.println("WOOPS, this cards rank was above 12"); System.exit(0); } } switch (numevalcards) { // run Cactus Keys routines case 5 : holdrank = eval_5cards(workcards[0],workcards[1],workcards[2],workcards[3],workcards[4]);
Edited by mark007 (01/14/07 04:07 PM)
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi EVERYONE!!!
I did have a bug in the program in the DoEval Routine!!!
Please change the line at the top of the routine: int suititerator = 0;
to
int suititerator = 1;
This should eliminate your problem with Java... For the C People out there... You should change this line in your code, but it didn't make a difference in the C routines because the only thing that this affected is the 0s for suit coming into the high bit of the rank. Suit didn't matter on these (because of the 0 suit), and if you don't have a suited hand it works off the prime numbers (not the rank >> 8). Interesting hit though, to be safe please rerun your Setup Programs...
Ray...
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
Sweet, that fixed my problems Here is the output and thanks alot for the efforts.
Getting Card IDs! Setting HandRanks! maxHR = 612977 32487833 Training time: 44.813 seconds Starting Test 0 23294460 58627800 31433400 6461620 6180020 4047644 3473184 224848 41584
Total Hands = 133784560 0.783 seconds
Edited by mark007 (01/14/07 06:00 PM)
|
rvg72
STTF Coder Dude
Reged: 06/01/05
Posts: 2342
Loc: Canada
|
|
Anyone ever post the .NET version? I'd love to take a look at that.
rvg
|
thomash
stranger
Reged: 01/17/07
Posts: 3
|
|
I am thinking of not storing an integer hand rank in the table but storing the actual effective hand strength. With effective hand strength I mean the probability that this hand will win the game if it comes to showdown. I start by generating the effective hand strengths for the 7-card combinations and then work my way backwards so I've got tables for 6,5 and 2 cards.
Has anyone tried this so far?
Thomas
|
Grunch
Bounty Hunter
Reged: 08/25/04
Posts: 9623
|
|
Just can't resist... 
My code isn't like any of yours I don't think. My goal wasn't to write a fast evaluator, necesarrily, but to see if I could figure out how to evaluate a 7-card holdem hand with logic & no lookup tables. So that's what I did. Its not fast, but it does what I wanted. Just a different approach.
Code:
#include "stdafx.h" #include "score.h" #include <set> #include <vector>
static const size_t sic = 0, sis = 1, sih = 2, sid = 3; static const size_t ria = 0, rik = 1, riq = 2, rij = 3, rit = 4, ri9 = 5, ri8 = 6, ri7 = 7, ri6 = 8, ri5 = 9, ri4 = 10, ri3 = 11, ri2 = 12;
struct matrix { // running totals of each suit typedef std::pair<int,int> suit_ttl; // first = total cards of this suit, second = high card in this suit suit_ttl suit_ttls[4];
// running totals of each rank typedef int rank_ttl; rank_ttl rank_ttls[13];
// table card matrix struct col { bool suit[4]; bool rank[13]; }; col board[7]; };
void BuildHEMatrix(card cards[7], bool suit[4][7], bool rank[13][7]) { for( int i = 0; i < 7; ++i ) { suit[cards[i].suit][i] = true; rank[cards[i].rank][i] = true; } }
inline handval BuildScore(int mask, int a, int b, int c, int d, int e) { handval ret = a; ret <<= 4; ret |= b; ret <<= 4; ret |= c;
ret <<= 4; ret |= d; ret <<= 4; ret |= e | mask;
return ret; }
handval ScoreHE(const card cards[7]) { // build the card matrix int flush_ttl[4] = {0}; int flush_hicard[4] = {0}; // int flush_locard[4] = {13}; int rank_ttl[13] = {0};
bool card_matrix[13][4] = {false};
for( int i = 0; i < 7; ++i ) { const card& c = cards[i]; card_matrix[c.rank][c.suit] = true;
++flush_ttl[c.suit]; if( c.rank > flush_hicard[c.suit] ) flush_hicard[c.suit] = c.rank; // if( c.rank < flush_locard[c.suit] ) flush_locard[c.suit] = c.rank; ++rank_ttl[c.rank]; }
// look for straight flush for(int s = 0; s < 4; ++s ) { int remain = flush_ttl[s]; int run = 0; int sf_rank = -1;
for( int r = flush_hicard[s]; run < 5 && r <= 12 && (remain+run)>=5; --r, --remain ) { if( card_matrix[r][s] ) { if( ++run == 1 ) sf_rank = r; } else run = 0; }
if( 5 == run ) return BuildScore(hipk_sflush, sf_rank, sf_rank-1, sf_rank-2, sf_rank-3, sf_rank-4 ); // we found a straight flush }
// look for quads, trips, pairs etc int quads_rank = -1, trips_rank = -1, twpr_rank = -1, pr_rank = -1, str_rank = -1, str_run = 0;
for( int r = 12; r >= 0; --r ) { if( quads_rank == -1 && rank_ttl[r] == 4 ) quads_rank = r; if( trips_rank == -1 && rank_ttl[r] == 3 ) trips_rank = r; if( twpr_rank == -1 && rank_ttl[r] == 2 ) { if( pr_rank == -1 ) pr_rank = r; else { if( pr_rank > r ) { twpr_rank = pr_rank; pr_rank = r; } else twpr_rank = r; } } if( str_rank == -1 ) { if( rank_ttl[r] == 0 ) str_run = 0; else if( ++str_run == 5 ) str_rank = r+4; } }
// score quads if( quads_rank > -1 ) { int kick = -1; for( int r = 12; r >= 0 && -1 == kick; --r ) if( rank_ttl[r] > 0 && rank_ttl[r] != 4 ) kick = r; // find kicker return BuildScore(hipk_quads, quads_rank, quads_rank, quads_rank, quads_rank, kick); }
// score boat if( trips_rank != -1 && pr_rank != -1 ) { if( twpr_rank != -1 ) return BuildScore(hipk_boat, trips_rank, trips_rank, trips_rank, twpr_rank, twpr_rank); else return BuildScore(hipk_boat, trips_rank, trips_rank, trips_rank, pr_rank, pr_rank); }
// score flush for( int s = 0; s < 4; ++s ) { if( flush_ttl[s] < 5 ) continue; std::vector<int> fc; for( int r = flush_hicard[s]; r >=0 && fc.size() < 5; --r ) { if( card_matrix[r][s] ) fc.push_back(r); } return BuildScore(hipk_flush, fc[0], fc[1], fc[2], fc[3], fc[4]); }
// score straight if( str_rank != -1 ) return BuildScore(hipk_straight, str_rank, str_rank-1, str_rank-2, str_rank-3, str_rank-4);
// score trips if( trips_rank != -1 ) { std::vector<int> kickers; for( int r = 12; r >= 0 && kickers.size() < 2; --r ) { if( r != trips_rank && rank_ttl[r] > 0 ) kickers.push_back(r); } return BuildScore(hipk_trips, trips_rank, trips_rank, trips_rank, kickers[0], kickers[1]); }
// score two pair if( twpr_rank != -1 ) { int kick = -1; for( int r = 12; r >= 0 && -1 == kick; --r ) if( r != twpr_rank && r != pr_rank && rank_ttl[r] > 0 ) kick = r; // find kicker return BuildScore(hipk_2pr, twpr_rank, twpr_rank, pr_rank, pr_rank, kick); }
// score pair if( pr_rank != -1 ) { std::vector<int> kicks; for( int r = 12; r >= 0 && kicks.size() < 3; --r ) if( rank_ttl[r] == 1 ) kicks.push_back(r); // find 3 kickers return BuildScore(hipk_1pr, pr_rank, pr_rank, kicks[2], kicks[1], kicks[0]); }
// score hi cards std::vector<int> kicks; for( int r = 12; r >= 0 && kicks.size() < 5; --r ) if( rank_ttl[r] > 0 ) kicks.push_back(r); return BuildScore(hipk_hicard, kicks[4], kicks[3], kicks[2], kicks[1], kicks[0]); }
|
Grunch
Bounty Hunter
Reged: 08/25/04
Posts: 9623
|
|
here's a test harness:
Code:
/*** John Dibling johndotdiblingatsungarddotcom
***/
#include "stdafx.h" #include "score.h" #include <algorithm> #include <time.h> #include <string> #include <iostream> //#include "x:\utils\stlext\stringext.h" #include <string> #include <vector> #include <algorithm> #include <list> #include <cstdarg> #include <ctype.h>
/***
NOTE : The following functions in namespace std are from my STL Extensions library. Only the necesarry code has been copied out of the STXEXT library and pasted here.
***/
namespace std { /* ---
Formatted Print
template<class C> int strprintf(basic_string<C>* pString, const C* pFmt, ...);
template<class C> int vstrprintf(basic_string<C>* pString, const C* pFmt, va_list args);
Returns :
# characters printed to output
Effects :
Writes formatted data to a string. strprintf() works exactly the same as sprintf(); see your documentation for sprintf() for details of peration. vstrprintf() also works the same as sprintf(), but instead of accepting a variable paramater list it accepts a va_list argument.
Requires :
pString is a pointer to a basic_string<>
--- */ inline int vstrprintf(string* pString, const char* pFmt, va_list args) { // prologue static const size_t ChunkSize = 1024; int retval = 0; // get local work buffer size_t nBufSize = 0; char* pBuf = 0; // format up string int i = -1; for( ; i == -1; ) { // realloc local buffer if( pBuf ) delete pBuf; pBuf = new char[nBufSize+=ChunkSize]; // try to sprintf i = _vsnprintf(pBuf,nBufSize,pFmt,args); } retval = i; // epilogue pString->assign(pBuf,retval); delete pBuf; return retval; };
/* ---
Inline Formatted Print
string strprintf(const char* Format, ...);
Returns :
Formatted string
Effects :
Writes formatted data to a string. formatstr() works the same as sprintf(); see your documentation for sprintf() for details of operation.
--- */ std::string formatstr(const char * fmt, ...) { std::string ret;
va_list args; va_start(args, fmt); int retval = vstrprintf(&ret, fmt, args); va_end(args); return ret; }
}; // namespace std
void deal_cards(card * deck, size_t n) { static bool seeded = false; if( !seeded ) { srand((unsigned)time(0)); seeded = true; }
for( size_t i = 0; i < n; ++i ) { for( bool cont = true; cont; ) { deck[i].rank = (cardrank)int(((double)rand()/(double)RAND_MAX) * 12); deck[i].suit = (cardsuit)int( 3*((double)rand()/(double)RAND_MAX) ); cont = &deck[i] != std::find(&deck[0],&deck[i],deck[i]); } } }
std::string card_abbrev(const card& c, bool incl_suit) { std::string ret; static const char suitc[] = "cshd"; static const char rankc[] = "23456789TJQKA"; ret += rankc[c.rank]; if( incl_suit ) ret += suitc[c.suit]; return ret; }
std::string print_hand(card* first, card* last) { std::string ret; for( card* it = first; it != last; ++it ) { if( it != first ) ret += " "; ret += card_abbrev(*it,true); } return ret; }
std::string rank_name(int rank) { switch( rank ) { case 12 : return "Ace"; case 11 : return "King"; case 10 : return "Queen"; case 9 : return "Jack"; case 8 : return "Ten"; case 7 : return "Nine"; case 6 : return "Eight"; case 5 : return "Seven"; case 4 : return "Six"; case 3 : return "Five"; case 2 : return "Four"; case 1 : return "Trey"; case 0 : return "Deuce"; default : return "Bug"; } }
std::string suit_name(cardsuit s) { switch( s ) { case club : return "Clubs"; case spade : return "Spades"; case diamond : return "Diamonds"; case heart : return "Hearts"; default : return "Wands"; } }
std::string card_name(card c) { return std::formatstr("%s of %s", rank_name(c.rank).c_str(), suit_name(c.suit).c_str()); }
std::string card_name(int r, cardsuit s) { return std::formatstr("%s of %s", rank_name(r).c_str(), suit_name(s).c_str()); }
std::string score2string(const handval& val) { switch( val & 0xF0000000 ) { case hipk_sflush : if( (0xf & (val >> 16)) == 12 ) return "Royal Flush"; else return std::formatstr("Straight Flush, %s High", rank_name((0xf & (val >> 16))).c_str()); case hipk_quads: return std::formatstr("Four of a Kind, %ss (%s Kicker)", rank_name((0xf & (val >> 16))).c_str(), rank_name((0xf & val)).c_str()); case hipk_boat : return std::formatstr("Full House, %ss Full of %ss", rank_name((0xf & (val >> 16))).c_str(), rank_name((0xf & val)).c_str()); case hipk_flush : return std::formatstr("Flush, %s-high", rank_name((0xf & (val >> 16))).c_str()); case hipk_straight : return std::formatstr("Straight, %s-high", rank_name((0xf & (val >> 16))).c_str()); case hipk_trips : return std::formatstr("Three of a Kind, %ss (%s-%s Kickers)", rank_name((0xf & (val >> 16))).c_str(), rank_name((0xf & (val >> 4))).c_str(), rank_name((0xf & val)).c_str()); case hipk_2pr : return std::formatstr("Two Pair, %ss over %ss (%s Kicker)", rank_name((0xf & (val >> 16))).c_str(), rank_name((0xf & (val >> 8))).c_str(), rank_name((0xf & val)).c_str()); case hipk_1pr : return std::formatstr("One Pair, %ss (%s-%s-%s Kickers)", rank_name((0xf & (val >> 16))).c_str(), rank_name((0xf & (val >> 8))).c_str(), rank_name((0xf & (val >> 4))).c_str(), rank_name((0xf & val)).c_str()); case hipk_hicard : return std::formatstr("High Card, %s-%s-%s-%s-%s", rank_name((0xf & (val >> 16))).c_str(), rank_name((0xf & (val >> 12))).c_str(), rank_name((0xf & (val >> 8))).c_str(), rank_name((0xf & (val >> 4))).c_str(), rank_name((0xf & val)).c_str());
default : return "Something Wierd!"; } }
struct deal_block { card deal[7]; deal_block(card in[7]) { std::copy(&in[0], &in[7], &deal[0]); } };
int _tmain(int argc, _TCHAR* argv[]) { card exp[] = { {club,7}, {club,6}, {club,5}, {club,4}, {club,3}, {club,2}, {club,1}, // sf {club,7}, {spade,10}, {club,5}, {heart,10}, {diamond,10}, {club,2}, {club,10},// quads {heart,12}, {heart,11}, {diamond,11}, {diamond,12}, {spade,12}, {club,3}, {club,2}, // full house {club,7}, {club,6}, {club,9}, {club,4}, {club,3}, {club,2}, {club,1},// flush {club,7}, {heart,6}, {spade,5}, {diamond,4}, {club,3}, {club,2}, {club,1},// straight {heart,12}, {heart,11}, {diamond,11}, {diamond,11}, {spade,9}, {club,3}, {club,2},// trips {heart,12}, {heart,11}, {diamond,11}, {diamond,12}, {spade,9}, {club,3}, {club,2},//2pair {heart,12}, {heart,11}, {diamond,10}, {diamond,9}, {spade,7}, {club,3}, {club,2},// pair {heart,12}, {heart,11}, {diamond,10}, {diamond,9}, {spade,7}, {club,3}, {club,2}, // high card };
size_t deals = (sizeof(exp)/sizeof(exp[0]))/7; for( size_t i = 0; i < deals; ++i ) { size_t ii = 7*i; handval hv = ScoreHE(&exp[ii]); std::cout << print_hand(&exp[ii], &exp[ii+7]).c_str() << " -- " << score2string(hv).c_str() << std::endl; }
for( size_t i = 0; i < deals; ++i ) { size_t ii = 7*i; card shuffled[7]; std::copy(&exp[ii], &exp[ii+7], shuffled); std::random_shuffle(&shuffled[0], &shuffled[7]); handval hv = ScoreHE(shuffled); std::cout << print_hand(shuffled, &shuffled[7]).c_str() << " -- " << score2string(hv).c_str() << std::endl; } for( int i = 0; i < 10; ++i ) { card a[7]; size_t na = sizeof(a)/sizeof(a[0]); deal_cards(a,na); std::cout << "[" << i+1 << "] : "; for( size_t j = 0; j < na; ++j ) std::cout << card_abbrev(a[j],true).c_str() << " "; std::cout << std::endl; ScoreHE(a); }
// time 1 million scores #ifdef _DEBUG const int trials = 200; #else const int trials = 10000000; #endif std::cout << "Dealing " << trials << " games..." << std::endl; std::vector<deal_block> bigdeal; bigdeal.reserve(trials); card deal[7]; for( int i = 1; i <= trials; ++i ) { if( i%(trials/10) == 0 ) std::cout << "."; deal_cards(deal,7); bigdeal.push_back(deal); }
std::cout << std::endl << "Scoring games..." << std::endl;
unsigned long results[9] = {0}; time_t start = time(0); handval hv; for( int i = 0; i < trials; ++i ) { if( i%(trials/10) == 0 ) std::cout << "."; hv = ScoreHE(bigdeal.at(i).deal); ++results[hv>>28]; } time_t end = time(0); std::cout << std::endl << "Elapsed Time: " << (long)end-start; if( (end-start)>0 ) std::cout << ", Scores per second: " << trials/((long)end-start); std::cout << std::endl << "Results:" << std::endl << "S-Flushes: " << results[8] << "\t (" << 100*results[8]/trials << "%)" << std::endl << "Quads: " << results[7] << "\t (" << 100*results[7]/trials << "%)" << std::endl << "Full Boats: " << results[6] << "\t (" << 100*results[6]/trials << "%)" << std::endl << "Flushes: " << results[5] << "\t (" << 100*results[5]/trials << "%)" << std::endl << "Straights: " << results[4] << "\t (" << 100*results[4]/trials << "%)" << std::endl << "Trips: " << results[3] << "\t (" << 100*results[3]/trials << "%)" << std::endl << "2-Pairs: " << results[2] << "\t (" << 100*results[2]/trials << "%)" << std::endl << "1-Pairs: " << results[1] << "\t (" << 100*results[1]/trials << "%)" << std::endl << "Hi-Cards: " << results[0] << "\t (" << 100*results[0]/trials << "%)" << std::endl;
return 0; }
|
mark007
stranger
Reged: 01/08/07
Posts: 12
|
|
Hand Strength or probability that your hand is ahead / behind depends on the other players cards, aswell as how many players you are against. Even if you just compare your hand versus x random hands, the number x (players playing) has a massive impact on the probabilities so I don't think what you are thinking of doing thomash is possible in this sense.
|
thomash
stranger
Reged: 01/17/07
Posts: 3
|
|
I am building a bot for two player games only at the moment. So the multiple players aren't an obstacle.
If I did have multiple opponents couldn't I just take the probability to the power of how many players there are and still have fairly good estimate?
Thomas
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
That would be a reasonable estimate, but because the opponents hands are independant of each other the numbers will not be exact.
|
thomash
stranger
Reged: 01/17/07
Posts: 3
|
|
I assume you mean because opponents hands are NOT independent of each other the numbers will not be exact.
Thomas
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Sorry, my mistake, you are right, because they are not independant it means powering will be incorrect.
I am hopefully going to run some exhaustive analysis on 2 opponents to see exactly how inaccurate powering down will be, although that will entail:
(50 choose 5) * (45 choose 2) * (43 choose 2) = 1,894,107,877,200
game simulations (1.8 trillion), so would take a reasonable amount of time to compute on a desktop PC!
|
chrisj1948
stranger
Reged: 01/19/07
Posts: 2
Loc: UK
|
|
The error introduced by exponentiating from a win probability against a single opponent to generate a probability against several gets even worse if you have set your opponents on a limited range of cards
Just as a quick example. Where the opponents had been set to have handranks in the top 25% a hand which gives you a 30% chance of winning against one opponent drops to 17% against 3 when using a Monte Carlo iterator for the calculation, but is about 3% if you exponentiate.
Regards Chris
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
I ported the table-lookup evaluator introduced in this thread to Java. Below are timings for the C and Java versions for it ("Loookup"); C and Java timings for HandEval, my evaluator; and C timings for Eval_N from pokersource. These results are from a Pentium 4 3.4G processor. The Java runs used Sun's JVM 6; where the -server JVM switch improved the timing it is noted. The Java source and class files are available at http://www.stevebrecher.com/misc/TestEvaluators.jar
Code:
1,000,000 pre-generated random hands, same for each evaluator (but different hands [different RNGs] for C and Java): C: HandEval: 0.0310 sec., 108 CPU cycles/hand Lookup: 0.5780 sec., 1958 CPU cycles/hand Eval_N 0.0310 sec., 92 CPU cycles/hand Java: HandEval: 0.0927 sec. (single-threaded) HandEval: 0.0924 sec. (dual-threaded, with -server) Lookup: 0.6097 sec. (single-threaded) Lookup: 0.4271 sec. (dual-threaded)
52c7 enumeration: C: HandEval: 3.2810 sec., 83 CPU cycles/hand Lookup: 0.6410 sec., 16 CPU cycles/hand Eval_N: 3.2030 sec., 81 CPU cycles/hand Java: HandEval: 5.5252 sec. (single-threaded, with -server) HandEval: 6.3874 sec. (dual-threaded, with -server) Lookup: 0.8572 sec. (single-threaded) Lookup: 1.1239 sec. (dual-threaded, with -server)
|
Jurrr
Carpal \'Tunnel
Reged: 12/18/06
Posts: 2715
|
|
Steve, very much appreciated. Does it work faster than your "old" one?
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
Quote:
Steve, very much appreciated. Does it work faster than your "old" one?
Which? The C/Java HandEval? The C version is about 12% faster than the Dec 2006 version than is on my web site; I'll update the latter by and by. The Java version hasn't changed.
Edit, P.S. The 12% is for a particular Hold 'Em Showdown scenario that I've been using for benchmarking.
Edited by Steve Brecher (01/23/07 06:21 PM)
|
theblitz
Pooh-Bah
Reged: 09/07/04
Posts: 1920
Loc: Israel
|
|
Steve, I wanted to send you a PM because I need to ask you something about your calculator and would rather not post here.
How can I get in contact with you?
|
ThirdEye
stranger
Reged: 08/18/06
Posts: 8
|
|
wow, i just spent two hours working myself through this excellent thread.
i'm working on some pokertools myself using the c# poker-eval port from keith and always thought its "fast enough". the magnitude of improvement you guys worked out here is really amazing.
two thumbs up... great work
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Made my full lookup table version. Here's the performance:
Using the following interation method:
date1 = Now
For c1 = 1 To 46 For c2 = c1 + 1 To 47 For c3 = c2 + 1 To 48 For c4 = c3 + 1 To 49 For c5 = c4 + 1 To 50 For c6 = c5 + 1 To 51 For c7 = c6 + 1 To 52
rank = HR(HR(HR(HR(c1 * 140660 - c2 * 2704 + c3 * 52 - c4) + c5) + c6) + c7)
Next c7 Next c6 Next c5 Next c4 Next c3 Next c2 Next c1
date2 = Now
'validate is global variable, outputed to screen 'prevents loop from being optimized out
validate1 = rank
Using the above iteration method takes .515625 seconds, or about 260 million hands a sec. or 3.85e-9 hand per second.
Using a different iteration method....i call the "cheating algorithm" (although its the one used by the latest series of posters) it gets faster speeds by solving part of the lookup process after each for loop (thus lumping together analysis)
date1 = Now
For c1 = 1 To 46 t1 = c1 * 140660 For c2 = c1 + 1 To 47 t2 = t1 - c2 * 2704 For c3 = c2 + 1 To 48 t3 = t2 + c3 * 52 For c4 = c3 + 1 To 49 t4 = HR(t3 - c4) For c5 = c4 + 1 To 50 t5 = HR(t4 + c5) For c6 = c5 + 1 To 51 t6 = HR(t5 + c6) For c7 = c6 + 1 To 52
rank = HR(t6 + c7) Next c7 Next c6 Next c5 Next c4 Next c3 Next c2 Next c1
date2 = Now
'validate is global variable, outputed to screen 'prevents loop from being optimized out
validate1 = rank
Using the above iteration method takes .390625 seconds, or about 343 million hands a sec. or 2.91e-9 hand per second.
Some things i did differently 1) used visual basic (studio, 2005, professional, .net 2.0)
2) Replaced three tables with integer math. The integer math was faster than looking up variables, table values, and adding results together. More tables can be replaced with integer math, but the memory requirements increase exponentially. (7mb to replace three, 360 mb to replace4, etc.)
3) Put everything into one table. Optimizer went crazy when i did this. Perhaps it didn't have to repeatedly lookup base memory addresses for the array.
Room to improve:
Pointers might be faster. Instead of retrieving a value, and re-inputing this value into the array to get a new memory adress, the table could be optimized so that that values are the new memory address. The relative position of data does not change in an array, and here we have all the data stored in one array.
Downside: Visual basic doesn't let me use pointers. I'm still learning file I/O and some other stuff in C++ so it may be a while before i can try this. But i got pointers down.
P.S. Great job everyone (pats on back), we got some super fast evalutors. Now whose up for starting a game theory AI thread to put these evalutors to use?
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi!
Nice Results! Just a quick question about the timings. If I run my optimizer with loops just like this, I get a totally unrealistic speed. I think my optimizer figures out that rank isn't used so why go through all the work. If you put the hand type counter in and display the results, what are your timings? What type/speed computer are you running on? I have a feeling that it would slow down a little bit... It would be interesting to see what your code for VB.Net looks like. How do you feel about showing it to the rest of the world? Personally, I think I will stick with my C code (for now at least!)
Ray
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
It doesn't get optimized out...i verified that (made that mistake before). Its why i use validate, because it has to use the last rank in the cycle to pass to validate, a global variable, which is outputted to screen. Using a counter, a trivial counter the difference was .51 to .61. The discrepency is that the counter is updated every iteration.
Sure i'll post what i got. Comments in this thread have been valuable, so i don't mind sharing.... I'll post it next msg.
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
The following is Post 1 of 2 of my code. This is the current run-time code. The code to make the tables, etc, that I used will be posted next.
Quote:
Public Class Main
Dim date1 As Date Dim date2 As Date Dim validate1 As Int32
Private Sub evaluate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles evaluate.Click
Dim interval As TimeSpan
Call determine_winner() 'normal algorithm 'Call determine_winner2() 'cheating algorithm
interval = date2 - date1
output.Text += " " + CStr(interval.TotalSeconds) + " " + validate1.ToString
End Sub
Quote:
Private Sub determine_winner()
Dim HR(37976331) As Int32 Dim c1, c2, c3, c4, c5, c6, c7, rank As Int32 Dim h1, h2, h3, h4, h5, h6, h7, h8, h9 As Int32
FileOpen(6, "C:\Program Files\DevStudio\VB\Poker5\HR.bin", OpenMode.Binary) FileGet(6, HR, 5) FileClose(6)
date1 = Now
For c1 = 1 To 46 For c2 = c1 + 1 To 47 For c3 = c2 + 1 To 48 For c4 = c3 + 1 To 49 For c5 = c4 + 1 To 50 For c6 = c5 + 1 To 51 For c7 = c6 + 1 To 52
rank = HR(HR(HR(HR(c1 * 140660 - c2 * 2704 + c3 * 52 - c4) + c5) + c6) + c7)
Select Case rank Case 0 To 3000 h1 += 1
Case 3001 To 7000 h2 += 1 Case 7001 To 9000 h3 += 1 Case 9001 To 11000 h4 += 1 Case 11001 To 12000 h5 += 1 Case 12001 To 15000 h6 += 1 Case 15001 To 16000 h7 += 1 Case 16001 To 17000 h8 += 1 Case Else h9 += 1 End Select
Next c7 Next c6 Next c5 Next c4 Next c3 Next c2 Next c1
date2 = Now
'validate is global variable, outputed to screen 'prevents loop from being optimized out
validate1 = h1 + h2 + h3 + h4 + h5 + h6 + h7 + h8 + h9
'Comparison data, check for accuracy '23294460: hi card '58627800: one pair '31433400: 2 pair '6461620: three kind '6180020: straight '4047644: flush '3473184: full house '224848: four kind '41584: straight flush
End Sub
Quote:
Private Sub determine_winner2()
Dim HR(37976331) As Int32 Dim c1, c2, c3, c4, c5, c6, c7, rank As Int32 Dim h1, h2, h3, h4, h5, h6, h7, h8, h9 As Int32 Dim t1, t2, t3, t4, t5, t6 As Int32
'Dim HR(37976331) As Int32
FileOpen(6, "C:\Program Files\DevStudio\VB\Poker5\HR.bin", OpenMode.Binary) FileGet(6, HR, 5) FileClose(6)
date1 = Now
For c1 = 1 To 46 t1 = c1 * 140660 For c2 = c1 + 1 To 47 t2 = t1 - c2 * 2704 For c3 = c2 + 1 To 48 t3 = t2 + c3 * 52 For c4 = c3 + 1 To 49 t4 = HR(t3 - c4) For c5 = c4 + 1 To 50 t5 = HR(t4 + c5) For c6 = c5 + 1 To 51 t6 = HR(t5 + c6) For c7 = c6 + 1 To 52
rank = HR(t6 + c7) Select Case rank Case 0 To 3000 h1 += 1 Case 3001 To 7000 h2 += 1 Case 7001 To 9000 h3 += 1 Case 9001 To 11000 h4 += 1 Case 11001 To 12000 h5 += 1 Case 12001 To 15000 h6 += 1 Case 15001 To 16000 h7 += 1 Case 16001 To 17000 h8 += 1 Case Else h9 += 1 End Select
Next c7 Next c6 Next c5 Next c4 Next c3 Next c2 Next c1
date2 = Now
'validate is global variable, outputed to screen 'prevents loop from being optimized out
'validate1 = rank validate1 = h1 + h2 + h3 + h4 + h5 + h6 + h7 + h8 + h9
'Comparison data, check for accuracy '23294460: hi card '58627800: one pair '31433400: 2 pair '6461620: three kind '6180020: straight '4047644: flush '3473184: full house '224848: four kind '41584: straight flush
End Sub End Class
|
_D&L_
member
Reged: 05/11/06
Posts: 128
|
|
Ok, I can't provide all the necessary source code to make the tables. Problem is my iterative process of design. I used my old hand evaluator to process results to create my new one. And the code to make old evalutor is burried in old builds, and is an extremely confusing network of functions. That being said...i'll show u the design process I used. If you have access to any sort of 7 card evaluator, u can sub that for mine, and it may work (u may have to adjust for how card numbers & suits are represnted).
Private Sub Create_tables()
'this is the main function to create all the tables
Dim table1(7314320), table1A(7314320), table2(), table2A(), table2B() As Int32 Dim table3(), table3A(), table3B(), table4(), table4A() As Int32 Dim HR() As Int32
Call Table1_SP(table1A) 'Table1_Starting Points
'New arrays are initalized big, then redimmed to size needed
table2A = New Int32(10000000) {} Call Table2_UI(table2A) 'Table2_Unique Indexes
table3A = New Int32(10000000) {} Call Table3_UI(table3A) 'Table2_Unique Index)es
'Finds starting position in table in table 2A*52 = SP in table 2 Table1_main(table1A, table2A, table1)
table2B = New Int32(table2A.GetUpperBound(0) * 52 + 52) {} Table2_UI_Four_to_five(table2A, table2B)
table2 = New Int32(table2B.GetUpperBound(0)) {} Table2_Main(table2, table2B, table3A)
table3B = New Int32(table3A.GetUpperBound(0) * 52 + 52) {} Table3_UI_five_to_six(table3A, table3B)
table4A = New Int32(10000000) {} Call Table4A_UI(table3B, table4A)
table3 = New Int32(table3B.GetUpperBound(0)) {} Table3_Main(table3, table3B, table4A)
table4 = New Int32(table4A.GetUpperBound(0) * 52 + 52) {} Table4_main(table4, table4A)
HR = New Int32() {} Call supertable(HR, table1, table2, table3, table4)
End Sub
Private Sub Table1_SP(ByRef table1A() As Int32)
Dim c1, c2, c3, c4 As Int32 'counters Dim Index As Int32 Dim OC(3) As Int32
For c1 = 0 To table1A.GetUpperBound(0) table1A(c1) = -1 Next c1
For c1 = 1 To 52 For c2 = 1 To 52 For c3 = 1 To 52 For c4 = 1 To 52 Select Case c1 Case c2, c3, c4 Continue For End Select
Select Case c2 Case c3, c4 Continue For End Select
Select Case c3 Case c4 Continue For End Select
Index = c1 * 140660 - c2 * 2704 + c3 * 52 - c4
table1A(Index) = FourCard_Lexograph(New Int32() {c1, c2, c3, c4})
Next c4 Next c3 Next c2 Next c1
FileOpen(7, "C:\Program Files\DevStudio\VB\Poker5\table1A.bin", OpenMode.Binary) FilePut(7, table1A, 1) FileClose(7)
End Sub
Private Sub Table1_main(ByRef table1A() As Int32, ByRef table2A() As Int32, ByRef Table1() As Int32) Dim c1 As Int32 Dim length As Int32 Dim value As Int32
For c1 = 0 To Table1.GetUpperBound(0) Table1(c1) = -1 Next
'put starting point into table 1 'Link 4 card UIs in table1 and table2, with table 2's index
length = table2A.GetUpperBound(0) For c1 = 0 To table1A.GetUpperBound(0) value = table1A(c1) If value = -1 Then Continue For Table1(c1) = Search_Array(table2A, length, value) * 52 Next c1
FileOpen(7, "C:\Program Files\DevStudio\VB\Poker5\table1.bin", OpenMode.Binary) FilePut(7, Table1, 1) FileClose(7)
End Sub Private Sub Table2_UI(ByRef Table2A() As Int32)
Dim c1, c2, c3, c4, c5 As Int32 'counters Dim value As Int32
'''''''' Code to make table 2A (unique Indexes) '''''''' ''' Generate all four card UIs
For c1 = 0 To Table2A.GetUpperBound(0) Table2A(c1) = -1 Next c1
For c1 = 1 To 49 For c2 = c1 + 1 To 50 For c3 = c2 + 1 To 51 For c4 = c3 + 1 To 52
value = FourCard_Lexograph(New Int32() {c1, c2, c3, c4})
For c5 = 0 To Table2A.GetUpperBound(0) Select Case Table2A(c5) Case value Exit For Case -1 Table2A(c5) = value Exit For End Select Next c5 Next c4 Next c3 Next c2 Next c1
Array.Sort(Table2A) Array.Reverse(Table2A)
For c1 = Table2A.GetUpperBound(0) To 0 Step -1 Select Case Table2A(c1) Case -1 Continue For Case Else ReDim Preserve Table2A(c1) Exit For End Select Next
Array.Reverse(Table2A)
FileOpen(7, "C:\Program Files\DevStudio\VB\Poker5\table2A.bin", OpenMode.Binary) FilePut(7, c1, 1) FilePut(7, Table2A, 5) FileClose(7)
End Sub
Private Sub Table2_UI_Four_to_five(ByRef table2A() As Int32, ByRef table2B() As Int32) ''''''Fan out 2As-4UIs, with 5UIs in table 2
Dim c1, c2, h1, h2, h3, h4 As Int32 Dim value As Int32
For c1 = 0 To table2B.GetUpperBound(0) table2B(c1) = -1 Next
For c1 = 0 To table2A.GetUpperBound(0) value = table2A(c1)
h1 = (value - 1) \ 283140 value -= h1 * 283140
h2 = (value - 1) \ 4290 value -= h2 * 4290
h3 = (value - 1) \ 65 value -= h3 * 65
h4 = value
For c2 = 1 To 52 Select Case c2 Case h1, h2, h3, h4 Continue For Case Else table2B(c1 * 52 + c2) = FiveCard_Lexograph(New Int32() {h1, h2, h3, h4, c2}) End Select Next c2 Next c1
c1 = table2A.GetUpperBound(0) * 52 + 52
FileOpen(7, "C:\Program Files\DevStudio\VB\Poker5\table2B.bin", OpenMode.Binary) FilePut(7, c1, 1) FilePut(7, table2B, 5) FileClose(7) End Sub
Private Sub Table2_Main(ByRef Table2() As Int32, ByRef Table2B() As Int32, ByRef table3A() As Int32) Dim c1 As Int32 Dim length, value As Int32
For c1 = 0 To Table2.GetUpperBound(0) Table2(c1) = -1 Next
'update table2 length = table3A.GetUpperBound(0)
For c1 = 0 To Table2B.GetUpperBound(0) If Table2B(c1) = -1 Then Continue For value = Table2B(c1) Table2(c1) = Search_Array(table3A, length, value) * 52 Next c1
length = Table2.GetUpperBound(0)
FileOpen(7, "C:\Program Files\DevStudio\VB\Poker5\table2.bin", OpenMode.Binary) FilePut(7, length, 1) FilePut(7, Table2, 5) FileClose(7)
End Sub
Private Sub Table3_UI(ByRef Table3A() As Int32) Dim c1, c2, c3, c4, c5, c6 As Int32 'counters Dim value As Int32
For c1 = 0 To Table3A.GetUpperBound(0) Table3A(c1) = -1 Next c1
''''''' Code to make table 3A - Five Card UIs ''''''''
For c1 = 1 To 48 For c2 = c1 + 1 To 49 For c3 = c2 + 1 To 50 For c4 = c3 + 1 To 51 For c5 = c4 + 1 To 52
value = FiveCard_Lexograph(New Int32() {c1, c2, c3, c4, c5})
For c6 = 0 To Table3A.GetUpperBound(0) Select Case Table3A(c6) Case value Exit For Case -1 Table3A(c6) = value Exit For End Select Next c6 Next c5 Next c4 Next c3 Next c2 Next c1
Array.Sort(Table3A) Array.Reverse(Table3A)
For c1 = Table3A.GetUpperBound(0) To 0 Step -1 Select Case Table3A(c1) Case -1 Continue For Case Else ReDim Preserve Table3A(c1) Exit For End Select Next
Array.Reverse(Table3A)
FileOpen(7, "C:\Program Files\DevStudio\VB\Poker5\table3A.bin", OpenMode.Binary) FilePut(7, c1, 1) FilePut(7, Table3A, 5) FileClose(7)
End Sub
Private Sub Table3_UI_five_to_six(ByRef table3A() As Int32, ByRef table3B() As Int32) Dim c1, c2, h1, h2, h3, h4, h5 As Int32 Dim value As Int32
For c1 = 0 To table3B.GetUpperBound(0) table3B(c1) = -1 Next c1
For c1 = 0 To table3A.GetUpperBound(0) value = table3A(c1)
h1 = (value - 1) \ 18687240 value -= h1 * 18687240
h2 = (value - 1) \ 283140 value -= h2 * 283140
h3 = (value - 1) \ 4290 value -= h3 * 4290
h4 = (value - 1) \ 65 value -= h4 * 65
h5 = value
For c2 = 1 To 52 Select Case c2 Case h1, h2, h3, h4, h5 Continue For Case Else table3B(c1 * 52 + c2) = SixCard_Lexograph(New Int32() {h1, h2, h3, h4, h5, c2}) End Select Next c2 Next c1
c1 = table3A.GetUpperBound(0) * 52 + 52
FileOpen(7, "C:\Program Files\DevStudio\VB\Poker5\table3B.bin", OpenMode.Binary) FilePut(7, c1, 1) FilePut(7, table3B, 5) FileClose(7)
End Sub
Private Sub Table3_Main(ByRef table3() As Int32, ByRef table3B() As Int32, ByRef table4A() As Int32) Dim c1 As Int32 Dim length, value As Int32
For c1 = 0 To table3.GetUpperBound(0) table3(c1) = -1 Next
'update table2 length = table4A.GetUpperBound(0)
For c1 = 0 To table3B.GetUpperBound(0) If table3B(c1) = -1 Then Continue For value = table3B(c1) table3(c1) = Search_Array(table4A, length, value) * 52 Next c1
length = table3.GetUpperBound(0)
FileOpen(7, "C:\Program Files\DevStudio\VB\Poker5\table3.bin", OpenMode.Binary) FilePut(7, length, 1) FilePut(7, table3, 5) FileClose(7)
End Sub
Private Sub Table4A_UI(ByRef table3B() As Int32, ByRef table4A() As Int32) Dim c1, c2, value, length As Int32
For c1 = 0 To table4A.GetUpperBound(0) table4A(c1) = -1 Next c1
c1 = 0 length = table3B.GetUpperBound(0) + 1
For c1 = 0 To table3B.GetUpperBound(0) value = table3B(c1) If value = -1 Then Continue For For c2 = 0 To table4A.GetUpperBound(0) Select Case table4A(c2) Case value Exit For Case -1 table4A(c2) = value Exit For End Select Next c2 Next c1
Array.Sort(table4A) Array.Reverse(table4A)
For c1 = table4A.GetUpperBound(0) To 0 Step -1 Select Case table4A(c1) Case -1 Continue For Case Else ReDim Preserve table4A(c1) Exit For End Select Next
Array.Reverse(table4A)
FileOpen(7, "C:\Program Files\DevStudio\VB\Poker5\table4A.bin", OpenMode.Binary) FilePut(7, c1, 1) FilePut(7, table4A, 5) FileClose(7)
End Sub
Private Sub Table4_main(ByRef table4() As Int32, ByRef table4A() As Int32) Dim c1, c2, h1, h2, h3, h4, h5, h6, h7, suit As Int32 Dim value, index, temp As Int32 Dim OC(5) As Int32 Dim HC(6) As Int32
Dim rank As Int32
For c1 = 0 To table4A.GetUpperBound(0) value = table4A(c1) index = c1 * 52
suit = (value - 1) \ 373071582 value -= suit * 373071582
h1 = (value - 1) \ 13817466 value -= h1 * 13817466
h2 = (value - 1) \ 511758 value -= h2 * 511758
h3 = (value - 1) \ 18954 value -= h3 * 18954
h4 = (value - 1) \ 702 value -= h4 * 702
h5 = (value - 1) \ 26 value -= h5 * 26
h6 = value
OC = New Int32() {h1, h2, h3, h4, h5, h6}
For c2 = 0 To 5 'Pass non-suited as clubs, suited as spades Select Case OC(c2) Case Is <= 13 OC(c2) = OC(c2) * 4 - 3 'clubs Case Else OC(c2) = (OC(c2) - 13) * 4 - 2 'diamond End Select Next
'Good luck trying to understand my logic here. Basically i'm tricking my old evaluator. I feed it clubs if the card is not part of a possible flush, and diamonds if it is. I then tell my evaluator to ignore all club flushes.
For c2 = 1 To 52 If SN(c2) <> suit Then temp = (CN(c2) - 1) * 4 - 3 Else temp = (CN(c2) - 1) * 4 - 2 ' club else diamond rank = determine_winner2(OC(0), OC(1), OC(2), OC(3), OC(4), OC(5), temp) 'If rank = 0 Then rank = rank table4(index + c2) = rank Next c2 Next c1
c1 = table4.GetUpperBound(0) FileOpen(7, "C:\Program Files\DevStudio\VB\Poker5\table4.bin", OpenMode.Binary) FilePut(7, c1, 1) FilePut(7, table4, 5) FileClose(7)
End Sub Private Sub supertable(ByRef HR() As Int32, ByRef table1() As Int32, ByRef table2() As Int32, ByRef table3() As Int32, ByRef table4() As Int32) Dim length As Int32 Dim shift1, shift2, shift3 As Int32 Dim c1 As Int32
length = table1.GetUpperBound(0) + table2.GetUpperBound(0) + 1 + table3.GetUpperBound(0) + 1 + table4.GetUpperBound(0) + 1
HR = New Int32(length) {}
shift1 += table1.GetUpperBound(0) + 1 For c1 = 0 To table1.GetUpperBound(0) HR(c1) = table1(c1) + shift1 Next
shift2 = shift1 + table2.GetUpperBound(0) + 1 For c1 = 0 To table2.GetUpperBound(0) HR(c1 + shift1) = table2(c1) + shift2 Next
shift3 = shift2 + table3.GetUpperBound(0) + 1 For c1 = 0 To table3.GetUpperBound(0) HR(c1 + shift2) = table3(c1) + shift3 Next
For c1 = 0 To table4.GetUpperBound(0) HR(c1 + shift3) = table4(c1) Next
FileOpen(7, "C:\Program Files\DevStudio\VB\Poker5\HR.bin", OpenMode.Binary) FilePut(7, length, 1) FilePut(7, HR, 5) FileClose(7) End Sub
Private Function FourCard_Lexograph(ByRef OC() As Int32) As Int32
'Don't let the word Lexograph fool you, really these are just unique ID's. I abandoned using lexographs. No need for em.
Select Case SN(OC(0)) Case SN(OC(1)), SN(OC(2)), SN(OC(3)) Case Else OC(0) = 52 + CN(OC(0)) - 1 End Select
Select Case SN(OC(1)) Case SN(OC(0)), SN(OC(2)), SN(OC(3)) Case Else OC(1) = 52 + CN(OC(1)) - 1 End Select
Select Case SN(OC(2)) Case SN(OC(0)), SN(OC(1)), SN(OC(3)) Case Else OC(2) = 52 + CN(OC(2)) - 1 End Select
Select Case SN(OC(3)) Case SN(OC(0)), SN(OC(1)), SN(OC(2)) Case Else OC(3) = 52 + CN(OC(3)) - 1 End Select
Array.Sort(OC)
FourCard_Lexograph = OC(0) + OC(1) * 65 + OC(2) * 4290 + OC(3) * 283140
End Function
Private Function FiveCard_Lexograph(ByVal OC() As Int32) As Int32
Dim c1 As Int32 Dim c2 As Int32 Dim count As Int32
For c1 = 0 To 4 count = 0
If OC(c1) <= 52 Then For c2 = 0 To 4 If SN(OC(c1)) = SN(OC(c2)) Then count += 1 Next c2
If count < 3 Then OC(c1) = 52 + CN(OC(c1)) - 1 End If
Next c1
If OC(0) = OC(1) And OC(1) = OC(2) And OC(2) = OC(3) And OC(3) = OC(4) Then FiveCard_Lexograph = -1 : Exit Function
Array.Sort(OC)
FiveCard_Lexograph = OC(0) + OC(1) * 65 + OC(2) * 4290 + OC(3) * 283140 + OC(4) * 18687240
End Function
Private Function SixCard_Lexograph(ByVal OC() As Int32) As Int32
Dim c1 As Int32 Dim c2 As Int32 Dim count As Int32 Dim suit As Int32
For c1 = 1 To 4 count = 0 For c2 = 0 To 5 If SN(OC(c2)) = c1 Then count += 1 Next If count >= 4 Then suit = c1 : Exit For Next
If suit > 0 Then For c1 = 0 To 5 Select Case OC(c1) Case Is <= 52 If SN(OC(c1)) = suit Then OC(c1) = 13 + CN(OC(c1)) - 1 Else OC(c1) = CN(OC(c1)) - 1 Case Else OC(c1) -= 52 End Select Next c1 Else For c1 = 0 To 5 Select Case OC(c1) Case Is <= 52 OC(c1) = CN(OC(c1)) - 1 Case Else OC(c1) -= 52 End Select Next c1 End If
Array.Sort(OC)
SixCard_Lexograph = OC(0) + OC(1) * 26 + OC(2) * 702 + OC(3) * 18954 + OC(4) * 511758 + OC(5) * 13817466 + suit * 373071582
End Function
Private Function Search_Array(ByRef table() As Int32, ByVal UB1 As Int32, ByVal value As Int32) As Int32
'I'm not a programmer - so i don't know where to look for optimized array search functions, so i wrote my own. I think its pretty efficient, hi speed, for any size array. Keeps dividing the search space by 2 till a solution converges, or returns -1 value is not in the array.
Dim LB1 As Int32
Do Search_Array = LB1 + (UB1 - LB1) \ 2
If value > table(Search_Array) Then LB1 = Search_Array ElseIf value < table(Search_Array) Then UB1 = Search_Array Else Exit Function End If
If UB1 - LB1 = 1 Then Select Case value Case table(LB1) Search_Array = LB1 Exit Function Case table(UB1) Search_Array = UB1 Exit Function Case Else Search_Array = -1 Debug.Print("error 3") Exit Function End Select End If Loop
End Function
Ok the only function I haven't given is the determine winner function, and that is because it calls a bunch of tables created by other functions in my old (read ancient) code. I have the tables in my program because they are stored on hard-drive, but the creation functions are in my old code.
'I admit RayW's code is probably 1000x times easier to follow if u know C++...(i don't btw). Sorry didn't write my code to be well understood, just to create the tables i needed.
|
theblitz
Pooh-Bah
Reged: 09/07/04
Posts: 1920
Loc: Israel
|
|
I just went through this thred again and there seem to be a few additons since I was last here. I tried to read most of it but might have missed something so don't flame me if I am repeating something.
A large number of threads talk about lookup tables and how big they become. Also, hashing was consider to be too large. This, however, assumes that the number of possible combinations is 52*51*52.... This is not so.
There are many combinations that are identical. The simplest example is that if all the cards are the same suit then it doesn't matter which one it is.
The reduction in the size of the lookup table would be so large that I think that it would easily offset the extra overhead required to reduce the hand to a unique value.
|
Gullanian
train time, imo
Reged: 12/21/06
Posts: 1748
|
|
Pruning data sets down this complex is extremely difficult. You are also looking at significant performance loss. An O(1) lookup into a larger data set will probably be more efficient.
|
tripos
stranger
Reged: 03/01/07
Posts: 7
|
|
I tried the code Ray posted, thanks to him, and the rest of you, mykey, cactus, jukofyork, gullanian and others for contributions. (Those others specifically do NOT include _D&L_.)
I made the "HandRanks.dat" file. Problem is my machine spends ages loading it. I do:
Code:
#include <fstream> using namespace std;
int main(void) { int HR[32487834]; ifstream inFile ("HandRanks.dat", ios::in | ios::binary); inFile.read((char*) HR, sizeof(HR));
return 0; }
It takes about 2 minutes to load, not very encouraging...
|
twobitplayer
journeyman
Reged: 02/18/07
Posts: 58
|
|
Maybe drop to lower level routines, fread, read, or whatever is the native OS calls for the OS you are using, with an upped buffer size?
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
Maybe drop to lower level routines, fread, read, or whatever is the native OS calls for the OS you are using, with an upped buffer size?
I don't think it will have any effect on speed, but you should be very careful allocating such a big data structure on the stack - it can create random crash bugs caused by stack overflows and sometimes it doesn't hit until you start adding more code and/or port to a new compiler/os:
"int HR[32487834];" should be replaced by either "int* HR=new int[32487834];" or "static int HR[32487834];" to avoid problems.
Linux gcc/g++ is very susceptible to this as IIRC they only allocate an 8MB stack by default.
Juk
|
tripos
stranger
Reged: 03/01/07
Posts: 7
|
|
Quote:
Maybe drop to lower level routines, fread, read, or whatever is the native OS calls for the OS you are using, with an upped buffer size?
I don't think that will have any effect on speed either. Since the fread(...) function is just C's equivalent of C++'s inFile.read(...) .
I didn't know there could be a problem with the stack, thanks Yuk, I guess, I put the keyword static infront of my array. Not that I noticed any difference but it felt right
|
tripos
stranger
Reged: 03/01/07
Posts: 7
|
|
Code:
#include "stdio.h" #include "time.h"
main( ) { int duration = time(0);
static int HR[32487834]; FILE *fp; fp = fopen("HandRanks.dat", "r"); fread(HR, sizeof(HR), 1, fp); fclose(fp);
duration = time(0) - duration; printf("it took %d seconds\n", duration); }
Using c source and a c compiler, output is: it took 115 seconds
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Quote:
Code:
#include "stdio.h" #include "time.h"
main( ) { int duration = time(0);
static int HR[32487834]; FILE *fp; fp = fopen("HandRanks.dat", "r"); fread(HR, sizeof(HR), 1, fp); fclose(fp);
duration = time(0) - duration; printf("it took %d seconds\n", duration); }
Using c source and a c compiler, output is: it took 115 seconds
Hi, My routine is very much like this to read in the Handranks. Seems that your PC might be paging memory out to the disk (check to see your free memory). Keep in mind you need about 120MB free for this to work. This is my first guess... I have other thoughts if this isn't your issue...
Ray...
|
tripos
stranger
Reged: 03/01/07
Posts: 7
|
|
I think I found the problem, I tried running the code on my windows machine at home and this time it took 26 seconds. The other times I've tried it I've used the univesity's linux machines, and I think reading off a remote hard-drive has added some latency to the process. I'm pretty sure the whole file is in memory because when testing all 52C7 it takes a fraction of a second to complete.
|
tripos
stranger
Reged: 03/01/07
Posts: 7
|
|
Extending the discussion on hand evaluators, the next step is perhaps applications of hand evaluators.
Presenting an example with c++ source below, we pick up Kh9h on the preflop, we ask what the pot equities are against 2c2d, 2c2h, ..., AhAs. There are 1225 opponent combinations.
In the following code all pot equities are calculated and placed into the array "float playerEquities[1326]".
In an opponent modelling scheme, take the opponent probability distribution and multiply with the playerEquities.
SIGMA(w(i) * Eq(i))
That is the sum of all the opponent probability weights with the pot equities. On a random = flat distribution, all the weights are 1/1225, so we can sum the equities and do the division at the end.
You need Ray's "HandRanks.dat" to run the code. And a file called "functions.cpp", code for this follows
Code:
#include <iostream> #include <fstream> #include <ctime> #include "functions.cpp"
using namespace std;
unsigned int subDeck = 0; unsigned int topDeck = 0;
int main(void) { //******* Read Ray's File static int HR[32487834]; cout << "loading cards, in some cases be more patient than others..." << endl; ifstream myFile ("HandRanks.dat", ios::in | ios::binary); myFile.read((char*) HR, sizeof(HR)); myFile.close();
cout << "loading completed" << endl;
int duration = time(0);
//******** Assign Deck subDeck = 0xffffffff; topDeck = 0x000fffff;
//********** This example assigns {Kh, 9h} to the player int playerCard1 = 46; //Kh int playerCard2 = 30; //9h extractCard(playerCard1); extractCard(playerCard2);
//Save state of deck after player has been dealt. int subDeckSP1 = subDeck; //SP = Save Point, "1" is 1 player dealt int topDeckSP1 = topDeck; //Need to build a 50 card array which contains all the cards except Kh and 9h. int preFlopUnknowns[50]; buildNaturalArray(preFlopUnknowns);
int combinadic;
//this is where all the equity results are stored //the lookup index is found from the 2 card combinadic float playerEquities[1326]; memset(playerEquities, 0, sizeof(playerEquities));
//Entry 1 is constant for this calculation, so: int entry1 = HR[HR[53 + playerCard1 + 1] + playerCard2 + 1]; //+1 -MOVE INTO RAY SYST.
//Now the strategy is to go through every preflop opponent possibility, //go through them in combinadics natural order which is //{0,1}, {0,2}, {1,2}, {0,3}, {1,4}, {2,4}, {3,4}, ... {48, 49}
for (int OC1 = 0; OC1 <= 49; OC1++) for (int OC0 = 0; OC0 < OC1; OC0++) { //here OC0 is always going to represent the larger of the //two cards
int opponentCard0 = preFlopUnknowns[OC0]; int opponentCard1 = preFlopUnknowns[OC1]; combinadic = (opponentCard1 * (opponentCard1-1)) / 2 + opponentCard0;
int entry2 = HR[HR[53 + opponentCard0 + 1] + opponentCard1 + 1];
cout << "\rcombinadic: " << combinadic;
//Make sure the deck is at the correct Save Point: subDeck = subDeckSP1; topDeck = topDeckSP1; extractCard(opponentCard0); extractCard(opponentCard1); //It's called RAY array, because Ray doesn't use my system //I call my system NATURAL, and I call Ray's system for RAY int RC[48]; //RC = Remaining Cards buildRayArray(RC);
int results[1712304]; int c0, c1, c2, c3, c4; int u0, u1, u2, u3;
int count = 0; for (c0 = 0; c0 < 48; c0++) { u0 = HR[entry1+RC[c0]]; for (c1 = c0+1; c1 < 48; c1++) { u1 = HR[u0+RC[c1]]; for (c2 = c1+1; c2 < 48; c2++) { u2 = HR[u1+RC[c2]]; for (c3 = c2+1; c3 < 48; c3++) { u3 = HR[u2+RC[c3]]; for (c4 = c3+1; c4 < 48; c4++) { results[count++] = HR[u3+RC[c4]]; } } } } }
count = 0; for (c0 = 0; c0 < 48; c0++) { u0 = HR[entry2+RC[c0]]; for (c1 = c0+1; c1 < 48; c1++) { u1 = HR[u0+RC[c1]]; for (c2 = c1+1; c2 < 48; c2++) { u2 = HR[u1+RC[c2]]; for (c3 = c2+1; c3 < 48; c3++) { u3 = HR[u2+RC[c3]]; for (c4 = c3+1; c4 < 48; c4++) { results[count++] -= HR[u3+RC[c4]]; } } } } }
int equityCount = 0; for (int i=0; i<1712304; i++) { if (results[i] > 0) equityCount += 2; if (results[i] == 0) equityCount += 1; } float equity = equityCount / 3424608.0; playerEquities[combinadic] = equity; }
cout << endl;
duration = time(0) - duration; cout << "It took " << duration << " seconds" << endl;
//CHECK: Sum the player equities and divide by 50C2 = 1225
float sumOfEquities = 0; for (int i=0; i<1326; i++) { sumOfEquities += playerEquities[i]; }
float meanEquity = sumOfEquities / 1225; cout << "Kh9h equity against a random hand is: " << meanEquity << endl;
return 0; }
//***************** functions.cpp
Code:
extern unsigned int subDeck; extern unsigned int topDeck;
int extractCard(int cardNumber) { if (cardNumber < 32) { subDeck ^= 1 << cardNumber; } else { cardNumber -= 32; topDeck ^= 1 << cardNumber; } }
void buildRayArray(int* remainingCards) { int positionCounter = 0; for (int i=0; i<32; i++) if (subDeck & (1 << i)) remainingCards[positionCounter++] = i + 1; for (int i=0; i<20; i++) if (topDeck & (1 << i)) remainingCards[positionCounter++] = i + 32 + 1; }
void buildNaturalArray(int* remainingCards) { int positionCounter = 0; for (int i=0; i<32; i++) if (subDeck & (1 << i)) remainingCards[positionCounter++] = i; for (int i=0; i<20; i++) if (topDeck & (1 << i)) remainingCards[positionCounter++] = i + 32; }
/* * Use below print methods for purposes of testing, for numerical calculations * only these may be removed. * */
const char cardsNatural[][3] = {"2c", "2d", "2h", "2s", "3c", "3d", "3h", "3s", "4c", "4d", "4h", "4s", "5c", "5d", "5h", "5s", "6c", "6d", "6h", "6s", "7c", "7d", "7h", "7s", "8c", "8d", "8h", "8s", "9c", "9d", "9h", "9s", "Tc", "Td", "Th", "Ts", "Jc", "Jd", "Jh", "Js", "Qc", "Qd", "Qh", "Qs", "Kc", "Kd", "Kh", "Ks", "Ac", "Ad", "Ah", "As" };
void printDeck() { for (int i=0; i<32; i++) if (subDeck & (1 << i)) std::cout << cardsNatural[i] << std::endl;
for (int i=0; i<20; i++) if (topDeck & (1 << i)) std::cout << cardsNatural[i+32] << std::endl;
}
OUTPUT IS:
loading cards, in some cases be more patient than others... loading completed combinadic: 1325 It took 45 seconds Kh9h equity against a random hand is: 0.599885
|
tripos
stranger
Reged: 03/01/07
Posts: 7
|
|
Sorry for those who are hurting their heads analysing the above, there are two mistakes in the comments.
The natural order of the combinadics should be: {0,1}, {0,2}, {1,2}, {0,3}, {1,3}, {2,3}, {0.4}, {1,4}, {2,4}, {3,4}, ... {48, 49}
hopefully I got it right this time... let these be ={c0, c1} we can check this by running it throught the combinadics formula for two indexes (c1 * (c1-1)) / 2 + c0. Should produce
0, 1, 2, 3, 4, 5,..., 1224
Let's see - yes, is good.
This leads me to the second error where I point out just below the double for loop that OC0 is always the largest, it is in fact OC1 which is always the largest. I got it right in the code anyway...
well, enjoy
|
404url
stranger
Reged: 03/29/07
Posts: 4
|
|
I last visited these forums quite a few months ago. After reading a few of the ideas here I began coding my own evaluator in Java. It was based upon the tabled lookup ideas from the forum. After returning here a little while ago I was amazed at the amount of progress that had been made. Though my algorithm was quite similar to some of the ones presented here it was not nearly as fast by almost a factor of two. In any case, after looking at some of the code I have made some improvements. There was some some unnecessary code in various places. For instance, the "suit iterator" part of Ray's C code was not necessary. It is fine (and also semantically correct) to leave the undefined suit with a value of zero. The bitwise ANDs used in Cactus Kev's evaluator take care of that case. The other changes are apparent upon inspection. In any case, here is a trimmed and somewhat optimized Java version. I placed the Flushes, etc. lookup tables in static arrays in their own wrapper classes. Java's constructor size limitation forced this. Code:
public class Evaluator { /* Card to integer conversions: 2c = 1 2d = 14 2h = 27 2s = 40 3c = 2 3d = 15 3h = 28 3s = 41 4c = 3 4d = 16 4h = 29 4s = 42 5c = 4 5d = 17 5h = 30 5s = 43 6c = 5 6d = 18 6h = 31 6s = 44 7c = 6 7d = 19 7h = 32 7s = 45 8c = 7 8d = 20 8h = 33 8s = 46 9c = 8 9d = 21 9h = 34 9s = 47 Tc = 9 Td = 22 Th = 35 Ts = 48 Jc = 10 Jd = 23 Jh = 36 Js = 49 Qc = 11 Qd = 24 Qh = 37 Qs = 50 Kc = 12 Kd = 25 Kh = 38 Ks = 51 Ac = 13 Ad = 26 Ah = 39 As = 52
*/
public final static int NO_SUIT = 0; public final static int CLUBS = 1; public final static int DIAMONDS = 2; public final static int HEARTS = 3; public final static int SPADES = 4;
public final static int BAD_CARD = -1; public final static int DEUCE = 2; public final static int THREE = 3; public final static int FOUR = 4; public final static int FIVE = 5; public final static int SIX = 6; public final static int SEVEN = 7; public final static int EIGHT = 8; public final static int NINE = 9; public final static int TEN = 10; public final static int JACK = 11; public final static int QUEEN = 12; public final static int KING = 13; public final static int ACE = 14;
public final static int NUM_SUITS = 4; public final static int NUM_RANKS = 13; public final static int NUM_CARDS = 52; public static int[] handRanks = new int[32487834]; // array to hold hand rank lookup table public static boolean verbose = true; // toggles verbose mode private static int[] hand; // re-usable array to hold cards in a hand private static long[] keys = new long[612978]; // array to hold key lookup table private static int numKeys = 1; // counter for number of defined keys in key array private static long maxKey = 0; // holds current maximum key value private static int numCards = 0; // re-usable counter for number of cards in a hand private static int cardIndex = 0; // re-usable index for cards in a hands private static int maxHandRankIndex = 0; private static long startTimer; private static long stopTimer; // Inserts a key into the key array and returns the insertion index. public static int insertKey(long key) { // check to see if key is valid if (key == 0) { return 0; } // short circuit insertion for most common cases if (key >= maxKey) { if (key > maxKey) { keys[numKeys++] = key; // appends the new key to the key array maxKey = key; } return numKeys - 1; } // use binary search to find insertion point for new key int low = -1; int high = numKeys; int pivot; long difference; while (high - low > 1) { pivot = (low + high) >>> 1; difference = keys[pivot] - key; if (difference > 0) { high = pivot; } else if (difference < 0) { low = pivot; } else { return pivot; // key already exists } } // key does not exist so must be inserted System.arraycopy(keys, high, keys, high + 1, numKeys - high); keys[high] = key; numKeys++; return high; } // END insertKey method // Returns a key for the hand created by adding a new card to the hand // represented by the given key. Returns 0 if new card already appears in hand. private static long makeKey(long baseKey, int newCard) { int[] suitCount = new int[NUM_SUITS + 1]; // number of times a suit appears in a hand int[] rankCount = new int[NUM_RANKS + 1]; // number of times a rank appears in a hand hand = new int[8]; // extract the hand represented by the key value for (cardIndex = 0; cardIndex < 6; cardIndex++) { // hand[0] is used to hold the new card hand[cardIndex + 1] = (int)((baseKey >>> (8 * cardIndex)) & 0xFF); } hand[0] = formatCard8bit(newCard); // examine the hand to determine number of cards and rank/suit counts for (numCards = 0; hand[numCards] != 0; numCards++) { suitCount[hand[numCards] & 0xF]++; rankCount[(hand[numCards] >>> 4) & 0xF]++; // check to see if new card is already contained in hand (rank and suit considered) if (numCards != 0 && hand[0] == hand[numCards]) { return 0; } } // check to see if we already have four of a particular rank if (numCards > 4) { for (int rank = 1; rank < 14; rank++) { if (rankCount[rank] > 4) return 0; } } // determine the minimum number of suits required for a flush to be possible int minSuitCount = numCards - 2; // check to see if suit is significant if (minSuitCount > 1) { // examine each card in the hand for (cardIndex = 0; cardIndex < numCards; cardIndex++) { // if the suit is not significant then strip it from the card if (suitCount[hand[cardIndex] & 0xF] < minSuitCount) { hand[cardIndex] &= 0xF0; } } } sortHand(); long key = 0; for (int i = 0; i < 7; i++) { key += (long)hand[i] << (i * 8); } return key; } // END makeKey method // Formats and returns a card in 8-bit packed representation. private static int formatCard8bit(int card) { // 8-Bit Packed Card Representation // +--------+ // |rrrr--ss| // +--------+ // r = rank of card (deuce = 1, trey = 2, four = 3, five = 4,..., ace = 13) // s = suit of card (suits are arbitrary, can take value from 0 to 3) card--; return (((card >>> 2) + 1) << 4) + (card & 3) + 1; } // END formatCard8bit method // Sorts the hand using Bose-Nelson Sorting Algorithm (N = 7). private static void sortHand() { swapCard(0, 4); swapCard(1, 5); swapCard(2, 6); swapCard(0, 2); swapCard(1, 3); swapCard(4, 6); swapCard(2, 4); swapCard(3, 5); swapCard(0, 1); swapCard(2, 3); swapCard(4, 5); swapCard(1, 4); swapCard(3, 6); swapCard(1, 2); swapCard(3, 4); swapCard(5, 6); } // End sortHand method // Swaps card i with card j. private static void swapCard(int i, int j) { if (hand[i] < hand[j]) { hand[i] ^= hand[j]; hand[j] ^= hand[i]; hand[i] ^= hand[j]; } } // END swapCard method // Determines the relative strength of a hand (the hand is given by its unique key value). private static int getHandRank(long key) { // The following method implements a modified version of "Cactus Kev's Five-Card // Poker Hand Evaluator" to determine the relative strength of two five-card hands. // Reference: http://www.suffecool.net/poker/evaluator.html hand = new int[8]; int currentCard; int rank; int handRank = 9999; int holdrank = 9999; int suit = 0; int numCards = 0;
final int[] primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41}; if (key != 0) {
for (cardIndex = 0; cardIndex < 7; cardIndex++) {
currentCard = (int)((key >>> (8 * cardIndex)) & 0xFF); if (currentCard == 0) break; numCards++; // Cactus Kev Card Representation // +--------+--------+--------+--------+ // |xxxbbbbb|bbbbbbbb|cdhsrrrr|xxpppppp| // +--------+--------+--------+--------+ // p = prime number of rank (deuce = 2, trey = 3, four = 5, five = 7,..., ace = 41) // r = rank of card (deuce = 0, trey = 1, four = 2, five = 3,..., ace = 12) // cdhs = suit of card // b = bit turned on depending on rank of card // extract suit and rank from 8-bit packed representation rank = (currentCard >>> 4) - 1; suit = currentCard & 0xF;
// change card representation to Cactus Kev Representation hand[cardIndex] = primes[rank] | (rank << 8) | (1 << (suit + 11)) | (1 << (16 + rank)); } switch (numCards) { case 5 : holdrank = eval_5hand(hand[0],hand[1],hand[2],hand[3],hand[4]); break;
case 6 : // Cactus Kev's Evaluator ranks hands from 1 (Royal Flush) to 7462 (Seven High Card) holdrank = eval_5hand( hand[0],hand[1],hand[2],hand[3],hand[4]); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[2],hand[3],hand[5])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[2],hand[4],hand[5])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[3],hand[4],hand[5])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[2],hand[3],hand[4],hand[5])); holdrank = Math.min(holdrank, eval_5hand(hand[1],hand[2],hand[3],hand[4],hand[5])); break; case 7 : holdrank = eval_5hand( hand[0],hand[1],hand[2],hand[3],hand[4]); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[2],hand[3],hand[5])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[2],hand[3],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[2],hand[4],hand[5])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[2],hand[4],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[2],hand[5],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[3],hand[4],hand[5])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[3],hand[4],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[3],hand[5],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[1],hand[4],hand[5],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[2],hand[3],hand[4],hand[5])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[2],hand[3],hand[4],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[2],hand[3],hand[5],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[2],hand[4],hand[5],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[0],hand[3],hand[4],hand[5],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[1],hand[2],hand[3],hand[4],hand[5])); holdrank = Math.min(holdrank, eval_5hand(hand[1],hand[2],hand[3],hand[4],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[1],hand[2],hand[3],hand[5],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[1],hand[2],hand[4],hand[5],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[1],hand[3],hand[4],hand[5],hand[6])); holdrank = Math.min(holdrank, eval_5hand(hand[2],hand[3],hand[4],hand[5],hand[6])); break; default : System.out.println("ERROR: Invalid hand in GetRank method."); break; } // Hand Rank Representation // +--------+--------+ // |hhhheeee|eeeeeeee| // +--------+--------+ // h = poker hand (1 = High Card, 2 = One Pair, 3 = Two Pair,..., 9 = Straight Flush) // e = equivalency class (Rank of equivalency class relative to base hand) // +-----------------------------------+----------------------------------+-----------------+ // 5-Card Equivalency Classes 7-Card Equivalency Classes // +-----------------------------------+----------------------------------+-----------------+ // 1277 407 High Card // 2860 1470 One Pair // 858 763 Two Pair // 858 575 Three of a Kind // 10 10 Straight // 1277 1277 Flush // 156 156 Full House // 156 156 Four of a Kind // 10 10 Straight Flush // +----------+------------------------+----------------------------------+-----------------+ // Total: 7462 4824 // +----------+------------------------+----------------------------------+-----------------+ handRank = 7463 - holdrank; // Invert ranking metric (1 is now worst hand) if (handRank < 1278) handRank = handRank - 0 + 4096 * 1; // High Card else if (handRank < 4138) handRank = handRank - 1277 + 4096 * 2; // One Pair else if (handRank < 4996) handRank = handRank - 4137 + 4096 * 3; // Two Pair else if (handRank < 5854) handRank = handRank - 4995 + 4096 * 4; // Three of a Kind else if (handRank < 5864) handRank = handRank - 5853 + 4096 * 5; // Straight else if (handRank < 7141) handRank = handRank - 5863 + 4096 * 6; // Flush else if (handRank < 7297) handRank = handRank - 7140 + 4096 * 7; // Full House else if (handRank < 7453) handRank = handRank - 7296 + 4096 * 8; // Four of a Kind else handRank = handRank - 7452 + 4096 * 9; // Straight Flush } return handRank; } // END getHandRank method private static int getIndex(int key) {
// use binary search to find key int low = -1; int high = 4888; int pivot;
while (high - low > 1) { pivot = (low + high) >>> 1; if (Products.table[pivot] > key) { high = pivot; } else if (Products.table[pivot] < key) { low = pivot; } else { return pivot; } } return -1; } // END getIndex method
private static int eval_5hand(int c1, int c2, int c3, int c4, int c5) { int q = (c1 | c2 | c3 | c4 | c5) >> 16; short s; // check for Flushes and Straight Flushes if ((c1 & c2 & c3 & c4 & c5 & 0xF000) != 0) return Flushes.table[q]; // check for Straights and High Card hands if ((s = Unique.table[q]) != 0) return s; q = (c1 & 0xFF) * (c2 & 0xFF) * (c3 & 0xFF) * (c4 & 0xFF) * (c5 & 0xFF); q = getIndex(q); return Values.table[q]; } // END eval_5hand method public static void generateTables() {
int card; int handRank; int keyIndex; long key; if (verbose) { System.out.print("\nGenerating and sorting keys..."); startTimer = System.currentTimeMillis(); } for (keyIndex = 0; keys[keyIndex] != 0 || keyIndex == 0; keyIndex++) { for (card = 1; card < 53; card++) { // add a card to each previously calculated key key = makeKey(keys[keyIndex], card); // create the new key if (numCards < 7) insertKey(key); // insert the new key into the key lookup table } } if (verbose) { stopTimer = System.currentTimeMillis(); System.out.printf("done.\n\n%35s %d\n", "Number of Keys Generated:", (keyIndex + 1)); System.out.printf("%35s %f seconds\n\n", "Time Required:", ((stopTimer - startTimer) / 1000.0)); System.out.print("Generating hand ranks..."); startTimer = System.currentTimeMillis(); } for (keyIndex = 0; keys[keyIndex] != 0 || keyIndex == 0; keyIndex++) { for (card = 1; card < 53; card++) { key = makeKey(keys[keyIndex], card); if (numCards < 7) { handRank = insertKey(key) * 53 + 53; // if number of cards is < 7 insert key } else { handRank = getHandRank(key); // if number of cards is 7 insert hand rank }
maxHandRankIndex = keyIndex * 53 + card + 53; // calculate hand rank insertion index handRanks[maxHandRankIndex] = handRank; // populate hand rank lookup table with appropriate value } if (numCards == 6 || numCards == 7) { // insert the hand rank into the hand rank lookup table handRanks[keyIndex * 53 + 53] = getHandRank(keys[keyIndex]); } } if (verbose) { stopTimer = System.currentTimeMillis(); System.out.printf("done.\n\n%35s %f seconds\n\n", "Time Required:", ((stopTimer - startTimer) / 1000.0)); } } // END generateTables method public static void main (String [] args) { generateTables();
int c0, c1, c2, c3, c4, c5, c6; int u0, u1, u2, u3, u4, u5; int numHands = 0; int handRank; int[] handEnumerations = new int[10]; int[][] equivalencyEnumerations = new int[10][3000]; String[] handDescriptions = {"Invalid Hand", "High Card", "One Pair", "Two Pair", "Three of a Kind", "Straight", "Flush", "Full House", "Four of a Kind", "Straight Flush"};
if (verbose) { System.out.print("Enumerating hand frequencies and equivalency classes..."); startTimer = System.currentTimeMillis(); }
for (c0 = 1; c0 < 53; c0++) { u0 = handRanks[53 + c0]; for (c1 = c0 + 1; c1 < 53; c1++) { u1 = handRanks[u0 + c1]; for (c2 = c1 + 1; c2 < 53; c2++) { u2 = handRanks[u1 + c2]; for (c3 = c2 + 1; c3 < 53; c3++) { u3 = handRanks[u2 + c3]; for (c4 = c3 + 1; c4 < 53; c4++) { u4 = handRanks[u3 + c4]; for (c5 = c4 + 1; c5 < 53; c5++) { u5 = handRanks[u4 + c5]; for (c6 = c5 + 1; c6 < 53; c6++) { handRank = handRanks[u5 + c6]; handEnumerations[handRank >>> 12]++; equivalencyEnumerations[handRank >>> 12][handRank & 0xFFF]++; numHands++; } } } } } } } if (verbose) { stopTimer = System.currentTimeMillis(); System.out.printf("done.\n\n%35s %f seconds\n\n", "Time Required:", ((stopTimer - startTimer) / 1000.0)); } System.out.println("SEVEN-CARD POKER HAND FREQUENCIES AND EQUIVALENCY CLASSES\n"); System.out.printf(" %-17s %15s %15s\n", "HAND", "FREQUENCY", "CLASSES"); System.out.println(" -------------------------------------------------");
int sumEquivalency; int numClasses = 0; for (int i = handEnumerations.length - 1; i >= 0; i--) { sumEquivalency = 0; for (int j = 0; j < equivalencyEnumerations[i].length; j++) { if (equivalencyEnumerations[i][j] != 0) sumEquivalency++; } numClasses += sumEquivalency; System.out.printf(" %-17s %15d %15d\n", handDescriptions[i], handEnumerations[i], sumEquivalency); } System.out.println(" -------------------------------------------------"); System.out.printf(" %-17s %15d %15d\n", "TOTAL", numHands, numClasses); }
} // END class Evaluator
|
404url
stranger
Reged: 03/29/07
Posts: 4
|
|
Forgot to mention. I stuck in everything statically so I could post it more easily. If you're going to use it it's probably not a bad idea to alter it so it adheres to OO design.
|
404url
stranger
Reged: 03/29/07
Posts: 4
|
|
And the top should read: Code:
2c = 1 2d = 2 2h = 3 2s = 4 3c = 5 3d = 6 3h = 7 3s = 8 4c = 9 4d = 10 4h = 11 4s = 12 5c = 13 5d = 14 5h = 15 5s = 16 6c = 17 6d = 18 6h = 19 6s = 20 7c = 21 7d = 22 7h = 23 7s = 24 8c = 25 8d = 26 8h = 27 8s = 28 9c = 29 9d = 30 9h = 31 9s = 32 Tc = 33 Td = 34 Th = 35 Ts = 36 Jc = 37 Jd = 38 Jh = 39 Js = 40 Qc = 41 Qd = 42 Qh = 43 Qs = 44 Kc = 45 Kd = 46 Kh = 47 Ks = 48 Ac = 49 Ad = 50 Ah = 51 As = 52
Edited by 404url (03/30/07 05:47 PM)
|
BinaryMan
stranger
Reged: 05/09/07
Posts: 4
Loc: CA, USA
|
|
I have been trying to implement some of the suggestions found here in VB, but I realized that I can never achieve high performance because of the way VB handles array accesses (the SAFEARRAY type protects you, but it also slows the code a bit).
I've tried large tables and hash arrays, but the memory overhead is ridiculous. Using VB classes isn't a solution either because of memory and process overhead (I kind of anticipated this, but tried anyway). While VB makes most of the speed-insensitive coding tasks easier, I'm turning to assembly code to do the hand evaluations in an external library (sort of a compromise).
The way I'm doing it now seems relatively efficient and straightforward, though I'm sure there are more complicated (and faster) ways. Here is the outline for the process:
VB has preprocessed the cards into an array Card(1 to 7). VB passes a pointer to Card(1) to the external optimized library. Each card is represented by a 64-bit structure containing one 16-bit integer per suit as recommended by several people. Bits 0-12 are set for each suit to represent the card ranks contained in the hand. The library can easily address the card data using this format.
The evaluator first uses each 16-bit rank mask in an 8K lookup table to determine if a flush is present. It then combines all four rank masks using the OR operation. A different 8K table lookup determines if the hand is a straight or high card hand. The only hand types that remain undetected so far are rank-count-based (pair, 2 pair, trip, full house, quad).
This is where optimization is critical (at least in my case). I have seen methods of using boolean operations on the rank masks to figure out which ranks are paired, triped, etc. However, there is still additional processing to identify the true hand type and final hand rank. Rather than working with the rank masks in this way, I am trying to find assembly code methods that more directly lead to a table access or hand rank.
Idea: If assembly code can quickly sort the ranks from high-low or vice versa accross all four rank masks, then the table size for a 7-vector rank set lookup is reduced from 60M+ to around 50K. I am testing using the BSR (Bit Scan Reverse; finds the index of the high bit in a register) instruction followed by BTR (Bit Test with Reset) to quickly extract the sorted rank bits. The BSR/BTR instructions only take one cycle on modern processors, and so they seem relatively efficient for this purpose. There are two factors I see that may allow for optimization tricks: (1) Each rank mask contains at most 4 set bits (because a flush has been ruled out in an earlier test). (2) All rank masks together contain at most 6 set bits (because a high card hand was also ruled out).
Problem: The high bits may be in different rank masks and the bits are not implicitly ordered between the masks (only within each individual mask). I can extract all seven card ranks, but they would not always be in order.
Could someone with assembly knowledge tell me how they would solve this problem (or anyone who has an alternative)? It may require a specialized trick. I can get the population count of each rank mask using an 8K table lookup if it helps with the solution. Array memory lookups for Core processors probably aren't too bad. The IMUL instruction if needed for other purposes takes about three cycles. The CMOV instruction takes about one cycle and might avoid some JMPs. If seven bytes (the bit indexes) contained in registers can be sorted/swapped quickly, then it alleviates the problem quite a bit - the hand rank is simply the sorted bytes representing the card ranks (4 bits are needed per card index for the rank; all seven cards will fit in a 32-bit register). The problem might come with the fact that VB code could displace the data cache, so I will assume that the 8K tables will not be resident in the L1 cache, therefor I will not expect maximum performance attained by some people. However, I think I can do much better than 100K comparisons per second (should hit at least several million per second using optimized library code).
Once the hand ranking algorithm is fast enough, I can work on my simulation code trying to get 10 bots at a table to converge on optimal play (most people want maximal play, but in limit poker you can leverage time and probabilities to be profitable with lower risk and bankroll volatility; no-limit poker is different altogether as you have a risk of ruin at Sit-n-Go tables that can't be ignored - that's a separate problem I will tackle later).
|
advis0r
journeyman
Reged: 04/02/07
Posts: 99
Loc: Southern Germany
|
|
does anyone know a java implementation of an equity calculator for given hands and a board? optional features: allows Ranges like TT+ and AQs+, is fast 
i know that there is a poker-eval project, but i have no clue how to compile a DLL that i can call out of java OR use the java native interface to call c functions directly (im too stupid to compile those ;D)
thx in advance
|
jukofyork
Carpal \'Tunnel
Reged: 09/16/04
Posts: 2551
Loc: Leeds, UK.
|
|
Quote:
i know that there is a poker-eval project, but i have no clue how to compile a DLL that i can call out of java OR use the java native interface to call c functions directly (im too stupid to compile those ;D)
The JNI is incredibly easy to use and has a very good tutorial to follow. It pretty much writes all the code stubs and creates the DLL project for you - all you have to do is fill in the functionality and then hit built and you're done. If if you are a Java programmer and not a C/C++ programmer, your knowledge of Java syntax and a quick read of some FAQ about the differences should be enough to interface some C/C++ code via the JNI.
Juk
|
advis0r
journeyman
Reged: 04/02/07
Posts: 99
Loc: Southern Germany
|
|
thanks for the reply, i know the JNI isnt that difficult to use (tutorial etc) but the problem is the compilation itself (when i use functions defined in the poker-eval library).
i would spend some hours figuring it all out but first i was gonna ask if something that is ready-to-use is available already 
http://svn.gna.org/viewcvs/pokersource/trunk/poker-eval/java/ here are some java to pokereval bindings via JNI but when i tried to get it running real quick i failed (not keen with make scripts). the java code there also has support for hand ranges etc. which is pretty neat - so if nobody has something for me already, i might invest some time and make it work 
(i think it could improve the AllinCalc performance a lot if i dont have to run pokenum.exe and parse its output )
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
Quote:
does anyone know a java implementation of an equity calculator for given hands and a board? optional features: allows Ranges like TT+ and AQs+, is fast 
Sorry, no optional features: http://www.stevebrecher.com/Software/software.html
|
advis0r
journeyman
Reged: 04/02/07
Posts: 99
Loc: Southern Germany
|
|
Hello Steve,
Thanks for your Code  Problem: Your Enumerator Class has Package visibility and thus isnt directly usable for other java programs. I did copy your source into my own files for this little Test:
http://nopaste.byte-welt.de/view.php?id=357
output:
Quote:
WINS: [1140, 186] TIED: [0, 0] PaPo: [0.0, 0.0]
while pokerstove and pokenum give me other results:
Quote:
990 games 0.005 secs 198,000 games/sec
Board: 2c 7d Ts Dead:
equity win tie pots won pots tied Hand 0: 91.616% 91.62% 00.00% 907 0.00 { AcAs } Hand 1: 08.384% 08.38% 00.00% 83 0.00 { KdKh }
Quote:
Holdem Hi: 990 enumerated boards containing Ts 2c 7d cards win %win lose %lose tie %tie EV As Ac 907 91.62 83 8.38 0 0.00 0.916 Kd Kh 83 8.38 907 91.62 0 0.00 0.084
did I do anything wrong in my little piece of code? and why do the printed results change if I change the instance and number of thread parameters for Enumerator??
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
Quote:
... I did copy your source into my own files for this little Test:
http://nopaste.byte-welt.de/view.php?id=357
output:
Quote:
WINS: [1140, 186] TIED: [0, 0] PaPo: [0.0, 0.0]
while pokerstove and pokenum give me other results: [...] did I do anything wrong in my little piece of code?
Sorry, I can't go over your code just now. Here's the output from the code downloaded from my site: Code:
Known hole cards; two per player: acaskdkh Number of players with unknown hole cards (0 to 2) [0]: Known board cards [none]: 2c7dts Dead/exposed cards [none]: 990 deals required. Start dealing? (y/n) [y]:
AcAs KdKh % chance of outright win 91.616162 8.383838 % chance of win or split 91.616162 8.383838 expected return, % of pot 91.616162 8.383838 fair pot odds:1 0.091510 10.927711 pots won: 907.00 83.00
Quote:
and why do the printed results change if I change the instance and number of thread parameters for Enumerator??
Because your parameters are "lies"? The parameters must reflect the number of threads; each of n enumeration threads does 1/n of the work. For thread creation and use, see Showdown.java.
|
advis0r
journeyman
Reged: 04/02/07
Posts: 99
Loc: Southern Germany
|
|
thx for the reply... if you would have taken a quick look at the 22 lines of code i posted, u could have told me earlier that i have to remove the used cards from the deck 
nevermind it seems to work well now do you plan to upload a newer version with a public Enumerator class or am i allowed to "copy" this part of your sourcecode ?
|
Steve Brecher
newbie
Reged: 09/06/04
Posts: 45
|
|
Quote:
do you plan to upload a newer version with a public Enumerator class or am i allowed to "copy" this part of your sourcecode ?
Copy at will!
|
advis0r
journeyman
Reged: 04/02/07
Posts: 99
Loc: Southern Germany
|
|
thank you my program is almost 2 times faster now
|
StickGuy
stranger
Reged: 05/20/07
Posts: 1
|
|
The number of unique 6 card IDs can be reduced from about 350,000 to about 280,000 by setting the rank of cards that won't factor into the final hand to 0. If the hand is already a flush, for example, any non-suited cards will be irrelevant. This is the single biggest reduction, but smaller ones can be made for four of a kind, full houses, flushes and straights.
|
hanhikoski
stranger
Reged: 06/15/07
Posts: 3
|
|
The reason to register. Thank you. I'd like to thank the guys at the UofA, poker source developers and the contributors in this thread.
Ran Ray Wotton's nested loop hand type enumeration test (TestHandRank.cpp) on a dual-core 2.00 GHz windows laptop. Compiled as a console application and with Maximize speed and Favor Fast Code compiler options in VS.Net 2005. Got some weird results:
BAD!! = 0 High Card = 23294460 Pair = 58627800 Two Pair = 31433400 Three of a Kind = 6461620 Straight = 6180020 Flush = 4047644 Full House = 3473184 Four of a Kind = 224848 Straight Flush = 41584 Total Hands = 133784560
Validation seconds = 1.0940 Total HighPrecision Clocks = 3923457 HighPrecision clocks per lookup = 0.029327
This is quite far from what I should be getting. Ideas?
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
|
Hi, I see that you have the right answers, well that is a good start. Since you have the 2005 version, try to instrument the code and optimize it. That should speed it up. You might want to select the proper CPU and other optimizations in the project settings (I am using the AMD64 dual processor). Honestly getting the 130+ million hands evaled in a second is a great accomplishment in itself!
Ray
|
bokeh
stranger
Reged: 08/05/07
Posts: 1
|
|
Hi friends, I'm new round here!
I've written an extremely simple and fast 7 card evaluator in PHP which can be seen in action at texas.holdem.poker.probability.cal.culator.org
The evaluator function takes an un-ordered hand array('AH', '5H', '4H', '9C', 'TC', '2D', '3H') runs five very simple lines of code and returns an integer ("1" Royal Flush thru "7414" Nine High "98754" which is the worst hand it is possible to form from 7 cards). There are a couple of simple look-up tables and the RAM footprint is in the region of 4.3 megabytes.
I personally don't know C/C++ but would be very interested in sharing my code with anyone that would like to port it into a CLI in C/C++ along with the simulate function.
My email
|
Masterplan
newbie
Reged: 08/10/06
Posts: 46
|
|
been trying to get Ray's code to work as part of my Msc project (very useful!) but am failing horribly. wirth a few subtle modifications to get it to run on visual studio 2005 i got the HandRankSetup to work fine.
Getting Card IDs! ID - 612976 Setting HandRanks! ID - 612976 Number IDs = 612977 maxHR = 32487833 Training seconds = 61.54
BAD!! = 0 High Card = 23294460 Pair = 58627800 Two Pair = 31433400 Three of a Kind = 6461620 Straight = 6180020 Flush = 4047644 Full House = 3473184 Four of a Kind = 224848 Straight Flush = 41584 Total Hands = 133784560
Validation seconds = 0.6250 Total HighPrecision Clocks = 2225124 HighPrecision clocks per lookup = 0.016632
however, i cannot get TestHandRank to run properly. i've converted:
FILE * fin = fopen("..\\HandRanks.dat", "rb");
to
FILE * fin; fopen_s(&fin,"..\\HandRanks.dat", "rb");
but this just doesnt find the file (in the same directory) so i change the ..\\ to .\\ and i get:
BAD!! = 133784560 High Card = 0 Pair = 0 Two Pair = 0 Three of a Kind = 0 Straight = 0 Flush = 0 Full House = 0 Four of a Kind = 0 Straight Flush = 0 Total Hands = 133784560
Validation seconds = 0.5000 Total HighPrecision Clocks = 1787357 HighPrecision clocks per lookup = 0.013360
which is just entirely confusing. any ideas anyone?
|
RayW
stranger
Reged: 03/28/05
Posts: 24
|
| |