gordonm888
Joined: Feb 18, 2015
• Posts: 4319
March 14th, 2023 at 9:02:12 PM permalink
Quote: richodude

Quote: gordonm888

Make separate categories out of 4 card straight plus a pair, 4 card flush plus a pair, and 4 card SF plus a pair.

This bit is really helpful. I started again today with a fresh mind and think I've got it. The cells marked "(calculated)" are my answers

 4sflush 2,032 360 34,320 102,600 134,888 134,888 1,320 10,200 33,264 89,592 121,536 121,536

 4sflush =36*46+8*47 =4*30*3 =4*COMBIN(13,4)*12 =4*COMBIN(13,4)*36-360 =4*(COMBIN(13,4)*48) - B22-B23 =sum(B24:B25) - B22 =(11*4^4)*12/256*10 =10*(4^5-4) =11*(4^4-4)*12 =11*(4^4-4)*36-B27 =11*(4^4-4)*48-B27-B26 =SUM(B28,B29)-B26

I used google sheets on cells A22-B33 for this with the order slightly different. I had it
4sflush (red)
4flush w/ 5straight (yellow)
4flush w/ pair (green)
4flush w/ kicker (green)
4straight w/ 4flush (red)
4straight w/ 5straight (red)
4straight w/ pair (green)
4straight w/ kicker (green)
4flush (orange)
4straight (blue)
4flush (calculated) (orange)
4straight (calculated) (blue)
where a red cell got subtracted from each respective total cell, a yellow got subtracted only from the theoretical, and green was what was added in the calculated cells. I'm sure there's a better way to display this information but I couldn't be bothered finding how.

There is a special case where a 4-card straight with a pair where the paired card also forms a 4-card flush. It happens to be that every single scenario of this forms a 4 card straight flush which I already subtracted out.
These calculations help me with the whole hierarchy

 royal flush 4 36 188 624 1,844 3,744 5,108 10,200 54,912 134,888 121,536 123,552 1,030,656 1,111,668 2,598,960 2,598,960

I'm somewhat confident in these numbers but now have some decisions about the game. Should 4-card flush still outrank straight and two pair? The difference is little and would be more intuitive for people without the numbers and knowledge of 5-card hands. Next, how would I add sections for ace high/king high/queen high etc. combinations? Normally it wouldn't be so bad but the 4-card combos really screw you.

Commenting on Mental's calculations, I'm curious as to how your code got lower answers than me. The 4flush is definitely too low at only 3x more likely than a 5flush but the 4straight is ballpark. I like the video poker prediction but this will be a stud game of player vs. dealer. Hoping to get into game invention and looking at a class in LV in August where corporations can buy final projects (table game ideas).

In your categories, when you use the word kicker, you might substitute the word "singleton" when it is meant that the uncoordinated 5th card is unpaired.

I was going to cite the omission of '4 straight +4 flush + pair' as a category and thus that your '4-straight + pair' may be too high. I do now see your note on '4 straight +4 flush + pair' hands but I still wonder if '4 straight + pair' is too high because of double counting.
So many better men, a few of them friends, are dead. And a thousand thousand slimy things live on, and so do I.
Mental
Joined: Dec 10, 2018
• Posts: 467
March 15th, 2023 at 6:31:43 AM permalink
Quote: Mental

Okay, I just looked at my code and saw a single character typo in the 4-flush code.

Hand,     Pay,   #Flops
Royal, 800.00, 4
Str_Flush, 50.00, 36
Full_House, 9.00, 3744
Flush, 6.00, 5108
Straight, 4.00, 10200
Trips, 3.00, 54912
Flush4, 2.70, 110940
Straight4, 2.30, 92208
Two_Pair, 2.00, 123552
JOB, 1.00, 323268

Okay, except for that typo that I spotted last night, I do not see any other mistakes. I assigned my 4-straights and 4-flush types a different way than the previous iteration and I still get the same results as the previous post.

Just to make comparisons easier, I defined a pair as twos or better and reran the calculation:
Hand,     Pay,   #Flops
Royal, 800.00, 4
Str_Flush, 50.00, 36
Full_House, 9.00, 3744
Flush, 6.00, 5108
Straight, 4.00, 10200
Trips, 3.00, 54912
Flush4, 2.70, 110940
Straight4, 2.30, 92208
Two_Pair, 2.00, 123552
2OB, 1.00, 1047552

If I restrict my count to hands that only use J,Q,K, and A, I count 48 hands that qualify as 4-flushes and 1488 hands that qualify as 4-straights. The 48 is four suits x 12 kickers. Here are the 48 possible 4-flushes:
JQKAJ
JQKAQ
JQKAK
JQKAA
JQKAJ
JQKAQ
JQKAK
JQKAA
JQKAJ
JQKAQ
JQKAK
JQKAA
JJQKA
JJQKA
JJQKA
QJQKA
QJQKA
QJQKA
KJQKA
KJQKA
KJQKA
AJQKA
AJQKA
AJQKA
JQKAJ
JQKAQ
JQKAK
JQKAA
JQKAJ
JQKAQ
JQKAK
JQKAA
JJQKA
JJQKA
QJQKA
QJQKA
KJQKA
KJQKA
AJQKA
AJQKA
JQKAJ
JQKAQ
JQKAK
JQKAA
JJQKA
QJQKA
KJQKA
AJQKA
Mental
Joined: Dec 10, 2018
• Posts: 467
March 15th, 2023 at 8:11:38 AM permalink
I added in SF4 and RF4 hands because these eat up some of the 4-flush and 4-straight hands.
Hand,     Pay,   #Flops
Royal, 800.00, 4
Str_Flush, 50.00, 36
RF4, 40.00, 188
SF4, 15.00, 1844
Full_House, 9.00, 3744
Flush, 6.00, 4792
Straight, 4.00, 9960
Trips, 3.00, 54912
Flush4, 2.70, 109464
Straight4, 2.30, 92208
Two_Pair, 2.00, 123552
2OB, 1.00, 1047552
So, we agree on everything except the straights and flushes.

I believe your flush number is wrong. You have flush = 5,108, but that is the total number of flushes that are not SF5 and RF5. Some of the Flushes are also also RF4 and SF4, so the number of plain 5-flushes has to be less than 5,108. Am I missing something about how you are defining and counting hands? A hand can only evaluate to one type, right?

Here are the flops for the traditional JOB VP hands.
Hand,     Pay,   #Flops
Royal, 800.00, 4
Str_Flush, 50.00, 36
Full_House, 9.00, 3744
Flush, 6.00, 5108
Straight, 4.00, 10200
Trips, 3.00, 54912
Two_Pair, 2.00, 123552
JOB, 1.00, 337920
richodude
Joined: Jul 29, 2022
• Posts: 54
March 17th, 2023 at 4:37:22 AM permalink
Quote: Mental

I added in SF4 and RF4 hands because these eat up some of the 4-flush and 4-straight hands.

Hand,     Pay,   #Flops
Royal, 800.00, 4
Str_Flush, 50.00, 36
RF4, 40.00, 188
SF4, 15.00, 1844
Full_House, 9.00, 3744
Flush, 6.00, 4792
Straight, 4.00, 9960
Trips, 3.00, 54912
Flush4, 2.70, 109464
Straight4, 2.30, 92208
Two_Pair, 2.00, 123552
2OB, 1.00, 1047552
So, we agree on everything except the straights and flushes.

I believe your flush number is wrong. You have flush = 5,108, but that is the total number of flushes that are not SF5 and RF5. Some of the Flushes are also also RF4 and SF4, so the number of plain 5-flushes has to be less than 5,108. Am I missing something about how you are defining and counting hands? A hand can only evaluate to one type, right?

Here are the flops for the traditional JOB VP hands.
Hand,     Pay,   #Flops
Royal, 800.00, 4
Str_Flush, 50.00, 36
Full_House, 9.00, 3744
Flush, 6.00, 5108
Straight, 4.00, 10200
Trips, 3.00, 54912
Two_Pair, 2.00, 123552
JOB, 1.00, 337920

My responses are usually quick. Why the delay? well I've been teaching myself to program of course! It's far from perfect, but I believe my logic for the 4-card hands is correct. Well, what did I get? Out of 2.6m random hands, ~95k of them were 4-card straights and ~89.5k were 4-card flushes. Along with ~1.16m high cards, ~1.05m pairs, ~54.8k trips and so on. Both of these 4-card hands still contain straight flush combos and I'm working that out but its 6:30 in the morning and I want to share something before I crash. That means your 4straight result is most likely correct! But how is my mathematical solution wrong? My brain already hurts and I don't wanna figure that out. That means then the actual number of 4flush combos should be closer to 86k, unless my code logic is undercounting something but it shouldn't with the lines

if temp_suit.count(0) == 4 or temp_suit.count(1) == 4 or temp_suit.count(2) == 4 or temp_suit.count(3) == 4:
is_4flush = True

where suits have a value of 0-3 hearts-spades. The hand is ranked higher than 4straight so that's not undercounted. I'd take another look at how you're counting 4flushes. I'll definitely have another go tomorrow and use this code to build onto the full game. I could provide the code somewhere if you'd like

Also, yes. That 5108 flush count is a silly mistake from me
Mental
Joined: Dec 10, 2018
• Posts: 467
March 17th, 2023 at 6:53:05 AM permalink
Quote: richodude

My responses are usually quick. Why the delay? well I've been teaching myself to program of course! It's far from perfect, but I believe my logic for the 4-card hands is correct. Well, what did I get? Out of 2.6m random hands, ~95k of them were 4-card straights and ~89.5k were 4-card flushes. Along with ~1.16m high cards, ~1.05m pairs, ~54.8k trips and so on. Both of these 4-card hands still contain straight flush combos and I'm working that out but its 6:30 in the morning and I want to share something before I crash. That means your 4straight result is most likely correct! But how is my mathematical solution wrong? My brain already hurts and I don't wanna figure that out. That means then the actual number of 4flush combos should be closer to 86k, unless my code logic is undercounting something but it shouldn't with the lines

if temp_suit.count(0) == 4 or temp_suit.count(1) == 4 or temp_suit.count(2) == 4 or temp_suit.count(3) == 4:
is_4flush = True

where suits have a value of 0-3 hearts-spades. The hand is ranked higher than 4straight so that's not undercounted. I'd take another look at how you're counting 4flushes. I'll definitely have another go tomorrow and use this code to build onto the full game. I could provide the code somewhere if you'd like

Also, yes. That 5108 flush count is a silly mistake from me

Good for you. I don't think I could get very far with just a spreadsheet. Is this C++?

I learned 25 years ago that the double counting problem is hard and prone to subtle errors. I wanted a general solution. What I do to handle double counting is to write a series of functions defining any hand type in terms of five indices i0, i1, i2, i3, i4 which range from 0-51 for a standard deck with no jokers. Nada is the type that you refer to as 'high card'. In my VP program, I could assign a payoff to Nada if I wanted to. When my program reads in a pay table, it sets a variable is'Type' to the line number in the pay table (where 'Type' is a specific hand type like Flush). Every specific variable isType is initialized to -1 before this. The first thing any of these hand-definition functions does is check the type. If it is less than 0, then that particular type was never defined in the pay table and I can skip it.

{
int k, int i0, i1, i2, i3, i4;
for (i0 = 0; i0 < 48; i0++) {
for (i1 = i0 + 1; i1 < 49; i1++) {
for (i2 = i1 + 1; i2 < 50; i2++) {
for (i3 = i2 + 1; i3 < 51; i3++) {
for (i4 = i3 + 1; i4 < 52; i4++) {
k = indexHand(i0, i1, i2, i3, i4);
tentative(k, type);
}
}
}
}
}
}
You can see that the setNada() function loops through all 52 non-joker cards and makes every possible permutation of 5 cards (without regard to order). The function indexHand() returns a index, k, into the 2598960 different deals. The function tentative(k, type) tentatively sets the payoff for that hand to the paytable value for type. If that hand already has a value higher than that, tentative() will return without doing anything. Otherwise, tentative() sets the type. This tentative value can be overridden by a later hand-definition function. Since the isNada type normally has a payoff of zero, any subsequent hand-definition function will override the isNada type.

All I did to implment you stud game was to add three new hand-definition functions and call them like this:
setSF4(v.isSF4, v.isRF4); // 4-Flush
setFl4(v.isFlush4); // 4-Flush
setStr4(v.isStr4); // 4-Straight

Here. v.isStr4 is the new type of a 4-Straight, etc. The setSF4() function defines 4-SF and 4-RF hands in the same function. A single hand could be resolved to isNada, isStr4, isFlush4, isStraight, isFlush, isSF4, isSF, and isRF. Only the last type would stick. Other hands could resolve to isNada, isHiPair, isStr4, isFlush4.

The indexHand(i0, i1, i2, i3, i4) function is implemented as an array lookup table. It is generic and reusable for any game. I currently support single and double joker games, so I can quickly index 54 choose 5 = 3162510 different hands. You could use a hash table to do this indexing faster, but the hash table would consume a lot of memory.
richodude
Joined: Jul 29, 2022
• Posts: 54
March 20th, 2023 at 3:05:27 AM permalink
Quote: Mental

Is this C++?

Python. Learned a tiny bit, made switching to other languages harder so stuck with it. I can still run ~150k hands/second on a r5 3600.

Anyways, I'm here to share my refined code update. I can confirm our results now match exactly. Hooray! Some things that were wrong were: the super simple checking for suits 0-3 when the suits were labeled 1-4 ;-; also I added 4sflush checks which was by far the most difficult thing with my logic. There was also this weird bug in my logic where the hand [A,K,4,3,2] would not register as a straight but [A,x,4,3,2] would.

You can check out my project at https://github.com/richodude/MinnesotaStud/tree/master

The part I'm most proud of is the logic I used to rank hands. Looking online, there's surprisingly little (at least that I understood) about programming poker. I took a concept from someone who categorized poker hand in excel and it will likely work with many cards and any poker combo.

Start by taking your list [x,x,x,x,x] and arrange your cards in order. I also had my cards in number form pulling from the list 2♥,2♣,2♦,2♠,3♥...A♠. So the hand [0,1,2,3,51] would be 2♥,2♣,2♦,2♠,A♠

Then do whatever you need to have the rank and suit separate. For example in the above list I made two separate lists, [2,2,2,2,14] and [1,2,3,4,4]. I'll call them the 'rank_list' and 'suit_list'

To find pairs, sets, two pairs, quads, and full houses, we can make another list I'll call 'has_pair'. Start by comparing index 0 and 1 (first and second number) and if they're the same, add 1 to the has_pair list, and if not, a 0. continue until index 4,5 where we will have a list looking like [1,1,1,0] Another example for [3♣,5♦,5♠,K♥,K♦] would yield the list [0,1,0,1]. The has_pair list determines the hand rank. [1,0,0,0] [0,1,0,0] [0,0,1,0] or [0,0,0,1] is just a pair, [1,0,1,0] [1,0,0,1] or [0,1,0,1] is two pair, [1,1,0,0] [0,1,1,0] or [0,0,1,1] is trips, [1,1,0,1] or [1,0,1,1] is a full house, and [1,1,1,0] or [0,1,1,1] is quads. A lot to read but the pattern is there.

to find flushes, you just find the mode of suit_list and if it's at least 4, you have a 4-card flush or better

to find straights (the tricky part) is actually pretty easy. I make another list called 'connector_list' and use it similarly to pair_list. if index 1 minus index 0 = 1, those two cards are connected to form a potential straight. the list [1,1,1,1] means a 5-card straight. Ace is 14 in my code so I added special logic for the case where the list is [1,1,1,0] and there is an Ace-Two present. 4-cards is a bit trickier but not too bad. lists [1,1,1,0] and [0,1,1,1] are 4straights, but also any single pair hand where the connector_list XOR the pair_list is [1,1,1,1] For example if the hand is [3♣,4♠,5♠,5♥,6♦] our connector_list would be [1,1,0,1] and the pair_list would be [0,0,1,0] XOR'ing combines the lists where they are not matching.

4-card straight flushes were pretty scuffed with the sheer amount of edge cases. Look at lines ~140 in HandRanking.py if you want to see how I did it

Most of this code was my brain doing the work, but every programmer knows there's some BS line that you need that you would never figure out yourself so thanks chatGPT for that. It also taught me a dual-line solution for cycling through all ~2.6 million hand combinations

import itertools
combinations = itertools.combinations(range(0,52),5)
for hand in combinations:
rank_the_hand()

remember python is line based rather than bracket/semicolon/whatever-else-you-use based in case it looks weird

Now I can actually move on to the game... *sudden realisation I've barely started this project*
Mental
Joined: Dec 10, 2018
• Posts: 467
March 20th, 2023 at 6:21:18 AM permalink
I am glad we agree on the results. It is really helpful to have a cross check on any new code.

I use python where I have to, but I am not proficient. I can usually understand code in any procedural programming language.

When I started out, I used an approach similar to the one you describe. But, I like to code fast and then check for mistakes. Someone who is more methodical than me might be able to code these classifying functions better than I can. I found that I make less mistakes by defining every hand within a class, and then having the program sort out which one is ranked highest. I can have my program print out any subset of hands with color coding for suits. I usually can spot any errors quickly.

The Wizard has a page somewhere on indexing any hand to an integer quickly. When I dug into the algorithm, it is equivalent to my array lookup table. That is, his indexing formula is equivalent to the math that the processor does to calculate the address of an element in a 4-dimensional array like the one I use. Since compiler know how to do array-index math very efficiently, my method is just a bit faster, but both methods are very fast in C++.

Let me know if you want or need any help with fast hand indexing. Since you are working on a stud game, you won't need fast code for drawing cards. If I recall correctly, I got hints for using the Serpinski triangle for this task from VP Genius. This is really the only way to go.