smallstepforman
smallstepforman
  • Threads: 1
  • Posts: 1
Joined: Nov 4, 2025
November 4th, 2025 at 3:45:03 AM permalink
Hi Wizard of Odds.
I've taken the challenge of verifying the simulations for Jacks/Better 9/6 as well as Double Jokers poker.
I've looked at the video and source code for an exhaustive search.

I have noticed an issue with the draw function, which starts from the initial deal, and cycles through all possible hold combinations to determine the optimal play.
This function also allows a discarded card to reappear, and increments draw_score_array. In true video poker, a discarded card cannot reappear. The hit rate calculations are hence incorrect.

The irony is that if you search the internet, most poker odds calculators have the same issue. I wouldn't be suprised that they use your data tables.
Mental
Mental
  • Threads: 17
  • Posts: 1560
Joined: Dec 10, 2018
Thanked by
Dieter
November 4th, 2025 at 10:50:38 AM permalink
This is a standard way to speed up the calculations. You allow the discards to reappear in the combinations (draw_score_array) and then correct this total before you use it in the EV calculations. I have never seen Wizard's code before, but it is structured a lot like my code. The corrections for the discards follows a 32x32 Sierpiński triangle.

Here, the plus and minuses tell you whether to add or subtract an array element from the total number of legal draws. So, if you draw to a four card RF, you start with 48 total draws, including the one when the discard comes back into the hand. Then, using the triangle, you subtract off the one hand that contains the RF. This gets you to the correct answer, 47.

If you are looking at a case where you discard all 5 cards, the process is identical, but the number of terms in the correction is much larger. It is a very clever and efficient way of doing the calculations. I learned this from reading explanations from VPGenius and the Wizard 25 years ago. I don't know if a good explanation can be found anywhere on the web today. The VPGenius page seems to be gone.
Gambling is a math contest where the score is tracked in dollars. Try not to get a negative score.
ThatDonGuy
ThatDonGuy
  • Threads: 133
  • Posts: 7464
