## Texas Hold ‘Em

### March 23, 2010

This exercise is harder than it looks. We begin with the code to rank hands. A card is represented as a two-character string, with pips in the first character and suit in the second. The four possible suits are `#\S` spades, `#\H` hearts, `#\C` clubs and `#\D` diamonds. For pips, digits represent themselves, with `#\T` ten, `#\J` jack, `#\Q` queen, `#\K` king, and `#\A` ace. For instance, `"AH"` is the ace of hearts. The functions below identify the pips and suit:

```(define pips   (let ((ps (map cons (map char-upcase           (string->list "23456789TJQKA")) (range 2 15))))     (lambda (card)       (cdr (assoc (char-upcase (string-ref card 0)) ps)))))```

`(define (suit card) (char-upcase (string-ref card 1)))`

A flush has all five suits the same:

`(define (flush? hand) (apply char=? (map suit hand)))`

There are two kinds of straights, with ace high and ace low; `straight` returns the number of the highest-ranking card in a hand, or `#f` if no straight is present:

```(define (straight cs)   (cond ((equal? cs '(14 5 4 3 2)) 5)         ((apply = 1 (map - (take 4 cs) (cdr cs))) (car cs))         (else #f)))```

The `same` function looks for n cards with the same rank; it returns the rank, or `#f` if the hand does not contain n cards of the same rank:

```(define (same n cards)   (let ((len (length cards)))     (let loop ((i 0) (cards cards))       (cond ((negative? (- len n i)) #f)             ((apply = (take n cards)) (car cards))             (else (loop (+ i 1) (cdr cards)))))))```

Now we can look at the code to rank a hand. `Rank` returns a list of numbers. The first number is the type of hand, ranging from 9 for a straight flush to 1 for a high card. The remaining numbers are tie-breakers, from highest to lowest. Note the difference between a hand (a list of five cards) and a list of cards (a list of five pips, sorted in descending order):

```(define (rank hand)   (let ((cards (sort > (map pips hand))))     (cond ((straight cards) =>             (lambda (c)               (if (flush? hand)                   (list 9 c)                   (list 5 c))))           ((flush? hand) (cons 6 cards))           ((same 4 cards) =>             (lambda (c)               (list 8 c (car (remove c cards)))))           ((same 3 cards) =>             (lambda (c)               (let* ((cs (remove c cards))                      (k (same 2 cs)))                 (if k (list 7 c k) (cons* 4 c cs)))))           ((same 2 cards) =>             (lambda (c)               (let* ((cs (remove c cards))                      (k (same 2 cs)))                 (if k (list 3 c k (car (remove k cs)))                       (cons* 2 c cs)))))           (else (cons 1 cards)))))```

The order of the `cond` clauses is significant. The first clause identifies straights and straight flushes. The second clause identifies flushes. The next three clauses identify quads, trips and pairs; the trips clause also looks for full houses, and the pairs clause also looks for two pairs. The final clause catches anything else. The `rank` function is complicated enough to deserve a test:

```(define (test-rank)   (assert (rank '("AH" "KH" "QH" "JH" "TH")) '(9 14))   (assert (rank '("7H" "7C" "3H" "7S" "7D")) '(8 7 3))   (assert (rank '("TH" "JC" "TS" "JD" "TC")) '(7 10 11))   (assert (rank '("4H" "7H" "AH" "KH" "9H")) '(6 14 13 9 7 4))   (assert (rank '("AH" "2C" "3S" "4D" "5H")) '(5 5))   (assert (rank '("9C" "4S" "KD" "9D" "9H")) '(4 9 13 4))   (assert (rank '("6D" "6C" "8H" "TD" "8D")) '(3 8 6 10))   (assert (rank '("9C" "3S" "4D" "7C" "3D")) '(2 3 9 7 4))   (assert (rank '("4C" "KD" "8S" "6D" "2D")) '(1 13 8 6 4 2)))```

Two hands are compared by running through the lists returned by `rank`; the first point of mis-match determines the winner:

```(define (lt? hand1 hand2)   (let loop ((h1 (rank hand1)) (h2 (rank hand2)))     (cond ((or (null? h1) (null? h2)) #f)           ((< (car h1) (car h2)) #t)           ((< (car h2) (car h1)) #f)           (else (loop (cdr h1) (cdr h2))))))```

Now we return to the original problem. Here is a function that enumerates all combinations of n items taken from a list; it’s simple and elegant, and dates to the folklore of computing. We use `combs` to generate all possible five-card hands from a list of seven cards:

```(define (combs n xs)   (cond ((zero? n) '(()))         ((null? xs) '())         (else (append (map (lambda (c) (cons (car xs) c))                            (combs (- n 1) (cdr xs)))                       (combs n (cdr xs))))))```

To find the best hand, loop through all the possibilities:

```(define (best-hand cards)   (let ((hands (combs 5 cards)))     (let loop ((best (car hands)) (hands (cdr hands)))       (cond ((null? hands) best)             ((lt? best (car hands))               (loop (car hands) (cdr hands)))             (else (loop best (cdr hands)))))))```

Here’s an example, two pairs of aces and eights, the “dead man’s hand” held in a game of five-card draw by Wild Bill Hickock at the time of his murder:

```> (best-hand '("AC" "JD" "8S" "8C" "AS" "3D" "4D")) ("AC" "JD" "8S" "8C" "AS")```

We used `take`, `drop`, `range`, `cons*`, `remove`, `sort` and `assert` from the Standard Prelude. You can run the code at http://programmingpraxis.codepad.org/tdgaPIju.

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

```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) + 2

def suit(card):
return card

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 == ranks + 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")

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.