socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
May 8th, 2014 at 4:58:11 PM permalink
I thought I would share. As I mentioned in the double draw poker thread, I put a lot of time into trying to analyze games without much success. Jopke suggested I take a look at Mississippi stud, and I already had, but I decided to revisit the game and… Eureka, EV= -0.049148582 !!

Mississippi Stud is a good first game to analyze because, at ~156M outcomes, it’s relatively small. All in all, it took me about 3 1/2 days to code and test (2 days a while back and 1.5 days just now), a far cry from the longer stretches I’ve put into BJ, VP, and general poker code (though the general poker code is used here). My final runtime was around 5 minutes, compared to a 23 second benchmark that Jopke offered for his code. I used a memoization + mutual recursion technique that I’d worked on with my BJ code which allowed me to easily take out much of the repetition found in the game tree. Unfortunately, this means I can’t get quite as much information back out at the end, for instance, I can’t distinguish between between a hand that followed a 1-1-3 betting pattern and an otherwise similar one that followed a 1-3-1 pattern. But in return, I think the problem is a bit easier to express, at least once you understand the method used. But I could be wrong, and may just be doing things the hard way.

Other than the suffe style deck and corrosponding hand evaluator (translated to clojure), I took a few lines from my BJ code:
(defmacro defn-memo [name & body] `(def ~name (memoize (fn ~body))))  ;found on internet, simplifies memoization slightly 

(defn average-over-vec [the-vec] (/ (reduce + the-vec) (count the-vec)))

(defn doseqfor [combineby deck digwith]
(do (doseq [val deck] (digwith val))
(let [vallist (vec (for [val deck] (digwith val)))]
(combineby vallist))))

and then there’s the program,
(def payouts [-1 0 1 2 3 4 6 10 40 100 500])

(defn-memo fold [[hand bet :as gm]] ["fld" (- bet)])

(defn-memo bet1 [[hand bet :as gm]]
(let [b-deck (apply disj s-deck1 hand)]
["bt1"
(doseqfor average-over-vec b-deck
#(let [nHand (conj hand %) nBet (inc bet)]
(if (= 5 (count nHand))
(* nBet (payouts (handtype (eval (vec nHand)))))
((dispatch [nHand nBet]) 1))))]))

(defn-memo bet3 [[hand bet :as gm]]
(let [b-deck (apply disj s-deck1 hand)]
["bt3"
(doseqfor average-over-vec b-deck
#(let [nHand (conj hand %) nBet (+ 3 bet)]
(if (= 5 (count nHand))
(* nBet (payouts (handtype (eval (vec nHand)))))
((dispatch [nHand nBet]) 1))))]))

(def ops [fold bet1 bet3])

(defn-memo dispatch [gm]
(doseq [op ops] (op gm))
(last (sort-by second (for [op ops] (op gm)))))
To run it, you need to cycle through the initial 2 card hands, something like
(/ (reduce + (vec (for [e (combinations s-deck1 2)] (dispatch [(into #{} e) 1]))))) 1326)
where (combinations) is another utility function I found somewhere on the internet.

I'm trying to decide where to next. I'm tempted to make an attempt at Double Draw Poker since that sort of brought me here.
-John
tringlomane
tringlomane
  • Threads: 8
  • Posts: 6284
Joined: Aug 25, 2012
May 8th, 2014 at 5:06:56 PM permalink
Good Job!
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
May 8th, 2014 at 5:16:05 PM permalink
Quote: tringlomane

Good Job!


Thanks :)
beachbumbabs
beachbumbabs
  • Threads: 101
  • Posts: 14268
Joined: May 21, 2013
May 8th, 2014 at 5:38:45 PM permalink
Quote: socks

Thanks :)



WTG, socks! You're well ahead of me on learning to do that.
If the House lost every hand, they wouldn't deal the game.
jopke
jopke
  • Threads: 5
  • Posts: 132
Joined: Aug 14, 2012
May 8th, 2014 at 5:43:11 PM permalink
Nice job!! Glad I could help you on your journey.

Three Card Poker might be a good next one. It is simple also and has the added twist of a dealer vs player hand.
Wizard
Administrator
Wizard 
  • Threads: 1518
  • Posts: 27042
Joined: Oct 14, 2009
May 8th, 2014 at 5:48:01 PM permalink
Congratulations! I think you would find a Double Draw program would take years to cycle through all the combinations.

A standard challenge is a video poker analyzer.
"For with much wisdom comes much sorrow." -- Ecclesiastes 1:18 (NIV)
jopke
jopke
  • Threads: 5
  • Posts: 132
Joined: Aug 14, 2012
May 8th, 2014 at 6:15:12 PM permalink
Quote: Wizard

Congratulations! I think you would find a Double Draw program would take years to cycle through all the combinations.

A standard challenge is a video poker analyzer.



Have you looked at the DD thread? By reducing the number of hands via suit compression and spreading the work out over a bunch of AWS nodes, I can go through all combinations in a few hours. Unfortunately, my numbers don't exactly match what has been published for DD.
RS
RS
  • Threads: 62
  • Posts: 8626
Joined: Feb 11, 2014
May 8th, 2014 at 6:22:26 PM permalink
What language is that?
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
May 8th, 2014 at 6:28:00 PM permalink
Quote: jopke

Have you looked at the DD thread? By reducing the number of hands via suit compression and spreading the work out over a bunch of AWS nodes, I can go through all combinations in a few hours. Unfortunately, my numbers don't exactly match what has been published for DD.


Yes, iirc, the transposition tables being built here, with my MS program, and with clojure's easy memoization, get me down to about 650k internal nodes on my tree, while covering the 156M leaf game. The data structures are a bit memory hungry, so I'm not sure I can memoize the full 5 card hand + 3 card discard, but if so, I believe the gains should be significantly larger.

Idk, maybe The Wizard has an intuitive (or explicit) sense for how much this sort of technique saves, but I'm not convinced. Until his response, I was just half heartedly considering it, but now, I think I have to give it go now, or at least run a test to see how big of a split hand/discard tree I can cram into memory without doing anything too fancy.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
May 8th, 2014 at 6:31:30 PM permalink
Quote: Wizard

Congratulations! I think you would find a Double Draw program would take years to cycle through all the combinations.

A standard challenge is a video poker analyzer.


Yes, thanks. I think I'm going give it a try, so I guess we'll see.

I did spend a few weeks on VP before, but I hit a dead end with that fancy trick on your programming tips page, and I had to take a break. I'll definitely revisit it in the future.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
May 8th, 2014 at 6:32:46 PM permalink
Quote: RS

What language is that?


Clojure. It' a modern Lisp that's built for performance and runs on the JVM.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
May 8th, 2014 at 8:36:21 PM permalink
I just realized that my JVM didn't have as much memory as I thought it did and that I'd been bumping up against that limit, so I gave it more and my run time went down to just under 4 minutes.
DeMango
DeMango
  • Threads: 36
  • Posts: 2958
Joined: Feb 2, 2010
May 10th, 2014 at 6:54:32 AM permalink
Any idea what the ev would be if you muck face-low cards?
When a rock is thrown into a pack of dogs, the one that yells the loudest is the one who got hit.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
May 10th, 2014 at 10:49:38 AM permalink
Quote: DeMango

Any idea what the ev would be if you muck face-low cards?


face-low? JJ's and QQ's? Or face cards and low cards? I guess I'm not sure what you're asking.
DeMango
DeMango
  • Threads: 36
  • Posts: 2958
Joined: Feb 2, 2010
May 11th, 2014 at 4:56:56 AM permalink
Say King, deuce. Know someone that plays that way, just wondering how much worse the ev is.
When a rock is thrown into a pack of dogs, the one that yells the loudest is the one who got hit.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
May 11th, 2014 at 12:30:06 PM permalink
Quote: DeMango

Say King, deuce. Know someone that plays that way, just wondering how much worse the ev is.


Oh, gotcha. I'm getting -9.17%, but that's just a quick look. I'm not sure how much faith you should put in that #.
miplet
miplet
  • Threads: 5
  • Posts: 2142
Joined: Dec 1, 2009
May 11th, 2014 at 5:32:02 PM permalink
Quote: socks

Oh, gotcha. I'm getting -9.17%, but that's just a quick look. I'm not sure how much faith you should put in that #.


I'm getting -8.16%
Hand1X evCombinationsProbabilityCost Per HandOverall Cost
Unsuited-0.9014291920.14479638010.0985710.01427272398
Suited No A-0.633265480.036199095020.3667350.01327547511
Suited A-0.591327160.012066365010.4086730.004931197587
Total-0.8317668752560.19306184010.1682331250.03247939668

So it costs 3.25% and a base house edge of 4.91%
“Man Babes” #AxelFabulous
richbailey86
richbailey86
  • Threads: 38
  • Posts: 325
Joined: May 8, 2014
May 11th, 2014 at 5:47:54 PM permalink
I read this thread. My response: http://gublecraft.com/gc/halp/files/stacks_image_2.png
An idea whose time has come cannot be stopped by any army or any government. – Ron Paul
jopke
jopke
  • Threads: 5
  • Posts: 132
Joined: Aug 14, 2012
May 11th, 2014 at 8:21:02 PM permalink
Quote: richbailey86

I read this thread. My response: http://gublecraft.com/gc/halp/files/stacks_image_2.png



LOL!
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
May 11th, 2014 at 9:22:51 PM permalink
Quote: miplet

I'm getting -8.16%


Yes, I see my mistake. Thanks for pointing that out.
DeMango
DeMango
  • Threads: 36
  • Posts: 2958
Joined: Feb 2, 2010
May 12th, 2014 at 3:49:16 AM permalink
Thanks gentlemen! Printed out and ready for reeducation! Probably won't do any good, but it stops me from following a bad example.
When a rock is thrown into a pack of dogs, the one that yells the loudest is the one who got hit.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
May 12th, 2014 at 11:07:20 PM permalink
Quote: socks

Yes, thanks. I think I'm going give it a try, so I guess we'll see.

I did spend a few weeks on VP before, but I hit a dead end with that fancy trick on your programming tips page, and I had to take a break. I'll definitely revisit it in the future.


Ok Wizard, I'm that convinced double draw is hard, and I can't do what I thought I could :(

I'm going to try Three Card Poker as a stepping stone instead.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
June 17th, 2014 at 2:31:38 PM permalink
I don't know that anyone cares, but I finally got a correct result for a second game, Three Card Poker. I'm slowly getting better at putting various optimizations into practice and the code runs in about 6 seconds, w/ a couple more seconds for setting things up.

I also cut the run time on MStud down from 4min to 30sec.
teliot
teliot
  • Threads: 43
  • Posts: 2871
Joined: Oct 19, 2009
June 17th, 2014 at 2:39:53 PM permalink
Quote: socks

I don't know that anyone cares, but I finally got a correct result for a second game, Three Card Poker. I'm slowly getting better at putting various optimizations into practice and the code runs in about 6 seconds, w/ a couple more seconds for setting things up.

I also cut the run time on MStud down from 4min to 30sec.

Funny about 3CP. That game is trivial compared to coding MS. But congrats, keep up the good work! There is a lot of room for more analysts in the industry, and you are doing exactly what we all do, figure it out ourselves from scratch.

I'm going to run my MS code and see the run time for a cycle, hang on ... 8 seconds. Maybe it's that I wrote it in C++, compiled in using g++ -Ofast, and run it on a pretty new Ubuntu quad core box. I use a fast poker library. Glad to send you my code if you want it, just email me.
Climate Casino: https://climatecasino.net/climate-casino/
beachbumbabs
beachbumbabs
  • Threads: 101
  • Posts: 14268
Joined: May 21, 2013
June 17th, 2014 at 4:41:57 PM permalink
Quote: socks

I don't know that anyone cares, but I finally got a correct result for a second game, Three Card Poker. I'm slowly getting better at putting various optimizations into practice and the code runs in about 6 seconds, w/ a couple more seconds for setting things up.

I also cut the run time on MStud down from 4min to 30sec.



I think that's pretty great, Socks, and I would also encourage you to keep working on it, and let us know where you are on it. Well done.
If the House lost every hand, they wouldn't deal the game.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
June 17th, 2014 at 4:52:27 PM permalink
Quote: teliot

Funny about 3CP. That game is trivial compared to coding MS. But congrats, keep up the good work! There is a lot of room for more analysts in the industry, and you are doing exactly what we all do, figure it out ourselves from scratch.

I'm going to run my MS code and see the run time for a cycle, hang on ... 8 seconds. Maybe it's that I wrote it in C++, compiled in using g++ -Ofast, and run it on a pretty new Ubuntu quad core box. I use a fast poker library. Glad to send you my code if you want it, just email me.


Thanks for the encouragement. It's really appreciated.

3CP didn't seem any easier to me. I'm guessing my approach (mutually recursive and memoized) made MS a bit easier to express than it would've been otherwise, especially since I'd explored that technique with an earlier attempt (at BJ). I also took the time to make a really sparse lookup table using a prime #'d deck for 3CP.

I see a way to make both games run faster. Not sure how much, but I'm guessing I can get a good multiple improvement on MS. I just have to figure out how to use mutable arrays w/ multiple cores, instead of the immutable data structures I'm using now.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
June 17th, 2014 at 4:57:08 PM permalink
Quote: beachbumbabs

I think that's pretty great, Socks, and I would also encourage you to keep working on it, and let us know where you are on it. Well done.


Thank you. I will keep you updated :)
teliot
teliot
  • Threads: 43
  • Posts: 2871
Joined: Oct 19, 2009
June 17th, 2014 at 5:26:44 PM permalink
Quote: socks

3CP didn't seem any easier to me.

Okay, I went back and looked at my code. You are right, it was harder than I remembered. I started by scoring all the three-card hands into an array score[52][52][52]. Overall, the program takes about 2 seconds to run.
Climate Casino: https://climatecasino.net/climate-casino/
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
June 17th, 2014 at 5:58:46 PM permalink
Quote: teliot

score[52][52][52]


Ugh. All of a sudden, my sparse array seems really gimmicky... it seemed clever at the time.
teliot
teliot
  • Threads: 43
  • Posts: 2871
Joined: Oct 19, 2009
June 17th, 2014 at 8:29:47 PM permalink
Quote: socks

Quote: teliot

score[52][52][52]


Ugh. All of a sudden, my sparse array seems really gimmicky... it seemed clever at the time.

I used all sorts of gimmicks before I discovered that I could increase memory to an array score[52][52][52][52][52] that stores the exact rank of each poker hand (I use ulimit -s 1048576 in Ubuntu). I do a lot of hashing and scoring now for big programs. I'm just waiting until the memory in my box is large enough to store the score all seven-card hands, then we're rocking!
Climate Casino: https://climatecasino.net/climate-casino/
AxiomOfChoice
AxiomOfChoice
  • Threads: 32
  • Posts: 5761
Joined: Sep 12, 2012
June 17th, 2014 at 9:00:13 PM permalink
Quote: teliot

I used all sorts of gimmicks before I discovered that I could increase memory to an array score[52][52][52][52][52] that stores the exact rank of each poker hand (I use ulimit -s 1048576 in Ubuntu). I do a lot of hashing and scoring now for big programs. I'm just waiting until the memory in my box is large enough to store the score all seven-card hands, then we're rocking!



A trillion values? Memory is not your only problem.
teliot
teliot
  • Threads: 43
  • Posts: 2871
Joined: Oct 19, 2009
June 17th, 2014 at 9:15:18 PM permalink
Quote: AxiomOfChoice

Memory is not your only problem.

My dogs, cat and wife agree with you.
Climate Casino: https://climatecasino.net/climate-casino/
AxiomOfChoice
AxiomOfChoice
  • Threads: 32
  • Posts: 5761
Joined: Sep 12, 2012
June 17th, 2014 at 10:00:55 PM permalink
Quote: teliot

My dogs, cat and wife agree with you.



Ha!

Seriously, though, even if you precompute everything, you are not loading 32T of data into memory in no time.

You should just build a datacenter.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
June 17th, 2014 at 10:58:31 PM permalink
Quote: teliot

Quote: socks

Quote: teliot

score[52][52][52]


Ugh. All of a sudden, my sparse array seems really gimmicky... it seemed clever at the time.

I used all sorts of gimmicks before I discovered that I could increase memory to an array score[52][52][52][52][52] that stores the exact rank of each poker hand (I use ulimit -s 1048576 in Ubuntu). I do a lot of hashing and scoring now for big programs. I'm just waiting until the memory in my box is large enough to store the score all seven-card hands, then we're rocking!


Actually, I think I was going the other way. I used an oversized(109MB), but very sparse, array and prime numbered cards, so I could just multiply individual cards to get an index w/o hasing or even keeping hands sorted. I knew I could use lots of memory, and this was the first usage that came to mind. I guess I had prime numbers on the mind.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
June 20th, 2014 at 12:06:42 PM permalink
Before moving on to other games, I put in the last obvious optimization for MS and 3CP, and got my times down to:

3cp: 1.6s + 2.2s for setup (the distinction is only important if you want to do multiple runs.)
MStud: 12.2s
CrystalMath
CrystalMath
  • Threads: 8
  • Posts: 1911
Joined: May 10, 2011
June 20th, 2014 at 2:49:34 PM permalink
I was just curious about my run time on 3CP, and it averages 250ms. I haven't done a MS analyzer, though.
I heart Crystal Math.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
June 20th, 2014 at 4:23:32 PM permalink
Quote: CrystalMath

I was just curious about my run time on 3CP, and it averages 250ms. I haven't done a MS analyzer, though.


Nice. A few minor refinements got me down to 1.3s. There are a few more rough edges, but much of what's left is likely my choice in language. Clojure is going to be slower than c/c++.
JB
Administrator
JB
  • Threads: 334
  • Posts: 2089
Joined: Oct 14, 2009
June 20th, 2014 at 9:22:07 PM permalink
Quote: teliot

Quote: teliot

score[52][52][52]


I'm just waiting until the memory in my box is large enough to store the score all seven-card hands, then we're rocking!


If you use a single-dimensional array for the exact number of combinations, such as score[22100] or score[2598960] or score[133784560], you can do it using the bare minimum amount of memory. PM me if interested.
teliot
teliot
  • Threads: 43
  • Posts: 2871
Joined: Oct 19, 2009
June 20th, 2014 at 9:54:56 PM permalink
Quote: JB

If you use a single-dimensional array for the exact number of combinations, such as score[22100] or score[2598960] or score[133784560], you can do it using the bare minimum amount of memory. PM me if interested.

I don't do PM's. Another way?
Climate Casino: https://climatecasino.net/climate-casino/
JB
Administrator
JB
  • Threads: 334
  • Posts: 2089
Joined: Oct 14, 2009
June 20th, 2014 at 10:05:55 PM permalink
I'll post it here... stay tuned.
AxiomOfChoice
AxiomOfChoice
  • Threads: 32
  • Posts: 5761
Joined: Sep 12, 2012
June 20th, 2014 at 10:19:33 PM permalink
I thought that the whole point of this was do to lookups as quickly as possible. With a min sized array or hash table you can get constant-time lookups and minimal memory usage, but there is a significant multiplicative constant factor to calculate the hash or the array offset or whatever you choose to do.

Are you saying that you can get a min-sized array without that constant factor?
JB
Administrator
JB
  • Threads: 334
  • Posts: 2089
Joined: Oct 14, 2009
June 20th, 2014 at 10:52:22 PM permalink
Quote: AxiomOfChoice

I thought that the whole point of this was do to lookups as quickly as possible. With a min sized array or hash table you can get constant-time lookups and minimal memory usage, but there is a significant multiplicative constant factor to calculate the hash or the array offset or whatever you choose to do.

Are you saying that you can get a min-sized array without that constant factor?


If you use multi-dimensional arrays, there is a behind-the-scenes overhead of calculating the offset into it, which is fastest if the dimensions are all powers of 2 since the compiler can use bit-shifting to compute the offset. If you use a single-dimensional array and calculate the offset yourself, the net effect is about the same (in my opinion). A benchmark would be necessary to compare, but I would hazard a guess that with larger arrays, using the combination approach is faster. Especially if you are just iterating through every possible 5-card hand, then all you do is increment an index by 1 each iteration, which is about as fast as you can get.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
June 20th, 2014 at 11:08:55 PM permalink
Quote: JB

If you use multi-dimensional arrays, there is a behind-the-scenes overhead of calculating the offset into it, which is fastest if the dimensions are all powers of 2 since the compiler can use bit-shifting to compute the offset. If you use a single-dimensional array and calculate the offset yourself, the net effect is about the same (in my opinion).


Yes, it seems like walking the 2+2 graph is faster than letting the computer do the indexing using a 7 dim array, but it seems like it would choke on memory accesses for random lookups. But if you manage the larger 52^7 array yourself, you can get the best of both worlds (once memory is available)
edit: I meant, "is faster for continuous blocks of hands"
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
June 20th, 2014 at 11:15:38 PM permalink
Has anyone looked over the specialk eval? I always thought it looked interesting but it seems to get overlooked. In part 2, the author claims to get 250M evals/sec on his 2010 macbook pro.
http://specialk-coding.blogspot.com/2010/04/texas-holdem-7-card-evaluator_23.html
JB
Administrator
JB
  • Threads: 334
  • Posts: 2089
Joined: Oct 14, 2009
June 20th, 2014 at 11:27:34 PM permalink
Okay, I'll use 5-card hands as an example to keep it simple. Instead of score[52][52][52][52][52] you can have score[2598960].

First, you need some precomputed arrays to hold the results of combin(n,k) where n <= 52 (or a larger deck size if applicable) and k >= 2 and k <= 5 (or a larger number of cards in the hand).

At design time, the value of k is always known, so I use separate single-dimensional arrays for k = 2 to 5. I call them Choose2[], Choose3[], Choose4[], Choose5[]. These should be populated with combin(n,k) values so that, for example, Choose5[52] = combin(52,5) = 2598960. "Invalid" possibilities such as Choose5[4] = combin(4,5) must have a zero value. Excel throws an error for these cases, but we need a zero, which is more accurate in my opinion, since there are zero ways to choose 5 items from 4 anyway.

int[] Choose2 = { 0, 0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171,
190, 210, 231, 253, 276, 300, 325, 351, 378, 406, 435, 465, 496, 528, 561, 595,
630, 666, 703, 741, 780, 820, 861, 903, 946, 990, 1035, 1081, 1128, 1176, 1225,
1275, 1326 };

int[] Choose3 = { 0, 0, 0, 1, 4, 10, 20, 35, 56, 84, 120, 165, 220, 286, 364, 455, 560, 680, 816,
969, 1140, 1330, 1540, 1771, 2024, 2300, 2600, 2925, 3276, 3654, 4060, 4495,
4960, 5456, 5984, 6545, 7140, 7770, 8436, 9139, 9880, 10660, 11480, 12341,
13244, 14190, 15180, 16215, 17296, 18424, 19600, 20825, 22100 };

int[] Choose4 = { 0, 0, 0, 0, 1, 5, 15, 35, 70, 126, 210, 330, 495, 715, 1001, 1365, 1820, 2380,
3060, 3876, 4845, 5985, 7315, 8855, 10626, 12650, 14950, 17550, 20475, 23751,
27405, 31465, 35960, 40920, 46376, 52360, 58905, 66045, 73815, 82251, 91390,
101270, 111930, 123410, 135751, 148995, 163185, 178365, 194580, 211876,
230300, 249900, 270725 };

int[] Choose5 = { 0, 0, 0, 0, 0, 1, 6, 21, 56, 126, 252, 462, 792, 1287, 2002, 3003, 4368, 6188,
8568, 11628, 15504, 20349, 26334, 33649, 42504, 53130, 65780, 80730, 98280,
118755, 142506, 169911, 201376, 237336, 278256, 324632, 376992, 435897,
501942, 575757, 658008, 749398, 850668, 962598, 1086008, 1221759, 1370754,
1533939, 1712304, 1906884, 2118760, 2349060, 2598960 };

Now, to score every possible 5-card hand, start with an index at zero and loop through the cards as follows:

int i = 0;

for (int c5 = 4; c5 < 52; c5++) {
for (int c4 = 3; c4 < c5; c4++) {
for (int c3 = 2; c3 < c4; c3++) {
for (int c2 = 1; c2 < c3; c2++) {
for (int c1 = 0; c1 < c2; c1++) {

score[i++] = ScoreHand(c1, c2, c3, c4, c5);

} } } } }

where ScoreHand() is your hand-scoring function.

----

If you have five card variables (c1, c2, c3, c4, c5) all ranging from 0 to 51 with no duplicates, you can calculate the offset into the score[] array as follows:

1) Sort the variables such that c5 is the highest and c1 is the lowest. I use the following code to sort them using XOR swaps:
if (c1 > c2) { c1 ^= c2; c2 ^= c1; c1 ^= c2; }
if (c1 > c3) { c1 ^= c3; c3 ^= c1; c1 ^= c3; }
if (c1 > c4) { c1 ^= c4; c4 ^= c1; c1 ^= c4; }
if (c1 > c5) { c1 ^= c5; c5 ^= c1; c1 ^= c5; }
if (c2 > c3) { c2 ^= c3; c3 ^= c2; c2 ^= c3; }
if (c2 > c4) { c2 ^= c4; c4 ^= c2; c2 ^= c4; }
if (c2 > c5) { c2 ^= c5; c5 ^= c2; c2 ^= c5; }
if (c3 > c4) { c3 ^= c4; c4 ^= c3; c3 ^= c4; }
if (c3 > c5) { c3 ^= c5; c5 ^= c3; c3 ^= c5; }
if (c4 > c5) { c4 ^= c5; c5 ^= c4; c4 ^= c5; }

2) Calculate the array offset as follows:

Choose5[c5] + Choose4[c4] + Choose3[c3] + Choose2[c2] + c1

-----

If you have a combination number (0 to 2598959) and need to decode the cards, here's a way to accomplish this:

int c1, c2, c3, c4, c5;

c1 = combination_number; // the combination number being decoded (0 to 2598959)

for (c5 = 51; Choose5[c5] > c1; c5--); c1 -= Choose5[c5];
for (c4 = c5 - 1; Choose4[c4] > c1; c4--); c1 -= Choose4[c4];
for (c3 = c4 - 1; Choose3[c3] > c1; c3--); c1 -= Choose3[c3];
for (c2 = c3 - 1; Choose2[c2] > c1; c2--); c1 -= Choose2[c2];

// c1, c2, c3, c4, c5 now contain the five card numbers corresponding to this combination
AxiomOfChoice
AxiomOfChoice
  • Threads: 32
  • Posts: 5761
Joined: Sep 12, 2012
June 20th, 2014 at 11:27:36 PM permalink
Quote: JB

If you use multi-dimensional arrays, there is a behind-the-scenes overhead of calculating the offset into it, which is fastest if the dimensions are all powers of 2 since the compiler can use bit-shifting to compute the offset. If you use a single-dimensional array and calculate the offset yourself, the net effect is about the same (in my opinion). A benchmark would be necessary to compare, but I would hazard a guess that with larger arrays, using the combination approach is faster. Especially if you are just iterating through every possible 5-card hand, then all you do is increment an index by 1 each iteration, which is about as fast as you can get.



Oh, I thought we were talking about random access, not sequential.

For something like MS Stud, it seems like, not only will you not be able to iterate through all 5-card hands in order, but your hand will be unsorted when you analyze it. It seems like sorting the hand and then calculating the offset would be a lot slower then teliot's method (although, you make a good point; he could probably increase performance even more by making his dimension sized 64 rather than 52)

Am I missing something here?
JB
Administrator
JB
  • Threads: 334
  • Posts: 2089
Joined: Oct 14, 2009
June 20th, 2014 at 11:30:58 PM permalink
Quote: socks

Has anyone looked over the specialk eval? I always thought it looked interesting but it seems to get overlooked. In part 2, the author claims to get 250M evals/sec on his 2010 macbook pro.
http://specialk-coding.blogspot.com/2010/04/texas-holdem-7-card-evaluator_23.html


That approach has a lot of fat that could be trimmed.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
June 20th, 2014 at 11:57:15 PM permalink
Quote: AxiomOfChoice

Oh, I thought we were talking about random access, not sequential.

For something like MS Stud, it seems like, not only will you not be able to iterate through all 5-card hands in order, but your hand will be unsorted when you analyze it. It seems like sorting the hand and then calculating the offset would be a lot slower then teliot's method (although, you make a good point; he could probably increase performance even more by making his dimension sized 64 rather than 52)

Am I missing something here?


Correct, there's a big difference between sorted and unsorted hands for random lookups. That's one benefit of directed acyclic graph approaches like moritz and 2+2, you keep memory requirements small while also doing away with the sorted requirement.
socks
socks
  • Threads: 15
  • Posts: 364
Joined: Jul 13, 2011
June 21st, 2014 at 12:24:53 AM permalink
Quote: AxiomOfChoice

Am I missing something here?


I'm going to take a half step back. It's always hard to predict how things operate in the real world. JB's sorting network probably has everything in the L1 cache, and the arrays being used are all small and will stay in the Lvl2 cache. IIRC, the lvl1 cache has a latency of 1ms, Lvl2 has a latency of 7ms, and RAM is... 100ms? So, yeah... just benchmark it. Sometimes I get carried away drooling over theoretical gains when I shouldn't
AxiomOfChoice
AxiomOfChoice
  • Threads: 32
  • Posts: 5761
Joined: Sep 12, 2012
June 21st, 2014 at 11:55:40 AM permalink
Quote: socks

I'm going to take a half step back. It's always hard to predict how things operate in the real world. JB's sorting network probably has everything in the L1 cache, and the arrays being used are all small and will stay in the Lvl2 cache. IIRC, the lvl1 cache has a latency of 1ms, Lvl2 has a latency of 7ms, and RAM is... 100ms? So, yeah... just benchmark it. Sometimes I get carried away drooling over theoretical gains when I shouldn't



Well, yeah. If we are talking about an analysis running in 2s or 5s it really doesn't matter -- it's not like you can't wait 5s. Of course for games with more cards the difference might be a lot larger.
  • Jump to: