Search code examples
vb.netloopsfor-loopnested-loops

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


Solution

  • 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.