Joined: Jun 22, 2011
November 4th, 2025 at 1:17:44 PM permalink
I do it slightly differently. First, I calculate the hand values of (a) all 2,598,960 5-card hands, (b) all 270,725 4-card hands, (c) all 22,100 3-card hands, (d) all 1326 2-card hands, (e) all 52 1-card "hands", and (f) the one "zero-card hand," which is the sum of all of the 5-card hands.
Now, for a given hand, I start by assuming the best play is "hold all five cards," with value = the hand's value.
For each card, I calculate the "discard that one card" value by taking the value of the 4-card hand of the 4 cards held, subtracting the 5-card hand value of the original hand (since you can't redraw the discard), and dividing the result by 47 (since there are only 47 ways to draw 1 card).
For each pair of cards, I calculate the "discard those two cards" values by starting with the value of the 3-card hand with the 3 held cards, subtracting each of the 4-card values of those three cards plus one of the discarded cards, and adding back the 5-card hand value of the original 5 cards (this works because of the Inclusion-Exclusion Principle), then dividing by 1081 (as there are 1081 ways to draw 2 cards from 47).
Repeat this for each set of 3 cards held, then each set of 4, and finally, for drawing all 5 cards.
Whichever of these 32 plays returns the highest value is the best play.
Mental
Mental
  • Threads: 17
  • Posts: 1560
Joined: Dec 10, 2018
November 4th, 2025 at 3:11:51 PM permalink
Quote: ThatDonGuy

I do it slightly differently. First, I calculate the hand values of (a) all 2,598,960 5-card hands, (b) all 270,725 4-card hands, (c) all 22,100 3-card hands, (d) all 1326 2-card hands, (e) all 52 1-card "hands", and (f) the one "zero-card hand," which is the sum of all of the 5-card hands.
Now, for a given hand, I start by assuming the best play is "hold all five cards," with value = the hand's value.
For each card, I calculate the "discard that one card" value by taking the value of the 4-card hand of the 4 cards held, subtracting the 5-card hand value of the original hand (since you can't redraw the discard), and dividing the result by 47 (since there are only 47 ways to draw 1 card).
For each pair of cards, I calculate the "discard those two cards" values by starting with the value of the 3-card hand with the 3 held cards, subtracting each of the 4-card values of those three cards plus one of the discarded cards, and adding back the 5-card hand value of the original 5 cards (this works because of the Inclusion-Exclusion Principle), then dividing by 1081 (as there are 1081 ways to draw 2 cards from 47).
Repeat this for each set of 3 cards held, then each set of 4, and finally, for drawing all 5 cards.
Whichever of these 32 plays returns the highest value is the best play.
link to original post


I believe that you, I, VPGenius, and the Wiz are all effectively doing the same thing. I just explicitly use the Sierpinski triangle to keep track of the Inclusion-Exclusion math.
[

void storeCount::tCountUpdate(int n, int t, int dt) {
int row, s;
for (row = 0; row < HOLD_LIM; row++) {
s = sierpinski[dt];
if (s == 2) {
tCountStore[t] += n;
} else if (s == 1) {
tCountStore[t] -= n;
}
}
}

where n is the number of hands of type t, and dt is the index to the 32 ways of drawing to a starting hand.
For the case where we hold all five cards, there is one hand of type t, and a single call to store.tCountUpdate(1, t, HOLD5) does one addition because we are on the tip of the triangle. The rest of the Inclusion-Exclusion math is all done by this simple loop:

for (dt = 0; dt < HOLD5; dt++) {
nh = numHeld[dt];
switch (nh) {
case 4:
k = hand.key4(dt);
for (t = 0; t < maxType; t++) {
n = hand.array1[t][k];
if (n) store.tCountUpdate(n, t, dt);
}
break;
case 3:
k = hand.key3(dt);
for (t = 0; t < maxType; t++) {
n = hand.array2[t][k];
if (n) store.tCountUpdate(n, t, dt);
}
break;
case 2:
k = hand.key2(dt);
for (t = 0; t < maxType; t++) {
n = hand.array3[t][k];
if (n) store.tCountUpdate(n, t, dt);
}
break;
case 1:
k = hand.key1(dt);
for (t = 0; t < maxType; t++) {
n = hand.array4[t][k];
if (n) store.tCountUpdate(n, t, dt);
}
break;
case 0:
for (t = 0; t < maxType; t++) {
n = hand.array5[t];
if (n) store.tCountUpdate(n, t, dt);
}
break;
default:
printf("%2d line %d\n", dt, __LINE__); fflush(stdout);
exit(0);
break;
}
}

where the hand.arrayX[] are the counts of hands containing X cards. The code performs 32 additions/subtractions for the case of discarding all five cards, so a programmer needs something like the triangle to keep track of which operations to use for each case.

How do you perform the Inclusion–Exclusion math and keep everything straight?
Gambling is a math contest where the score is tracked in dollars. Try not to get a negative score.
heatmap
heatmap
  • Threads: 281
  • Posts: 2492
Joined: Feb 12, 2018
November 4th, 2025 at 5:32:53 PM permalink
aren't humans awesome based on this thread?
ThatDonGuy
ThatDonGuy
  • Threads: 133
  • Posts: 7464
Joined: Jun 22, 2011
November 5th, 2025 at 7:39:05 AM permalink
Quote: Mental

I believe that you, I, VPGenius, and the Wiz are all effectively doing the same thing. I just explicitly use the Sierpinski triangle to keep track of the Inclusion-Exclusion math.

I see how the triangle works now, and you are right.

Quote: Mental

How do you perform the Inclusion–Exclusion math and keep everything straight?
link to original post

"The long way" - with 32 separate calculations.
For example, to calculate the expected return of discarding the first and fifth cards:
PlayValue = 
(
Hands2[Hand2(C1, C5)]
- Hands3[Hand3(C1, C2, C5)] - Hands3[Hand3(C1, C3, C5)] - Hands3[Hand3(C1, C4, C5)]
+ Hands4[Hand4(C1, C2, C3, C5)] + Hands4[Hand4(C1, C2, C4, C5)] + Hands4[Hand4(C1, C3, C4, C5)]
- Hands5[Hand5(C1, C2, C3, C4, C5)]
) / 16215.0;

Here, PlayValue is a double containing the ER for this particular play
C1, C2, C3, C4, and C5 are integers representing the five cards, from 0 to 51 (0 to 12 are A-K of spades, 13-25 are hearts, and so on)
Hand2(C1, C5) converts the pair (C1, C5) to a single number - for example, (0, 1) is 0, (0, 2) is 1, ..., (0, 51) is 50, (1, 2) is 51, (1, 3) is 52, ..., (1, 52) is 99, (2, 3) is 100, and so on
Hands2[n] contains the sum of the values of all of the hands that contain the 2 cards represented by n
Hand3, Hand4, Hand5, Hands3[ ], Hands4[ ], and Hands5[ ] work the same way, but with sets of 3, 4, and 5 cards.
16,215 is the number of sets of 3 cards that are in the 47 cards remaining in the deck
KevinAA
KevinAA 
  • Threads: 20
  • Posts: 426
Joined: Jul 6, 2017
November 5th, 2025 at 9:42:33 AM permalink
I wrote a computer program to calculate VP perfect strategy and it matches WOO.

My program ran through all possible results to calculate the best strategy. It calculates and then selects the maximum of the value of holding all 5 cards, the expected value of holding 4 cards (5 choices, 47 possible results), the expected value of holding 3 cards (10 choices, 1081 possible results), etc.
Mental
Mental
  • Threads: 17
  • Posts: 1560
Joined: Dec 10, 2018
November 5th, 2025 at 2:29:39 PM permalink
Quote: ThatDonGuy


Quote: Mental

How do you perform the Inclusion–Exclusion math and keep everything straight?
link to original post

"The long way" - with 32 separate calculations.
For example, to calculate the expected return of discarding the first and fifth cards:
PlayValue = 
(
Hands2[Hand2(C1, C5)]
- Hands3[Hand3(C1, C2, C5)] - Hands3[Hand3(C1, C3, C5)] - Hands3[Hand3(C1, C4, C5)]
+ Hands4[Hand4(C1, C2, C3, C5)] + Hands4[Hand4(C1, C2, C4, C5)] + Hands4[Hand4(C1, C3, C4, C5)]
- Hands5[Hand5(C1, C2, C3, C4, C5)]
) / 16215.0;

Here, PlayValue is a double containing the ER for this particular play
C1, C2, C3, C4, and C5 are integers representing the five cards, from 0 to 51 (0 to 12 are A-K of spades, 13-25 are hearts, and so on)
Hand2(C1, C5) converts the pair (C1, C5) to a single number - for example, (0, 1) is 0, (0, 2) is 1, ..., (0, 51) is 50, (1, 2) is 51, (1, 3) is 52, ..., (1, 52) is 99, (2, 3) is 100, and so on
Hands2[n] contains the sum of the values of all of the hands that contain the 2 cards represented by n
Hand3, Hand4, Hand5, Hands3[ ], Hands4[ ], and Hands5[ ] work the same way, but with sets of 3, 4, and 5 cards.
16,215 is the number of sets of 3 cards that are in the 47 cards remaining in the deck
link to original post


Thanks for the reply.

For the OP or anyone trying to program a VP analyzer, I cannot emphasize how useful it is to understand what this calculation does and how it speeds up a VP analyzer. It takes just 0.22 milliseconds for me to calculate all this information and cache it. Then, I use the cached information over and over again to analyze each of the 152,646 hands. Then it takes 1.4 seconds for me to analyze jacks or better and create a full strategy.

I recall that TomSki was given credit by Bob Dancer and Dan Paymar for speeding up their commercial VP analyzer. I believe TomSki did the same caching trick.

I don't know where the clearest explanation can be found. It took me a while reading all the abovenamed sources to figure out this trick. Wizard explains some of it here: https://wizardofodds.com/games/video-poker/methodology/
Gambling is a math contest where the score is tracked in dollars. Try not to get a negative score.
ThatDonGuy
ThatDonGuy
  • Threads: 133
  • Posts: 7464
Joined: Jun 22, 2011
November 5th, 2025 at 2:37:08 PM permalink
Quote: Mental

For the OP or anyone trying to program a VP analyzer, I cannot emphasize how useful it is to understand what this calculation does and how it speeds up a VP analyzer. It takes just 0.22 milliseconds for me to calculate all this information and cache it. Then, I use the cached information over and over again to analyze each of the 152,646 hands. Then it takes 1.4 seconds for me to analyze jacks or better and create a full strategy.
link to original post


152,646 hands? Shouldn't that be 134,459?
Mental
Mental
  • Threads: 17
  • Posts: 1560
Joined: Dec 10, 2018
November 6th, 2025 at 5:08:00 AM permalink
Quote: ThatDonGuy

Quote: Mental

For the OP or anyone trying to program a VP analyzer, I cannot emphasize how useful it is to understand what this calculation does and how it speeds up a VP analyzer. It takes just 0.22 milliseconds for me to calculate all this information and cache it. Then, I use the cached information over and over again to analyze each of the 152,646 hands. Then it takes 1.4 seconds for me to analyze jacks or better and create a full strategy.
link to original post


152,646 hands? Shouldn't that be 134,459?
link to original post


Yes, I grabbed the number of unique hands for a double joker game but I used a 52-card deck for the timing test.

Here are the numbers for different numbers of jokers.

No Jokers
Combos: 2,598,960
Unique: 134,459

One Joker
Combos: 2,869,685
Unique: 150,891

Two Jokers
Combos: 3,162,510
Unique: 152,646

I redid the speed test on a faster laptop, but still using only one thread. I can analyze JOB and create an optimal strategy in 0.96 seconds. For double joker games, this takes 1.06 seconds.
Gambling is a math contest where the score is tracked in dollars. Try not to get a negative score.
  • Jump to: