Texas Hold ‘Em

March 23, 2010

Texas Hold ‘Em is a variant of poker. After the ante, two cards are dealt face down to each player and a round of betting ensues. Then three community cards (the flop), which may be used by any player, are dealt, followed by another round of betting. Then a single community card (the turn) is dealt, followed by a round of betting, followed by the final community card (the river) with its round of betting. In total, each player has two concealed cards plus five community cards, and the player with the best five-card hand of any of the seven cards available to him is the winner, claiming the pot. Poker hands are ranked, from highest to lowest, according to the following rules:

  1. Straight flush: Five cards in sequence, all of the same suit. The ace can be played either high or low. Between two straight flushes, the highest-ranking card wins; two straight flushes with the same sequence tie.
  2. Four of a kind: Four cards of the same rank, plus one unmatched card. Highest-ranking quads beat lower-ranking quads; if both are the same, the highest-ranking kicker wins, otherwise there is a tie.
  3. Full house: Three matching cards of one rank, and two matching cards of another. The hand with the highest-ranking triplet wins; if both are the same, the highest-ranking pair wins, otherwise there is a tie.
  4. Flush: Five cards of the same suit. Two flushes are compared by the ranks of their cards; the highest-ranking card wins, in the event of a tie the second highest-ranking card wins, and so on until a difference is found. If both flushes have cards of the same rank, they tie.
  5. Straight: Five cards in sequence. The ace may be used either high or low. Two straights are ranked by their highest cards; if their highest cards are equal, they tie.
  6. Three of a kind: Three cards of the same rank, plus two unmatched cards. The highest-ranking triplet wins; if they are the same, the kickers are compared to break the tie.
  7. Two pair: Two cards with matched rank, plus two more cards of matched rank, plus one unmatched card. Two-pair hands are ranked on the highest pair, then the lowest pair, and finally the kicker.
  8. One pair: Two cards with matched rank, plus three more unmatched cards. The highest-ranking pair wins, with the three kickers used to break ties.
  9. High card: Five unmatched cards. Hands are ranked on their highest card, followed by their second-highest card, and so on.

Your task is to write a program that selects the best five-card hand from a list of seven cards. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.

Pages: 1 2

5 Responses to “Texas Hold ‘Em”

  1. […] Praxis – Texas Hold ‘Em By Remco Niemeijer In today’s Programming Praxis exercise we have to write a program to rank poker hands. The provided Scheme […]

  2. Remco Niemeijer said

    My Haskell solution (see http://bonsaicode.wordpress.com/2010/03/23/programming-praxis-texas-hold-em/ for a version with comments):

    import Data.Char
    import Data.List
    import qualified Data.List.Key as K
    
    toCard :: String -> (Int, Char)
    toCard ~[v,s] = maybe undefined (flip (,) $ toUpper s) .
                    lookup v $ zip "23456789TJQKA" [2..]
    
    flush :: [(Int, Char)] -> Bool
    flush = (== 1) . length . K.group snd
    
    straight :: [Int] -> Bool
    straight xs = (l:r) == [2,3,4,5,14] || isPrefixOf r [l + 1..]
                  where (l:r) = reverse $ map fromEnum xs
    
    same :: [Int] -> [Int]
    same = reverse . sort . map length . group
    
    rank :: [String] -> (Int, [Int])
    rank xs | straight s && flush cs     = (9, s)
            | elem 4 $ same s            = (8, s)
            | same s == [3,2]            = (7, s)
            | flush cs                   = (6, s)
            | straight s                 = (5, s)
            | elem 3 $ same s            = (4, s)
            | elem 2 . delete 2 $ same s = (3, s)
            | elem 2 $ same s            = (2, s)
            | otherwise                  = (1, s)
            where s = reverse . sort $ map fst cs
                  cs = map toCard xs
    
    choose :: Int -> [a] -> [[a]]
    choose 0 _      = [[]]
    choose _ []     = []
    choose n (x:xs) = map (x:) (choose (n-1) xs) ++ choose n xs
    
    bestHand :: [String] -> [String]
    bestHand = K.maximum rank . choose 5
    
  3. Remco Niemeijer said

    Phil pointed out a bug in my initial version where n-of-a-kind hands were compared on the values of the remaining cards. Here’s a fixed version:

    import Data.Char
    import Data.List
    import qualified Data.List.Key as K
    
    toCard :: String -> (Int, Char)
    toCard ~[v,s] = maybe undefined (flip (,) $ toUpper s) .
                    lookup v $ zip "23456789TJQKA" [2..]
    
    flush :: [(Int, Char)] -> Bool
    flush = (== 1) . length . K.group snd
    
    straight :: [Int] -> Bool
    straight xs = (l:r) == [2,3,4,5,14] || isPrefixOf r [l + 1..]
                  where (l:r) = reverse $ map fromEnum xs
    
    same :: [Int] -> [Int]
    same = map length . group
    
    rank :: [String] -> (Int, [Int])
    rank xs | straight s && flush cs     = (9, s)
            | elem 4 $ same s            = (8, s)
            | same s == [3,2]            = (7, s)
            | flush cs                   = (6, s)
            | straight s                 = (5, s)
            | elem 3 $ same s            = (4, s)
            | elem 2 . delete 2 $ same s = (3, s)
            | elem 2 $ same s            = (2, s)
            | otherwise                  = (1, s)
            where s = concat . reverse . K.sort (\x -> (length x, head x)) .
                      group . sort $ map fst cs
                  cs = map toCard xs
    
    choose :: Int -> [a] -> [[a]]
    choose 0 _      = [[]]
    choose _ []     = []
    choose n (x:xs) = map (x:) (choose (n-1) xs) ++ choose n xs
    
    bestHand :: [String] -> [String]
    bestHand = K.maximum rank . choose 5
    
  4. Mike said

    A Python version:

    from itertools import combinations, groupby, product, starmap
    from random import sample
    
    def rank(card):
        return "23456789TJQKA".index(card[0]) + 2
    
    def suit(card):
        return card[1]
    
    def score(hand):
        """
        The cards are grouped by rank.  Based on the grouping, make a list
        of the length of each group and a list of the rank of each group.
        
        The score of the hand is a tuple containing the two lists
            ex: AC KD 2S KD AH  -> ((2,2,1), (14,13,2))
        
        Made-up groups are used for straights and flushes so they sort properly
            triple (3,1,1) < straight (3,1,2) < flush (3,1,3)
                   < four of a kind (4,1) < straight flush (4,2)
        """
        
        ranks = sorted( map( rank, hand ), reverse=True )
        grouper = ( ( len( list( g ) ), r ) for r,g in groupby( ranks ) )
        groups, group_ranks = zip( *sorted( grouper, reverse=True ) )
    
        if groups == (1,1,1,1,1):
            flush = len( set( map( suit, hand ) ) ) == 1
            straight = ranks[0] == ranks[4] + 4 or ranks == [13,5,4,3,2]
    
            if straight and flush:
                groups = (4,2)
            elif flush:
                groups = (3,1,3)
            elif straight:
                groups = (3,1,2)
    
        return groups, group_ranks
    
    # examples
    fmt = "{deal:^42} {best:^30}"
    print fmt.format(deal="Deal", best="Best")
    
    # dead man's hand
    deal = "AC JD 8S 8C AS 3D 4D".split()
    best = max( combinations( deal, 5 ), key=score )
    print fmt.format(deal=deal, best=best)
    
    #random hands
    deck = map( ''.join, product( '23456789TJQKA', 'CDHS' ) )
    for n in range(10):
        deal = sample( deck, 7 )
        best = max( combinations( deal, 5 ), key=score )
        print fmt.format(deal=deal, best=best)
    
    
  5. Bill B said

    My solution to Texas Hold’Em (ruby) is on the long side, so it can be seen here http://codingjunkie.net/?p=326.

Leave a comment