FraserOfSmeg
FraserOfSmeg

Reputation: 1148

All non-repeating combinations of a deck of cards

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

Answers (2)

djv
djv

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

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

Related Questions