Results 1 to 19 of 19

Thread: Need a Faster Poker Evaluation Method

  1. #1

    Thread Starter
    New Member
    Join Date
    Apr 2007
    Posts
    12

    Question Need a Faster Poker Evaluation Method

    I have been working on a poker calculator with the ultimate goal of testing for both optimal play methods and maximal play methods using opponent modeling. Right now, I have a way to evaluate and compare 7-card poker hands with reasonable speed (~100K compares/s). However, I know it can be done faster; I currently delegate larger enumerations to an external program ("pokenum"). The speed difference is significant: the other program can compare two 2-card pockets in 0.15s, while my program takes about 15-20s. I would like to achieve similar speeds using native VB code, but I am also willing to write a C++/ASM module if necessary.

    My current method is based on something I found online. It combines the card rank/suit bits using the OR operation such that it can quickly detect a flush or a straight. For other hands, it multiplies the prime numbers associated with each rank (order independent) and uses a static perfect double hash table.

    In order to improve performance, I am trying to figure out a way to quickly evaluate the seven card hand using the card indexes (1-52) directly. There are three ways I thought of to do it:

    1) Create a tree to store all the ~134M values (large memory requirement) and use the indexes sorted low to high as vectors

    2) Create a similar tree for ranks/suits and combine them somehow (haven't figured out how to do it yet)

    3) Use the prime multiplication and hash table approach again (this would still require ~134M entries however)

    Is there a fast and straightforward way to use seven card indexes to translate to a hand ranking without using a ton of memory?

  2. #2
    VB6, XHTML & CSS hobbyist Merri's Avatar
    Join Date
    Oct 2002
    Location
    Finland
    Posts
    6,654

    Re: Need a Faster Poker Evaluation Method

    The basic question in this situatation: have you compiled your program with all optimizations turned on? It can have major differences in speed, especially when a lot of math and arrays are in use.

    I can't say anything about the poker calculations; you might have to post code so it would be possible to see where you might go wrong speed wise. I guess there aren't many here that have done poker related programming; I haven't.

  3. #3
    PowerPoster
    Join Date
    Nov 2002
    Location
    Manila
    Posts
    7,629

    Re: Need a Faster Poker Evaluation Method

    Let's see what you already have.

  4. #4

    Thread Starter
    New Member
    Join Date
    Apr 2007
    Posts
    12

    Re: Need a Faster Poker Evaluation Method

    Here's the code for the function. Most of it simply initializes the tables for evaluation. The actual code to eval the 7 cards is not very long. I have tried it in compiled mode and it works about 2x the speed (which is normal). In my new module, I will be using a type rather than an array of cards because I expect a small performance boost. I don't care about the speed of the init part, but I would like to speed up the hand eval significantly. Thanks.

    Code:
    Public Function RankHand(Card() As Long) As HANDRANKDATA
        Static Init As Boolean
        
        Dim x As Long, S As Long
        
        If Init = False Then
            Static BIT(0 To 30) As Long
            If BIT(0) = 0 Then ' init the bit mask
                For x = 0 To 30
                    BIT(x) = (2 ^ x)
                Next
            End If
            
            Static BITCOUNT(0 To 8192 - 1) As Long ' highest bit is bit 12
            If BITCOUNT(1) = 0 Then ' init the bit counts
                For x = 0 To 8192 - 1
                    For B = 0 To 12
                        If (x And BIT(B)) Then
                            BITCOUNT(x) = BITCOUNT(x) + 1 ' max is 13
                        End If
                    Next
                Next
            End If
            
            ' handles any flushes
            Static FlushQuickTable(0 To 8191) As HANDRANKDATA ' FLUSHDATA  ' 13-bit check
            
            For x = 0 To 8191
                If (BITCOUNT(x) >= 5) And (BITCOUNT(x) <= 7) Then
                    ' flush (default over s.flush)
                    FlushRank = 0
                    BCount = 0
                    For B = 12 To 0 Step -1
                        If (x And BIT(B)) Then
                            ' this rank (b) is of the flush suit
                            ' extract 5 highest ranks of the suit
                            FlushRank = FlushRank Or BIT(B)
                            
                            BCount = BCount + 1
                            If BCount >= 5 Then Exit For ' five highest cards extracted
                        End If
                    Next
                    FlushQuickTable(x).HandType = Flush
                    FlushQuickTable(x).AlphaBits = FlushRank ' flushrank contains the highest flush mask
                    FlushQuickTable(x).BetaBits = 0
                    Call HandRankNumber(FlushQuickTable(x))
                    
                    ' test for s.flush, which would replace the flush mask
                    For B = 12 To 3 Step -1
                        For n = 0 To 4 ' need 5 consecutive bits for a straight
                            If (B = 3) And (n = 4) Then ' test for Ace in A-5 straight
                                If (x And BIT(12)) = 0 Then Exit For
                            Else
                                If (x And BIT(B - n)) = 0 Then Exit For
                            End If
                        Next
                        
                        If (n > 4) Then ' straight flush
                            FlushQuickTable(x).HandType = StraightFlush
                            FlushQuickTable(x).AlphaBits = B ' high rank of the flush
                            FlushQuickTable(x).BetaBits = 0
                            Call HandRankNumber(FlushQuickTable(x))
                            Exit For
                        End If
                    Next
                Else
                    ' no flush, handtype = 0
                    FlushQuickTable(x).HandType = Invalid
                    FlushQuickTable(x).AlphaBits = 0
                    FlushQuickTable(x).BetaBits = 0
                    Call HandRankNumber(FlushQuickTable(x))
                End If
            Next
                    
            Dim Key() As Double  ' keys for prime product combinations
            ReDim Key(1 To 49205)   ' 49205 total unique keys (50388 tested combinations)
            
            Dim ST As String
            Open App.Path & "\sortedkeys.txt" For Binary As #1
            ST = String(LOF(1), 0)
            Get #1, , ST
            Close #1
            
            S2 = Split(ST, vbCrLf)
            ReDim Key(1 To UBound(S2) + 1)
            For x = 0 To UBound(S2)
                Key(x + 1) = Val(S2(x))
            Next
            
            Dim Prime() As Variant
            Prime = Array(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41) ' primes for ranks
            
            ' all keys have 7 ranks. extract and sort the ranks
            Dim KeyFactor() As Long
            ReDim KeyFactor(1 To UBound(Key), 1 To 7) As Long
            
            For x = 1 To UBound(Key)
                TempKey = Key(x)
                FactorIndex = 0
                For R = 12 To 0 Step -1
                    For n = 1 To 4
                        If fmod(TempKey, Prime(R)) = 0 Then
                            FactorIndex = FactorIndex + 1
                            KeyFactor(x, FactorIndex) = R ' Prime(r)
                            TempKey = Fix(TempKey / Prime(R)) ' should still be an integer
                        End If
                    Next
                Next
            Next
            
            ' we have the 7 ranks in KeyProduct(x, 1-7)
            Dim KeyRankCount() As Long
            ReDim KeyRankCount(1 To UBound(Key), 0 To 12)
            
            Dim KeyHandRank() As HANDRANKDATA
            ReDim KeyHandRank(1 To UBound(Key))
            
            ' * = handled by above method
            ' NAME, HANDTYPE, PRIMARY, SECONDARY
            ' * StraightFlush, 9, high rank bit, 0
            ' FourOfAKind, 8, quad rank bit, kicker rank bit
            ' FullHouse, 7, trip rank bit, pair rank bit
            ' * Flush, 6, high flush bits (5), 0
            ' Straight, 5, high rank bit, 0
            ' ThreeOfAKind, 4, trip rank bit, kicker rank bits (2)
            ' TwoPair, 3, pair rank bits (2), kicker rank bit
            ' OnePair, 2, pair rank bit, kicker rank bits (3)
            ' HighCard, 1, kicker rank bits (5), 0
            ' Invalid, 0, 0, 0 [invalid hand]

  5. #5

    Thread Starter
    New Member
    Join Date
    Apr 2007
    Posts
    12

    Re: Need a Faster Poker Evaluation Method

    (continuing previous code)

    Code:
          
            ' evaluate each key as a hand result (to use in hash table)
            For x = 1 To UBound(Key)
                KeyHandRank(x).HandType = Invalid ' default value until hand found
                
                Dim RankCount(0 To 12) As Long ' temp disposable array for tests
                For n = 0 To 12
                    RankCount(n) = 0
                Next
                
                For n = 1 To 7
                    KeyRankCount(x, KeyFactor(x, n)) = KeyRankCount(x, KeyFactor(x, n)) + 1
                    RankCount(KeyFactor(x, n)) = RankCount(KeyFactor(x, n)) + 1
                Next
                
                Dim VectorCount(0 To 4) As Long
                For n = 0 To 4
                    VectorCount(n) = 0
                Next
                
                For R = 12 To 0 Step -1
                    VectorCount(KeyRankCount(x, R)) = VectorCount(KeyRankCount(x, R)) + 1
                Next
                
                ' straight test (if there is a straight, then it must be the high hand)
                For R = 12 To 3 Step -1
                    For n = 0 To 4 ' need 5 consecutive bits for a straight
                        If (R = 3) And (n = 4) Then ' test for Ace in A-5 straight
                            If KeyRankCount(x, 12) = 0 Then Exit For
                        Else
                            If KeyRankCount(x, (R - n)) = 0 Then Exit For
                        End If
                    Next
                    
                    If (n > 4) Then ' straight
                        KeyHandRank(x).HandType = Straight
                        KeyHandRank(x).AlphaBits = R
                        KeyHandRank(x).BetaBits = 0
                        Call HandRankNumber(KeyHandRank(x))
                        Exit For
                    End If
                Next
                
                If (KeyHandRank(x).HandType = Invalid) Then ' no straight, continue search
                    If (VectorCount(4) > 0) Then
                        ' quads
                        r1 = HighRank(RankCount(), 4)
                        r2 = HighRank(RankCount(), 1)
                        KeyHandRank(x).HandType = FourOfAKind
                        KeyHandRank(x).AlphaBits = r1
                        KeyHandRank(x).BetaBits = r2
                        Call HandRankNumber(KeyHandRank(x))
                    ElseIf (VectorCount(3) = 2) Or ((VectorCount(3) = 1) And VectorCount(2) >= 1) Then
                        ' full house
                        r1 = HighRank(RankCount(), 3)
                        r2 = HighRank(RankCount(), 2)
                        KeyHandRank(x).HandType = FullHouse
                        KeyHandRank(x).AlphaBits = r1
                        KeyHandRank(x).BetaBits = r2
                        Call HandRankNumber(KeyHandRank(x))
                    ElseIf (VectorCount(3) = 1) Then
                        ' trips
                        r1 = HighRank(RankCount(), 3)
                        r2 = HighRank(RankCount(), 1)
                        r3 = HighRank(RankCount(), 1)
                        KeyHandRank(x).HandType = ThreeOfAKind
                        KeyHandRank(x).AlphaBits = r1
                        KeyHandRank(x).BetaBits = BIT(r2) Or BIT(r3)
                        Call HandRankNumber(KeyHandRank(x))
                    ElseIf (VectorCount(2) >= 2) Then
                        ' 2 pair
                        r1 = HighRank(RankCount(), 2)
                        r2 = HighRank(RankCount(), 2)
                        r3 = HighRank(RankCount(), 1)
                        KeyHandRank(x).HandType = TwoPair
                        KeyHandRank(x).AlphaBits = BIT(r1) Or BIT(r2)
                        KeyHandRank(x).BetaBits = r3
                        Call HandRankNumber(KeyHandRank(x))
                    ElseIf (VectorCount(2) >= 1) Then
                        ' 1 pair
                        r1 = HighRank(RankCount(), 2)
                        r2 = HighRank(RankCount(), 1)
                        r3 = HighRank(RankCount(), 1)
                        r4 = HighRank(RankCount(), 1)
                        KeyHandRank(x).HandType = OnePair
                        KeyHandRank(x).AlphaBits = r1
                        KeyHandRank(x).BetaBits = BIT(r2) Or BIT(r3) Or BIT(r4)
                        Call HandRankNumber(KeyHandRank(x))
                    Else
                        ' high card only
                        r1 = HighRank(RankCount(), 1)
                        r2 = HighRank(RankCount(), 1)
                        r3 = HighRank(RankCount(), 1)
                        r4 = HighRank(RankCount(), 1)
                        r5 = HighRank(RankCount(), 1)
                        KeyHandRank(x).HandType = HighCard
                        KeyHandRank(x).AlphaBits = BIT(r1) Or BIT(r2) Or BIT(r3) Or BIT(r4) Or BIT(r5)
                        KeyHandRank(x).BetaBits = 0
                        Call HandRankNumber(KeyHandRank(x))
                    End If
                End If
            Next
            
            ' get prime list
            Open App.Path & "\100000.txt" For Binary As #1
            ST = String(LOF(1), 0)
            Get #1, , ST
            Close #1
            
            S2 = Split(ST, vbCrLf)
            
            Dim PrimeList() As Long
            ReDim PrimeList(1 To (UBound(S2) + 1) * 9)
            PIndex = 0
            
            For n = 0 To UBound(S2)
                For a = 1 To 20
                    S2(n) = Replace(S2(n), "  ", " ")
                Next
                S2(n) = Trim(S2(n))
                
                S3 = Split(S2(n), " ")
                
                For a = 0 To UBound(S3)
                    PIndex = PIndex + 1
                    PrimeList(PIndex) = Val(S3(a))
                Next
            Next
            
            ' map each key value to a 2-level hash index that points to the hand rank
            
            Static MAXKEY As Long
            MAXKEY = (2 ^ 27)
                
            For x = 1 To UBound(Key)
                If Key(x) >= MAXKEY Then
                    Key(x) = fmod(Key(x), MAXKEY)
                End If
            Next
            
            Static Hash() As HASHARRAY
            Dim HashTest() As HASHTESTARRAY
            Static PrimeA As Long
            
            PrimeA = 17389 ' 2000th prime = 17389 (has good performance)
            ReDim HashTest(0 To PrimeA - 1)
            ReDim Hash(0 To PrimeA - 1)
            
            For a = 0 To UBound(HashTest)
                ReDim HashTest(a).Key(1 To 1000)
                ReDim HashTest(a).KeyIndex(1 To 1000)
            Next
            
            For x = 1 To UBound(Key)
                a = fmod(Key(x), PrimeA)
                
                HashTest(a).KeyCount = HashTest(a).KeyCount + 1
                'If (HashTest(a).KeyCount > UBound(HashTest(a).Key)) Then
                '    ReDim Preserve HashTest(a).Key(1 To UBound(HashTest(a).Key) + 1000)
                'End If
                HashTest(a).Key(HashTest(a).KeyCount) = Key(x)
                HashTest(a).KeyIndex(HashTest(a).KeyCount) = x
            Next
                        
            For a = 0 To UBound(HashTest)
                For PIndexB = 1 To 100 ' find lowest prime that resolves collisions
                    PrimeB = PrimeList(PIndexB)
                    
                    HashTest(a).Count = 0
                    HashTest(a).Prime = PrimeB
                    ReDim HashTest(a).ColCount(0 To PrimeB - 1)
                    
                    For x = 1 To HashTest(a).KeyCount
                        Test = HashTest(a).Key(x)
                        
                        M2 = fmod(Test, PrimeB)
                        
                        If HashTest(a).ColCount(M2) = 0 Then
                            HashTest(a).ColCount(M2) = 1
                            HashTest(a).Count = HashTest(a).Count + 1
                        Else
                            Exit For ' collision, try next prime
                        End If
                    Next
                    
                    If (x > HashTest(a).KeyCount) Then
                        ' no collisions, make it permanent
                        Hash(a).Count = HashTest(a).Count
                        Hash(a).Prime = HashTest(a).Prime
                        ReDim Hash(a).Result(0 To (PrimeB - 1))
                        
                        For x = 1 To HashTest(a).KeyCount
                            M2 = fmod(HashTest(a).Key(x), PrimeB)
                            Hash(a).Result(M2) = KeyHandRank(HashTest(a).KeyIndex(x))
                        Next
                        
                        Exit For
                    End If
                Next
            Next
                                                                                            
            Init = True
        End If
        
        ' Other modules: CardTable()
        ' This modules:  Hash(), PrimeA, FlushQuickTable()
        ' Begin actual hand evaluation:
    
        Dim P(1 To 7) As Long  ' prime
        Dim RB(1 To 7) As Long ' rankbit
        Dim SB(1 To 7) As Long ' suitbit
        Dim SV(1 To 7) As Long ' suit value (1-4)
        Dim RV(1 To 7) As Long ' rank value (2-14)
        
        For x = 1 To 7
            If (Card(x) >= 1) And (Card(x) <= 52) Then
                P(x) = CardTable(Card(x)).Prime
                RB(x) = CardTable(Card(x)).RankBit '(1, 2, 4, 8, ... , 4096)
                SB(x) = CardTable(Card(x)).SuitBit '(1, 2, 4, 8)
                SV(x) = CardTable(Card(x)).Suit    '(1-4)
                RV(x) = CardTable(Card(x)).Value   '(2-14)
            Else
                ' hand will not be calculated correctly
                Exit Function
            End If
        Next
            
        ' begin analysis
        Dim SuitMask(1 To 4) As Long
        
        For x = 1 To 7
            SuitMask(SV(x)) = SuitMask(SV(x)) Or RB(x)
        Next
        
        ' only 1 flush should be possible per board
        ' if you have a flush, then flush or s.flush will always be the highest hand type
        For S = 1 To 4
            If (FlushQuickTable(SuitMask(S)).HandType <> Invalid) Then
                ' flush or s.flush
                RankHand = FlushQuickTable(SuitMask(S))
                Exit Function
            End If
        Next

  6. #6

    Thread Starter
    New Member
    Join Date
    Apr 2007
    Posts
    12

    Re: Need a Faster Poker Evaluation Method

    Code:
        ' check other combinations by prime multiplication
        Dim Product As Double
        
        Product = 1
        For x = 1 To 7
            Product = Product * P(x)
        Next
            
        ' extract the hand from the hash
        If Product >= MAXKEY Then
            Product = fmod(Product, MAXKEY) ' limit to 2^27-1
        End If
        
        M1 = fmod(Product, PrimeA)
        PrimeB = Hash(M1).Prime
        M2 = fmod(Product, PrimeB)
        
        RankHand = Hash(M1).Result(M2)
    end function

  7. #7
    PowerPoster
    Join Date
    Nov 2002
    Location
    Manila
    Posts
    7,629

    Re: Need a Faster Poker Evaluation Method

    Additional overview please, will look again later when I get home...

    How is Card() as long argument interpreted? Interpreted based on an enum or bit-wise operation?

  8. #8
    PowerPoster
    Join Date
    Nov 2002
    Location
    Manila
    Posts
    7,629

    Re: Need a Faster Poker Evaluation Method

    Don't have much time, it would probably be faster if your the one to implement my idea. It goes something like this, rather than making the processing work with the data structure of the cards, create different data structures (or views of the data) to facilitate processing. Create several arrays based on cards passed:
    - an array(3) to hold cards by suit, first bit or 2^0 is ace as one up to bit 2^13 for ace as higher than king.
    - an array(13) to hold counts of cards per value, if you have three kings then array(12) = 3
    - an array(3) to hold counts of cards per suit.

    Then you check existence of good hands starting at straight flush (royal flush is highest straight flush). You iterate from mask = 2^13

    edit have to go will continue later

  9. #9

    Thread Starter
    New Member
    Join Date
    Apr 2007
    Posts
    12

    Re: Need a Faster Poker Evaluation Method

    The Card() array contains 7 card indexes (range 1-52) which is used in an external lookup table CardTable(1 to 52) to retrieve rank/suit bits and other information for use in the bitmask and prime multiplication operations which lead to a hand ranking. 80% of the code initializes tables and other data; only a small portion actually does the operation on the Card() array.

    I've pretty much got it near optimal using bitmasks and such. I was just considering a method to take the 7 card indexes (1-52) directly to give myself a result. For instance, the fastest possible way is a 7-dimensional array with each dimension being (1-52), and using the card indexes into this array (requiring only 7 simple operations). Of course, 52^7 elements is impractical from a memory standpoint, so the next best way might be to sort the cards and used the sorted indexes into a tree with 7 levels (there would still be ~134M elements, which is impractical but possible on my machine). If there is a really fast way to sort 7 numbers (1-52) then this might be the way to go. The third way is to combine the 7 indexes into a hash key used with a hash table, but it would most likely be slower. Without mapping out all the combinations, there might exist a method for quickly collapsing the card indexes into the ~5000 equivalence classes. I just have no idea what that might be.

    What I need to do is process each 7-card hand to discover the EV (or win%) against 1-9 opponents (quick access table, but obviously many hands will collapse to the same basic type). There are 6,009,159 7-card hand types, and 4,824 5-card hand types possible from all 7-card hands.
    Last edited by BinaryMan; May 2nd, 2007 at 10:22 PM.

  10. #10
    PowerPoster
    Join Date
    Nov 2002
    Location
    Manila
    Posts
    7,629

    Re: Need a Faster Poker Evaluation Method

    You don't need 52 bits, use an array of integer (16 bits) and use 14 bits to store card value (eg ace is 2^0, two is 2^1...). That way cards are already sorted based on the bit value. So to check for straight flush:

    Code:
    Const STRMAXMASK = 15872  '2^13 +2^12 +2^11 +2^10 + 2^9 'note consecutive bits
    Const STRMINMASK = 31        '2^0 +2^1 + 2^2 + 2^3 + 2^4
    
    Private Function HasStrFlush() As Boolean
    Dim intMask As Integer
    Dim booFound As Boolean
    
    intMask = STRMAXMASK
    Do
       If (intBySuit(0) AND intMask) = intMask Then
          booFound = True:  Exit Do
       End If
       If (intBySuit(1) AND intMask) = intMask Then
          booFound = True:  Exit Do
       End If
       If (intBySuit(2) AND intMask) = intMask Then
          booFound = True:  Exit Do
       End If
       If (intBySuit(3) AND intMask) = intMask Then
          booFound = True:  Exit Do
       End If
       intMask = intMask / 2
    Loop Until intMask < STRMINMASK
    
    If booFound And (intMask = STRMAXMASK) Then
       Msgbox "Hand has royal flush"
    ElseIf booFound Then
       Msgbox "Hand has straigh flush"
    End If
    
       HasStrFlush = booFound
    End Function
    Checking for straight works same way but non suit dependent check or in loop check mask against variable intCards where:
    intCards = intBySuit(0) OR intBySuit(1) OR intBySuit(2) OR intBySuit(3)

    That just loops 10 times rather than 100K times, if a straight/royal flush is found then processing stops no need to check for quadro, full house, etc. You can modify the implementation to return weighted hands to differentiate same hands (eg full house of aces has more weight compared to full house of 4's).

  11. #11
    PowerPoster
    Join Date
    Nov 2002
    Location
    Manila
    Posts
    7,629

    Re: Need a Faster Poker Evaluation Method

    I already made a weighted scheme in the code bank but the processing was not efficient... I am planning to update it with what we discuss here when I find the time to do so

  12. #12

    Thread Starter
    New Member
    Join Date
    Apr 2007
    Posts
    12

    Re: Need a Faster Poker Evaluation Method

    >> That just loops 10 times rather than 100K times <<

    Actually, what I meant was that I could do 100K hand rankings per second using my engine. I appreciate your example though. My code actually just has bitmasks for each of the 4 suits and does an OR of each card rank bit with the appropriate suit. It then indexes the bitmask into a table which is pre-processed and tells me if a flush/s.flush has occured. If no flush is triggered from the 4 comparisons, then it does the alternative method of prime multiplication and hashing.

    My method is an adaptation of the method found here: http://www.suffecool.net/poker/evaluator.html. That page describes a 5-card evaluator, but I am extending the concept for 7-cards if possible.

  13. #13
    PowerPoster
    Join Date
    Nov 2002
    Location
    Manila
    Posts
    7,629

    Re: Need a Faster Poker Evaluation Method

    If you add cards according to
    Code:
    Public Enum CardSuits
       Clubs = 0
       Spades = 1
       Hearts = 2
       Diamonds = 3
    End Enum
    
    Public Enum CardValues
       Ace = 0
       Two = 1
       Three = 2
    ...
       King = 12
    End Enum
    
    Public Function AddCard(Suit As CardSuits, Value As CardValues) As Long
       AddCard = -1
       If (intBySuits(Suit) And Value) > 0 Then Exit Function 'already exists
    
       intBySuits(Suit) = intBySuits(Suit) Or (2^ Value)
       If Value = Ace Then
          intBySuits(Suit) = intBySuits(Suit) Or (2^(Value+13))
       End If
       AddCard = 0 'successful add
    End Function
    Then you've set up an ordered listing of the cards by suit which you'll use to determine royal flush, straight flush, straights. Sample of use already provided... lacking is the base values per hand type that will be added to card weights e.g. 900000+15872 for royal flushes, 800000+7936 for straight flush 9 to king. The value of intBySuits() already has weight info of the cards with max value 15872 and minimum 31.

    For the other card types, during AddCard() maintain other arrays in addition to intBySuits() that count cards per suit (for determining flushes) and per value (for determining quadro, trio, pairs).

    I believe this will be faster compared to iterating through arrays with upper boundaries in the thousands.

  14. #14

    Thread Starter
    New Member
    Join Date
    Apr 2007
    Posts
    12

    Re: Need a Faster Poker Evaluation Method

    It doesn't actually iterate through an array. For the quick detection method, the bitmask is the index used to lookup a rank quickly in an array. If using a tree method, it's actually fast to index several arrays by card index. My primary problem might not even be the speed of rank eval, but rather reducing the number of combinations using several methods.

    In the following code, my hand ranking method is fast, but the problem is that there are 134M board layouts and the number of opponents can vary from 1 to 9. I need to map out the Expected Value (EV) of every board and opponent count combination. This means there are over 1B simulation sets that need to be performed, which is too many to calculate. There must be a way to reduce the number of board types so that all boards can be mapped and the EV calculated.

    Code:
    Public Function EnumEV()
        Dim a As Long, b As Long, c As Long, d As Long, e As Long
        Dim f As Long, g As Long, h As Long, i As Long, j As Long
        Dim x As Long, p As Long, TestCount As Long, n As Long
        Dim TotalEV As Double
        
        Dim UsedCards(1 To 52) As Boolean
        TestCount = 1000
        Dim OppHand() As HANDCARDS
        Dim Rank() As Long
        
        T = Timer
        
        For a = 1 To 52 ' p1
            UsedCards(a) = True
            For b = a + 1 To 52 ' p2
                UsedCards(b) = True
                For c = b + 1 To 52 ' c1
                    UsedCards(c) = True
                    For d = c + 1 To 52 ' c2
                        UsedCards(d) = True
                        For e = d + 1 To 52 ' c3
                            UsedCards(e) = True
                            For f = e + 1 To 52 ' c4
                                UsedCards(f) = True
                                For g = f + 1 To 52 ' c5
                                    UsedCards(g) = True
                                    ' 134M combinations
                                    For p = 1 To 9 ' # of opponents
                                        TotalEV = 0 ' reset EV counter
                                        
                                        For x = 1 To TestCount ' # of random tests to run
                                            If Abs(Timer - T) > 0.25 Then
                                                Text = x & "/" & TestCount & " - opp: " & p & " - EV: " & Format(TotalEV / x, "0.000") & " - " & TotalEV & " - "
                                                Text = Text & CardTable(a).Text & " " & CardTable(b).Text & " " & CardTable(c).Text & " "
                                                Text = Text & CardTable(d).Text & " " & CardTable(e).Text & " " & CardTable(f).Text & " "
                                                Text = Text & CardTable(g).Text
                                                
                                                Window.Caption = Text
                                                DoEvents
                                                T = Timer
                                            End If
                                            
                                            ReDim OppHand(0 To 9) ' reset hands
                                            
                                            For n = 0 To p ' current # of opps
                                                If (n = 0) Then ' player pocket
                                                    OppHand(n).p1 = a
                                                    OppHand(n).p2 = b
                                                Else ' random opp pocket selection
                                                    OppHand(n).p1 = GetRandomCard(UsedCards())
                                                    OppHand(n).p2 = GetRandomCard(UsedCards())
                                                End If
                                                
                                                ' community cards
                                                OppHand(n).c1 = c
                                                OppHand(n).c2 = d
                                                OppHand(n).c3 = e
                                                OppHand(n).c4 = f
                                                OppHand(n).c5 = g
                                            Next
                                            
                                            ReDim Rank(0 To 9) ' reset ranks for each player
                                            
                                            MaxRank = 0
                                            For n = 0 To p
                                                Rank(n) = RankHand(HandArray(OppHand(n))).RankNumber
                                                If Rank(n) > MaxRank Then
                                                    MaxRank = Rank(n)
                                                End If
                                            Next
                                        
                                            PotSplit = 0
                                            For n = 0 To p
                                                If Rank(n) = MaxRank Then ' player has won or tied
                                                    PotSplit = PotSplit + 1
                                                End If
                                            Next
                                            
                                            If Rank(0) = MaxRank Then
                                                If PotSplit = 1 Then ' win
                                                    TotalEV = TotalEV + 1
                                                Else ' tie/split
                                                    TotalEV = TotalEV + (1 / PotSplit)
                                                End If
                                            Else ' loss
                                                TotalEV = TotalEV + 0
                                            End If
                                        
                                            For n = 1 To p ' undo randomly used cards from opps
                                                UsedCards(OppHand(n).p1) = False
                                                UsedCards(OppHand(n).p2) = False
                                            Next
                                        Next ' end of test run
                                        
                                        ' test result (log)
                                        AveEV = (TotalEV / TestCount) ' range = 0-1
                                        
                                    Next ' end of all opp test runs for this 7-card hand
                                    
                                    UsedCards(g) = False
                                Next
                                UsedCards(f) = False
                            Next
                            UsedCards(e) = False
                        Next
                        UsedCards(d) = False
                    Next
                    UsedCards(c) = False
                Next
                UsedCards(b) = False
            Next
            UsedCards(a) = False
        Next
    
    End Function

  15. #15
    PowerPoster
    Join Date
    Nov 2002
    Location
    Manila
    Posts
    7,629

    Re: Need a Faster Poker Evaluation Method

    What's a board?

  16. #16

    Thread Starter
    New Member
    Join Date
    Apr 2007
    Posts
    12

    Re: Need a Faster Poker Evaluation Method

    A "board" consists of the 5 community cards that are dealt in Texas Holdem. My usage, however, is refering to the 7-cards formed by these 5 plus the player's own 2 cards (because they interact). Perhaps it is more accurate to say that there are 134M known "situations" that can occur by the end of a hand (there are actually many more because other player's cards are unknown, but for building a calculator we cannot make assumptions unless we are modeling players, which at this time I am not).

  17. #17
    PowerPoster
    Join Date
    Nov 2002
    Location
    Manila
    Posts
    7,629

    Re: Need a Faster Poker Evaluation Method

    So your trying to develop the AI? So its intelligent enough to go for it or fold based on an incomplete hand (less than 7), correct?

    Why not just base it on the current hand? Those with pairs or trios would obviously have better chances compared to those striving for 5 card dependent combinations.

  18. #18
    PowerPoster
    Join Date
    Nov 2002
    Location
    Manila
    Posts
    7,629

    Re: Need a Faster Poker Evaluation Method

    Also, another simple metric for texas would be "how many of my two cards am I using for the best hand possible?" If the answer is zero or your only using held cards as kickers then you have no advantage over the other players even without checking the probabilities.

    Another metric would be how much effect does your held cards have since its your advantage over other players. Such as considering only public cards best possible is a pair but when you include your 2 cards it becomes a straight then the effect considerable and worth bidding for. Your method for assigning weight to hand should not be limited to minimum 5 cards e.g. can handle two/three cards (held cards not yet included) with best possible pair/trio respectively.

    Last metric would be how far away from you from the ideal hand (for straight flushes, quadro,etc) with how many draws left. If your just one card away with 2 draws then you still have a chance.

    I suggested this cause the probabilities isn't the only metric. Where the cards are located (private/public) makes/breaks the advantage.
    Last edited by leinad31; May 3rd, 2007 at 07:42 AM.

  19. #19

    Thread Starter
    New Member
    Join Date
    Apr 2007
    Posts
    12

    Re: Need a Faster Poker Evaluation Method

    You do have some good insights into poker. All of those things make sense to consider for a human player, but for my program it has to have a quantitative way to calculate choices. In most cases, what has worked well for me is to make choices based on a certain ratio.

    EVRatio = (mywin% / (100% / PlayerCount))

    mywin% varies from 0-100% based on the cards in play (as the end of the hand approaches, there is less variance in the final probability). PlayerCount varies from 2-10 players. At a point in time, when I make a decision it is based on the EVRatio generally.

    if EVRatio >= RaiseEV then Action.Raise
    else if EVRatio >= 1.0 and mywin% > PotOdds then Action.Call
    else Check/Fold

    If we make the assumption that players are calling to the river with random hands, then we would call/raise any hand with EVRatio > 1.0. However, this is not true as players selectively play cards. My search for an optimal play algorithm is basically a simulation of 10 players with various EVRatio tolerances ("tightness"/"looseness"). Optimal play style is found when all players converge on a particular strategy. From previous data, preflop play suggests that ~20% of hands are profitable in professional tables with 10 players; this roughly equates to an EV tolerance of 1.3 preflop. The number of players will affect the EVRatio; there is a tendency for it to move toward 1.0 as the number of players decreases (applies to preflop only). What I need to figure out is the optimal tolerance after the flop based on the situation.

    Note that optimal play refers to a strategy that considers win% and pot odds only, and does not attempt to read or predict opponents. Most people will deviate from the optimal play, and any deviation will result in their loss to you over the long run. This strategy does not maximize winnings, but it is designed to protect against losses and volatility based on bad luck. It applies only to limit poker, as no limit creates a "risk of ruin" scenario when players go all-in and must be handed using more complex methods.

    I got my simulator up and running. I'm now waiting for it to converge on an optimal strategy.

    I found some information that is useful for optimization: http://www.xtremevbtalk.com/archive/.../t-278840.html
    Last edited by BinaryMan; May 4th, 2007 at 03:29 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width