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 ;-) 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...