Reputation: 1148
I'm trying to write a poker odds calculator. The idea behind it is that it brute forces it's way through all possible combinations of cards that might be played.
My current logic (stripped down for easy of reading) is as follows;
For i = 0 To unknownCards - 1
For j = 1 To 4
'pick suit
For k = 1 To 13
'pick number
'do other work here
Next
Next
Next
However this is wrong. It's only going to loop through the cards in order. For my purposes the order of the cards isn't important (eg. I don't want to deal with 2, 3, 4 and 4, 3, 2 separately), but it's important that I see every possible unique combination. I just can't wrap my head around how to do this? Any help or advice would be great.
P.S. I'm doing this in VB.net
Upvotes: 0
Views: 177
Reputation: 15774
There are 2,598,960 possible hands. This code generates all possible hands through brute force. It's faster / easier / better to just generate the combinations of 52 card indices than to worry about suits and ranks in your loop. Exactly what @ElizabethSQGoodman said, where there are 5 nested loops, each starting higher than the previous in my case.
I opted for a byte to hold each card, and a struct to hold the hand, for performance reasons. Then, later, you can figure out what card each is based on rules: the first 13 cards are clubs, the next 13 are diamonds, etc. (see getHumanReadableHand()). There you can also define Ace high or low (but not both, sorry!). The rank (A, 2, 3, ..., J, Q, K) is determined by the index modulo 13. The suit is determined by integer division of 13 into the index.
Module Module1
Sub Main()
Dim hands As New List(Of Hand)()
For c0 As SByte = 0 To 51
For c1 As SByte = c0 + 1 To 51
For c2 As SByte = c1 + 1 To 51
For c3 As SByte = c2 + 1 To 51
For c4 As SByte = c3 + 1 To 51
Dim hand = New Hand
hand.Card0 = c0
hand.Card1 = c1
hand.Card2 = c2
hand.Card3 = c3
hand.Card4 = c4
hands.Add(hand)
Next c4
Next c3
Next c2
Next c1
Next c0
Console.WriteLine("There are {0} possible hands.", hands.Count)
Dim rnd As New Random()
Dim r = rnd.Next(hands.Count - 1)
Console.WriteLine("Random hand: {0}", getHumanReadableHand(hands(r)))
Console.WriteLine("Value: {0}", getHandValue(hands(r)))
Console.ReadLine()
End Sub
Function getHumanReadableHand(hand As Hand) As String
Static suits = {"C", "D", "H", "S"}
Static ranks = {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"}
Return String.Join(", ", hand.Cards.Select(Function(card) ranks(rank(card)) & suits(suit(card))))
End Function
Private Function rank(card As SByte) As SByte
Return card Mod 13
End Function
Private Function suit(card As SByte) As SByte
Return CSByte(card \ 13)
End Function
Function getHandValue(hand As Hand) As String
Dim cards = hand.Cards
If cards.Select(Function(card) rank(card)).Max() - cards.Select(Function(card) rank(card)).Min() = 4 AndAlso
cards.Select(Function(card) rank(card)).Distinct().Count = 5 AndAlso
cards.Select(Function(card) suit(card)).Distinct().Count = 1 Then
Return "Straight Flush"
ElseIf cards.OrderBy(Function(card) rank(card)).Take(4).Select(Function(card) rank(card)).Distinct().Count = 1 OrElse
cards.OrderBy(Function(card) rank(card)).Skip(1).Take(4).Select(Function(card) rank(card)).Distinct().Count = 1 Then
Return "Four of a Kind"
ElseIf cards.Select(Function(card) rank(card)).Distinct().Count = 2 Then
Return "Full House"
ElseIf cards.Select(Function(card) suit(card)).Distinct().Count = 1 Then
Return "Flush"
ElseIf cards.Select(Function(card) rank(card)).Max() - cards.Select(Function(card) rank(card)).Min() = 4 AndAlso
cards.Select(Function(card) rank(card)).Distinct().Count = 5 Then
Return "Straight"
ElseIf cards.OrderBy(Function(card) rank(card)).Take(3).Select(Function(card) rank(card)).Distinct().Count = 1 OrElse
cards.OrderBy(Function(card) rank(card)).Skip(1).Take(3).Select(Function(card) rank(card)).Distinct().Count = 1 OrElse
cards.OrderBy(Function(card) rank(card)).Skip(2).Take(3).Select(Function(card) rank(card)).Distinct().Count = 1 Then
Return "Three of a Kind"
ElseIf cards.Select(Function(card) rank(card)).Distinct().Count = 3 Then
Return "Two Pairs"
ElseIf cards.Select(Function(card) rank(card)).Distinct().Count = 4 Then
Return "One Pair"
Else
Return "Garbage"
End If
End Function
Structure Hand
Public Property Card0 As SByte
Public Property Card1 As SByte
Public Property Card2 As SByte
Public Property Card3 As SByte
Public Property Card4 As SByte
Public ReadOnly Property Cards As IEnumerable(Of SByte)
Get
Return New List(Of SByte)({Card0, Card1, Card2, Card3, Card4})
End Get
End Property
End Structure
End Module
Sample output:
There are 2598960 possible hands.
Random hand: 2C, 5C, 2D, 5S, KS
Value: Two Pairs
This code takes around 60 ms to generate all the possible hands on my machine.
Upvotes: 2
Reputation: 101
After the comment clarification: to brute force getting a list of all hands, the simplest way I know to do that is to impose an order on the cards (e.g. rank them by number then suit: standard is 2 of Clubs low, Ace of Spades high) then pick hands that are in ascending order. That way even though order doesn't matter to you, sorting your hands makes sure you have an unambiguous way to decide unique hands.
One, simple to understand but less than optimal way to do this would be to nest 5 loops: the entire deck for the first card, then choose the second card by looping over all cards lower than that one, and so on. (You can actually choose the first card starting at the fifth one in the deck i.e. 3 of clubs, and so on, but it's probably easier just to make sure each hand has 5 cards when you're done.)
My language of choice is Python; if you want the full list, I would make a list of 52 cards, or better yet just number the cards and use ranges; then use itertools.product on your list 5 times over only allowing hands where card_1<card_2<card_3<card_4<card_5. See this question for use of that. For an iterator, I would look at their documented code and modify it to allow only ascending hands. Perhaps a more experienced coder could suggest an optimal iterator.
Upvotes: 0