[SUMMARY] Texas Hold'Em (#24)

People wrote quite a bit of code to solve this quiz. I don't think it's all
that tough, but there are quite a few combinations to check for, which seemed to
increase the line count of the solutions.

There was something interesting in all the solutions though, so I do recommend
browsing through them if you haven't already. I know I'm always saying that. I
guess it's always true.

I'm going to show Patrick Hurley's solution below. Patrick resubmitted just to
defend against my rant about how programs should stay within an 80 character
line limit. My argument wasn't meant as an attack on any submissions, but I
still appreciate Patrick's efforts. Here's the start of the code:

  #!ruby -w

  class Card
    SUITS = "cdhs"
    FACES = "L23456789TJQKA"
    SUIT_LOOKUP = {
      'c' => 0,
      'd' => 1,
      'h' => 2,
      's' => 3,
      'C' => 0,
      'D' => 1,
      'H' => 2,
      'S' => 3,
    }
    FACE_VALUES = {
      'L' => 1, # this is a magic low ace
      '2' => 2,
      '3' => 3,
      '4' => 4,
      '5' => 5,
      '6' => 6,
      '7' => 7,
      '8' => 8,
      '9' => 9,
      'T' => 10,
      'J' => 11,
      'Q' => 12,
      'K' => 13,
      'A' => 14,
    }

    def Card.face_value(face)
      if (face)
        FACE_VALUES[face] - 1
      else
        nil
      end
    end

    def build_from_string(card)
      build_from_face_suit(card[0,1], card[1,1])
    end

    def build_from_value(value)
      @value = value
      @suit = value / FACES.size()
      @face = (value % FACES.size())
    end

    def build_from_face_suit(face, suit)
      @face = Card::face_value(face)
      @suit = SUIT_LOOKUP[suit]
      @value = (@suit * FACES.size()) + (@face - 1)
    end

    def build_from_face_suit_values(face, suit)
      build_from_value((face - 1) + (suit * FACES.size()))
    end

    # got a little carried away with this constructor :wink:
    def initialize(*value)
      if (value.size == 1)
        if (value[0].respond_to?(:to_str))
          build_from_string(value[0])
        elsif (value[0].respond_to?(:to_int))
          build_from_value(value[0])
        end
      elsif (value.size == 2)
        if (value[0].respond_to?(:to_str) &&
            value[1].respond_to?(:to_str))
          build_from_face_suit(value[0], value[1])
        elsif (value[0].respond_to?(:to_int) &&
               value[1].respond_to?(:to_int))
          build_from_face_suit_values(value[0], value[1])
        end
      end
    end

    attr_reader :suit, :face, :value

    def to_s
      FACES[@face].chr + SUITS[@suit].chr
    end
  end
  
  # ...

That's the Card class Patrick uses for tracking individual cards. It looks like
a lot of code, but it's mostly a single constructor that accepts many different
forms of initialization. initialize() breaks down the parameters and hands them
off to the various build_from_... methods. Those build methods should probably
be private, leaning on initialize() as their interface. Once you get past
construction, you'll see that Card just contains a suit, face, and value.
Glance at build_from_face_suit() to see how those break down.

You can see it above and a little more below, but this code has a little
creeping featurism. Patrick was clearly building for the future with the card
handling classes. That's probably a safe bet as card quizzes are fairly common.
Dave Burt reused code from his Blackjack solution this time around. All I'm
saying is, don't be surprised if you see a handful of things in here that never
get used. Agile purists bare with us...

Let's move on to Deck objects:

  # ...

  class Deck
    def shuffle
      deck_size = @cards.size
      (deck_size * 2).times do
        pos1, pos2 = rand(deck_size), rand(deck_size)
        @cards[pos1], @cards[pos2] = @cards[pos2], @cards[pos1]
      end
    end

    def initialize
      @cards = []
      Card::SUITS.each_byte do |suit|
        # careful not to double include the aces...
        Card::FACES[1..-1].each_byte do |face|
          @cards.push(Card.new(face.chr, suit.chr))
        end
      end
      shuffle()
    end

    def deal
      @cards.pop
    end

    def empty?
      @cards.empty?
    end
  end

  # ...

initialize() just creates and shuffles a deck. deal() pops a card and empty?()
tells you if there are any left. If you read shuffle(), you'll see that it's
just a bunch of random swaps. Not sure why Patrick went this way. I believe
the standard Ruby shuffling idiom is:

  @cards.sort_by { rand }

On to the Hand class, but let's take this one in slices:

  # ...

  class Hand
    def initialize(cards = [])
      if (cards.respond_to?(:to_str))
        @hand = cards.scan(/\S\S/).map { |str| Card.new(str) }
      else
        @hand = cards
      end
    end
    attr_reader :hand

    # ...

initialize() just builds new Hand objects from the lines of input in the quiz by
scan()ing for the two character format. You can also build a Hand from an Array
of Card objects. Then there's the accessor to get them back.

    # ...
  
    def face_values
      @hand.map { |c| c.face }
    end
      
    def by_suit
      Hand.new(@hand.sort_by { |c| [c.suit, c.face] }.reverse)
    end
      
    def by_face
      Hand.new(@hand.sort_by { |c| [c.face, c.suit] }.reverse)
    end

    # ...

You can use the above methods to request hands by face_values(), by_suit(), or
by_face(). Note that both of the by_... sorts also sort by the other value, as a
secondary condition.

    # ...
      
    def =~ (re)
      re.match(@hand.join(' '))
    end
      
    def arrange_hand(md)
        hand = if (md.respond_to?(:to_str))
          md
        else
          md[0] + ' ' + md.pre_match + md.post_match
        end
        hand.gsub!(/\s+/, ' ')
        hand.gsub(/\s+$/,'')
    end
      
    # ...

The first method here is an operator overload to allow using regular expressions
on Hand objects. The second method returns a hand string in an order specified
by a MatchData object (the else clause). Whatever cards were matched are put
first, follow by cards preceding the match, and finally trailing cards. This
floats a matched "hand" to the front of the string while keeping the ordering
for any non-matched cards. arrange_hand() can also be called with a string
order (the if clause), but it doesn't do much in these cases except clean up
spacing issues.

From here, we start to get into hand matching code:

    # ..
    
    def royal_flush?
      if (md = (by_suit =~ /A(.) K\1 Q\1 J\1 T\1/))
        [[10], arrange_hand(md)]
      else
        false
      end
    end
    
    # ...

This method looks for the coveted royal flush. First it calls by_suit() to
order the cards. Remember that will order suits first, then faces. That makes
it trivial to spot the pattern with a Regexp. When found, royal_flush?()
returns a hand ranking number and the properly arranged hand in an Array, which
is of course a true value in Ruby. false is used when no match is found.

The code then pauses to define a couple more helper methods for spotting the
other hands:

    # ...
    
    def delta_transform(use_suit = false)
      aces = @hand.select { |c| c.face == Card::face_value('A') }
      aces.map! { |c| Card.new(1,c.suit) }
      
      base = if (use_suit)
        (@hand + aces).sort_by { |c| [c.suit, c.face] }.reverse
      else
        (@hand + aces).sort_by { |c| [c.face, c.suit] }.reverse
      end
      
      result = base.inject(['',nil]) do |(delta_hand, prev_card), card|
        if (prev_card)
          delta = prev_card - card.face
        else
          delta = 0
        end
        # does not really matter for my needs
        delta = 'x' if (delta > 9 || delta < 0)
        delta_hand += delta.to_s + card.to_s + ' '
        [delta_hand, card.face]
      end
      
      # we just want the delta transform, not the last cards face too
      result[0].chop
    end
    
    # ...

Dave Burt asked on Ruby Talk what delta_transform() does. Here's the author's
own response:

  The delta transform creates a version of the cards where the delta
  between card values is in the string, so a regexp can then match a
  straight and/or straight flush - I used regexp to match all my cases
  with appropriate sort and/or transforms.

Because that's probably easier to understand when you see it, here's a typical
return value from delta_tranform():

  "0Jh 38h xJd 38d 44d 13d x8c"

The extra character preceding each card shows the drop from the previous card
rank. The jack is the first card, so it shows a 0 drop. The eight is then down
3, as shown. Tracking increases isn't needed in the solution, so the code just
punts with an x character, as seen with the next jack. All this is just
building up a handy string for pattern matching.

Note that the first couple of lines of delta_transform() add a "low ace" to the
back of the hand for each ace found in the hand. This is for spotting low
straights, but the magic must eventually be undone by:

    # ...
    
    def fix_low_ace_display(arranged_hand)
      # remove card deltas (this routine is only used for straights)
      arranged_hand.gsub!(/\S(\S\S)\s*/, "\\1 ")
      
      # Fix "low aces"
      arranged_hand.gsub!(/L(\S)/, "A\\1")
      
      # Remove duplicate aces (this will not work if you have
      # multiple decks or wild cards)
      arranged_hand.gsub!(/((A\S).*)\2/, "\\1")
      
      # cleanup white space
      arranged_hand.gsub!(/\s+/, ' ')
      # careful to use gsub as gsub! can return nil here
      arranged_hand.gsub(/\s+$/, '')
    end
    
    # ...

This just restores the ace back to its usual display.

Now we can see both of those methods put to good use:

    # ...
    
    def straight_flush?
      if (md = (/.(.)(.)(?: 1.\2){4}/.match(delta_transform(true))))
        high_card = Card::face_value(md[1])
        arranged_hand = fix_low_ace_display(md[0] + ' ' +
            md.pre_match + ' ' + md.post_match)
        [[9, high_card], arranged_hand]
      else
        false
      end
    end
    
    # ...

This is similar in function to royal_flush?(), but you can see that it uses
delta_transform() to make it easy to match a straight. fix_low_ace_display() is
called on the result, before the method returns.

The rest of the hand methods are very similar. Sort the cards, match a pattern,
return rank and hand or false. Here they are, without further explanation:

    # ...
    
    def four_of_a_kind?
      if (md = (by_face =~ /(.). \1. \1. \1./))
        # get kicker
        (md.pre_match + md.post_match).match(/(\S)/)
        [
          [8, Card::face_value(md[1]), Card::face_value($1)],
          arrange_hand(md)
        ]
      else
        false
      end
    end
      
    def full_house?
      if (md = (by_face =~ /(.). \1. \1. (.*)(.). \3./))
        arranged_hand = arrange_hand(md[0] + ' ' +
            md.pre_match + ' ' + md[2] + ' ' + md.post_match)
        [
          [7, Card::face_value(md[1]), Card::face_value(md[3])],
          arranged_hand
        ]
      elsif (md = (by_face =~ /((.). \2.) (.*)((.). \5. \5.)/))
        arranged_hand = arrange_hand(md[4] + ' ' + md[1] + ' ' +
            md.pre_match + ' ' + md[3] + ' ' + md.post_match)
        [
          [7, Card::face_value(md[5]), Card::face_value(md[2])],
          arranged_hand
        ]
      else
        false
      end
    end
      
    def flush?
      if (md = (by_suit =~ /(.)(.) (.)\2 (.)\2 (.)\2 (.)\2/))
        [
          [
            6,
            Card::face_value(md[1]),
            *(md[3..6].map { |f| Card::face_value(f) })
          ],
          arrange_hand(md)
        ]
      else
        false
      end
    end
      
    def straight?
      result = false
      if hand.size > 5
        transform = delta_transform
        # note we can have more than one delta 0 that we
        # need to shuffle to the back of the hand
        until transform.match(/^\S{3}( [1-9x]\S\S)+( 0\S\S)*$/) do
          transform.gsub!(/(\s0\S\S)(.*)/, "\\2\\1")
        end
        if (md = (/.(.). 1.. 1.. 1.. 1../.match(transform)))
          high_card = Card::face_value(md[1])
          arranged_hand = fix_low_ace_display(md[0] + ' ' +
              md.pre_match + ' ' + md.post_match)
          result = [[5, high_card], arranged_hand]
        end
      end
    end
      
    def three_of_a_kind?
      if (md = (by_face =~ /(.). \1. \1./))
        # get kicker
        arranged_hand = arrange_hand(md)
        arranged_hand.match(/(?:\S\S ){3}(\S)\S (\S)/)
        [
          [
            4,
            Card::face_value(md[1]),
            Card::face_value($1),
            Card::face_value($2)
          ],
          arranged_hand
        ]
      else
        false
      end
    end
      
    def two_pair?
      if (md = (by_face =~ /(.). \1.(.*) (.). \3./))
        # get kicker
        arranged_hand = arrange_hand(md[0] + ' ' +
            md.pre_match + ' ' + md[2] + ' ' + md.post_match)
        arranged_hand.match(/(?:\S\S ){4}(\S)/)
        [
          [
            3,
            Card::face_value(md[1]),
            Card::face_value(md[3]),
            Card::face_value($1)
          ],
          arranged_hand
        ]
      else
        false
      end
    end
      
    def pair?
      if (md = (by_face =~ /(.). \1./))
        # get kicker
        arranged_hand = arrange_hand(md)
        arranged_hand.match(/(?:\S\S ){2}(\S)\S\s+(\S)\S\s+(\S)/)
        [
          [
            2,
            Card::face_value(md[1]),
            Card::face_value($1),
            Card::face_value($2),
            Card::face_value($3)
          ],
          arranged_hand
        ]
      else
        false
      end
    end
      
    def highest_card?
      result = by_face
      [[1, *result.face_values[0..4]], result.hand.join(' ')]
    end
    
    # ...

Now what we really need to know is which one of those hands was found. The code
for that isn't overly complex:

      # ...
      
      OPS = [
        ['Royal Flush', :royal_flush? ],
        ['Straight Flush', :straight_flush? ],
        ['Four of a kind', :four_of_a_kind? ],
        ['Full house', :full_house? ],
        ['Flush', :flush? ],
        ['Straight', :straight? ],
        ['Three of a kind', :three_of_a_kind?],
        ['Two pair', :two_pair? ],
        ['Pair', :pair? ],
        ['Highest Card', :highest_card? ],
      ]
      
      def hand_rating
        OPS.map { |op|
          (method(op[1]).call()) ? op[0] : false
        }.find { |v| v }
      end
      
      def score
        OPS.map { |op|
          method(op[1]).call()
        }.find([0]) { |score| score }
      end
      
      # ...

The OPS Array maps hand names to the method that will spot them. With that, you
call call either hand_rating() or score() which will walk the whole list of
tests, then return the first one that was true. hand_rating() returns the name
while score() returns the rank and hand Array from the hand method call.

Finally, Hand has a few more very basic helper methods:

    # ...
  
    def take_card(card)
      @hand.push(card)
    end

    def arranged_hand
      score[1] + " (#{hand_rating})"
    end

    def just_cards
      @hand.join(" ")
    end

    def to_s
      just_cards + " (" + hand_rating + ")"
    end
  end
  
  # ...

The only thing to notice there is the arranged_hand() is just a shell over
score() and hand_rating() and to_s() is a shell over just_cards() and
hand_rating().

The rest of Patrick's code goes on to build a complete game of Texas Hold'Em
that plays itself out round by round and displays results as it goes. This is
very interesting stuff, but it doesn't solve the quiz, the way I read it.
Luckily, a solution is easy to finish off from here. Here's my solution to the
quiz, using Partick's classes:

  # ...
  
  ### code by JEG2 ###
  if __FILE__ == $0
    best = nil
    results = []
    
    ARGF.each_line do |line|
      if line.length < 20 # they folded
        results << line.chomp
      else
        hand = Hand.new(line) # rank hand
        name = hand.hand_rating
        score, arranged = hand.score
        
        if best.nil? or (score[0] <=> best[0]) == 1 # track best
          best = [score[0], results.size]
        end

        results << "#{arranged} #{name}"
      end
    end
    
    # show results
    results.each_with_index do |e, index|
      puts(if index == best[1] then "#{e} (winner)" else e end)
    end
  end

That should be pretty straight forward by this point. I setup variables to
track the best hand and the complete results, parse input, handle folds, score
each hand, remembering to track the best so far, and finally out the results.
That funny compare, (score[0] <=> best[0]) == 1, is because the grade returned
by score is actually an Array of values and Array implements <=> but not >; go
figure. That gets me the following output for the quiz example:

  Ks Kd Kc 9s 9d 6d 3c Full house (winner)
  Ks Kd 9d 9c Ah 6d 3c Two pair
  Ac Qc Ks Kd 9d 3c
  9h 5s
  Kd 9d 6d 4d 2d Ks 3c Flush
  7s Ts Ks Kd 9d

While I'm showing output, check out this neat variation by Derek Wyatt:

  9d 9s Kd Ks Kc 3c 6d Full House (Kings over Nines) (winner)
  9d 9c Kd Ks Ah 3c 6d Two Pair (Kings and Nines)
  Ac Qc Ks Kd 9d 3c
  9h 5s
  Ks Kd 2d 4d 3c 6d 9d Pair (Kings)
  7s Ts Ks Kd 9d

I love the way it gives you extra details about the hand, but as you can see we
don't agree on hand number four. Don't sweat that though, seems everyone had a
good round of bug hunting for this one.

My thanks to all the card sharks out there. I also want to thank Patrick for
writing code I could figure out how to hijack. This summary was definitely a
team effort.

Tomorrow, Tymothy Byrd will hit you with a brain bender you and Ruby can work
together to solve...