Bridge Hands

August 11, 2015

This problem comes to us courtesy of The Awk Programming Language by Al Aho, Brian Kernighan and Peter Weinberger, where it appears as exercise 5-3. Their solution is on the next page. We represent the deck as an array of cards, with cards as integers from 51 (ace of spades) to 0 (deuce of clubs). We begin with a function that deals the deck and arranges the four hands in order:

(define (hands)
  (let loop ((deck (shuffle (range 52)))
             (hands (list)))
    (if (null? deck) hands
      (loop (drop 13 deck)
            (cons (sort > (take 13 deck)) hands)))))

The output of hands contains the four hands in order North, West, East, South. Then we write a function that prints the formatted output:

(define (print-hands hands)
  (define (blanks n) (display (make-string n #\space)))

  (blanks 20) (display "NORTH") (newline)
  (blanks 20) (display (cards "S" (car hands))) (newline)
  (blanks 20) (display (cards "H" (car hands))) (newline)
  (blanks 20) (display (cards "D" (car hands))) (newline)
  (blanks 20) (display (cards "C" (car hands))) (newline)

  (set! hands (cdr hands))

  (display "WEST") (blanks 36) (display "EAST") (newline)
  (display (cards "S" (car hands)))
    (blanks (- 40 (string-length (cards "S" (car hands)))))
    (display (cards "S" (cadr hands))) (newline)
  (display (cards "H" (car hands)))
    (blanks (- 40 (string-length (cards "H" (car hands)))))
    (display (cards "H" (cadr hands))) (newline)
  (display (cards "D" (car hands)))
    (blanks (- 40 (string-length (cards "D" (car hands)))))
    (display (cards "D" (cadr hands))) (newline)
  (display (cards "C" (car hands)))
    (blanks (- 40 (string-length (cards "C" (car hands)))))
    (display (cards "C" (cadr hands))) (newline)

  (set! hands (cddr hands))

  (blanks 20) (display "SOUTH") (newline)
  (blanks 20) (display (cards "S" (car hands))) (newline)
  (blanks 20) (display (cards "H" (car hands))) (newline)
  (blanks 20) (display (cards "D" (car hands))) (newline)
  (blanks 20) (display (cards "C" (car hands))) (newline))

The cards function returns a string containing the cards of the designated suit in the designated hand; it calls a function face to convert the card index to a face value:

(define (cards suit hand)
  (define (between lo hi) (lambda (x) (<= lo x hi)))
  (cond ((string=? suit "S")
          (string-join #\space (cons "S:"
            (map face (filter (between 39 51) hand)))))
        ((string=? suit "H")
          (string-join #\space (cons "H:"
            (map face (filter (between 26 38) hand)))))
        ((string=? suit "D")
          (string-join #\space (cons "D:"
            (map face (filter (between 13 25) hand)))))
        ((string=? suit "C")
          (string-join #\space (cons "C:"
            (map face (filter (between  0 12) hand)))))))


(define (face card)
  (case (modulo card 13)
    ((12) "A")
    ((11) "K")
    ((10) "Q")
    ((9)  "J")
    (else (number->string (+ (modulo card 13) 2)))))

You can run the program at http://ideone.com/IcpYvT, where you will also see the contributions from the Standard Prelude. Here’s a sample run:

> (print-hands (hands))
                    NORTH
                    S: A Q J 10 8
                    H: 5 4 2
                    D: 9
                    C: 10 7 6 2
WEST                                    EAST
S: 7                                    S: 6
H: Q J 7 6 3                            H: K 10 8
D: J 10 6 4 3                           D: A K Q 5
C: A 8                                  C: K 9 5 4 3
                    SOUTH
                    S: K 9 5 4 3 2
                    H: A 9
                    D: 8 7 2
                    C: Q J

I have no direct experience teaching beginning programmers, but I suspect this would make a good assignment partway through a first programming course, as the imperative version of the program uses arrays, loops, random numbers and a little bit of arithmetic, data types for the cards, sorting, and formatted output, and requires no input. I know some of my readers do teach beginning programmers. What do you think?

Pages: 1 2 3

10 Responses to “Bridge Hands”

  1. Paul said

    In Python.

    import random
    from itertools import groupby
    from operator import itemgetter
    
    VALUES = list("23456789TJQKA")
    VALUES[8] = "10"
    
    def random_hand():
        cards = list(range(52))
        random.shuffle(cards)
        return [sorted(cards[i*13:i*13+13], reverse=True) for i in range(4)]
     
    def nr_to_card(n):
        suit, value = divmod(n, 13)
        return "CDHS"[suit], VALUES[value]
    
    def hand(side):
        cards = [nr_to_card(c) for c in side]
        cc = groupby(cards, key=itemgetter(0))
        D = {k:"{:s}: {:s}".format(k, " ".join(v for s, v in g)) for k, g in cc}
        return [D[k] if k in D else k + ":" for k in "SHDC"]
        
    def print_bridge(hands):
        output = [[" "] * 80 for _ in  range(15)]
        pos = [(0, 20), (5, 0), (5, 40), (10, 20)]
        seats = ["NORTH", "WEST", "EAST", "SOUTH"]
        for (row, col), seat, hand_ in zip(pos, seats, hands):
            output[row][col:col+len(seat)] = seat
            for i, hi in enumerate(hand(hand_)):
                output[row+i+1][col:col+len(hi)] = hi
        for o in output:
            print ("".join(o))
            
    print_bridge(random_hand())
    
  2. Rutger said

    Good to see you recovered!
    Hereby my Python solution..

    import random
    
    suits = ["S", "H", "D", "C"]
    values = ["2","3","4","5","6","7","8","9","10","J","Q","K","A"]
    
    # returns tuples of (suit, value)
    def deck(shuffle=True):
        def card_number_to_card_tuple(n):
            suit, value = divmod(n, 13)
            return suits[suit], values[value]
    
        all_cards = range(52)
        if shuffle:
            random.shuffle(all_cards)
        return map(card_number_to_card_tuple, all_cards)
    
    # returns list of 4 hands, with each hand as a dict with k=suit , v=list of values for suit k
    def four_hands(deck):
        hands = []
        for hand in [deck[i*13:i*13+13] for i in range(4)]:
            d = dict(zip(suits, [[],[],[],[]]))
            for suit, value in hand:
                d.setdefault(suit, []).append(value)
            hands.append(d)
        return hands
     
    
    # prints output. Spent most time in that python format mini language..
    def print_formatted(four_hands):
        def hand_to_string(hand):
            return [suit + ': ' + " ".join(hand[suit]) for suit in suits]
    
        print ' '*20 + 'NORTH'
        for north in hand_to_string(four_hands[0]):
            print ' '*20 + north
        print '{:<40}{:}'.format('WEST', 'EAST')
        for west, east in zip(hand_to_string(four_hands[1]), hand_to_string(four_hands[2])):
            print '{:<40}{:}'.format(west, east)
        for south in hand_to_string(four_hands[3]):
            print ' '*20 + south
    
    
    print_formatted(four_hands(deck()))
    
  3. Andras said

    Scala:
    import scala.util.Random
    type Card = Tuple2[String, String]
    val color: Seq[String] = List(“S”, “H”, “D”, “C”)
    val numbers: Seq[String] = (2 to 10 map { _.toString }) ++ List(“J”, “Q”, “K”, “A”)
    def numberSorter(a: String, b: String): Boolean = numbers.indexOf(a) > numbers.indexOf(b)
    val cards: Seq[Card] = Random.shuffle(for (c <- color; n List(filter(x, “S”), filter(x, “H”), filter(x, “D”), filter(x, “C”)))
    val blockLines = blocks.map(x => (List(x._1) ++ x._2).map(x => x + ” ” * (20 – x.length())))
    val north = for (i <- 0 to 4) yield " " * 20 + blockLines(0)(i) + " " * 20
    val west_east = for (i <- 0 to 4) yield blockLines(1)(i) + " " * 20 + blockLines(2)(i)
    val south = for (i <- 0 to 4) yield " " * 20 + blockLines(3)(i) + " " * 20
    println((north ++ west_east ++ south).mkString("\r\n"))

  4. Andras said

    @Paul, @Rutger: could you please tell me how do you insert your code in such a beauty way? Which website do you use?

  5. Paul said

    @Andras.I am using method Third as mentioned in the “Howto: Posting Source Code”. You have to know the mnemonic for Scala (I guess it is scala). Further the tag is usefull if you want to use a link in your post. I hope this helps.

  6. Paul said

    @Andras, the “a href” tag messed up part of my reply. You only have to google for this tag and see how it is used.

  7. Andras said

    @Paul Thanks it works fine!

  8. Globules said

    A Haskell version.

    import Data.Function (on)
    import Data.List (groupBy, intercalate, sortOn)
    import Data.List.Split (chunksOf)
    import Data.Maybe (fromMaybe)
    import Data.Ord (Down(..))
    import System.Random
    import System.Random.Shuffle (shuffle')
    import Control.Monad (liftM)
    
    shuffle :: (RandomGen r) => [a] -> r -> [a]
    shuffle xs = shuffle' xs (length xs)
    
    -- A standard deck of 52 cards, where each card is a pair consisting of a suit
    -- and a rank.
    deck :: [(Int, Int)]
    deck = [(s, r) | s <- [0..3], r <- [0..12]]
    
    -- Deal hands of 13 cards.  Each hand consists of ordered lists of ranks grouped
    -- by suit.
    deal :: [(Int, Int)] -> [[(Int, [Int])]]
    deal = map (map suitify . groupBy ((==) `on` fst) . sortOn Down) . chunksOf 13
      where suitify (rss@((r, _) : _)) = (r, map snd rss)
    
    showSuit :: Int -> String
    showSuit = (["S", "H", "D", "C"] !!)
    
    showRank :: Int -> String
    showRank = (ranks !!)
      where ranks = [ "2", "3",  "4", "5", "6", "7",
                      "8", "9", "10", "J", "Q", "K", "A"]
    
    -- Each element of the result is one suit letter followed by the card ranks.
    showHand :: [(Int, [Int])] -> [String]
    showHand cs = map (\s -> showSuit s ++ ": " ++ showRanks (ranks s)) [0..3]
      where ranks s = fromMaybe [] $ lookup s cs
            showRanks = unwords . map showRank
    
    printHand :: [String] -> IO ()
    printHand = putStrLn . intercalate "\n"
    
    -- Pad all the strings to the length of the longest.
    padR :: [String] -> [String]
    padR xs = let maxLen = maximum $ map length xs
              in map (padTo maxLen) xs
      where padTo mx s = s ++ replicate (mx - length s) ' '
    
    -- Prepend all the strings with spaces.
    padL :: Int -> [String] -> [String]
    padL n = map (replicate n ' ' ++)
    
    -- Append corresponding elements of the lists.
    followedBy :: [[a]] -> [[a]] -> [[a]]
    followedBy = zipWith (++)
    
    main :: IO ()
    main = do
      hands <- liftM (map showHand . deal . shuffle deck) getStdGen
      let [n, s, e, w] = zipWith (:) ["NORTH", "SOUTH", "EAST", "WEST"] hands
      printHand $ padL 20 n
      printHand $ padR w `followedBy` padL 30 e
      printHand $ padL 20 s
    
    $ ./bridge 
                        NORTH
                        S: K Q 9 5
                        H: Q 10
                        D: K 9 8 3
                        C: 6 5 4
    WEST                                     EAST
    S: A 10 8 7                              S: J 6 3
    H: K 8 2                                 H: J 9 7 6 3
    D: 6 4                                   D: Q J 7
    C: 9 8 3 2                               C: A Q
                        SOUTH
                        S: 4 2
                        H: A 5 4
                        D: A 10 5 2
                        C: K J 10 7
    
  9. […] Bridge hands problem in C++ […]

  10. […] bridge hands problem on programming praxis in Common […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: