```I will concur this one was fun, although it took me more like 4 hours
than the one I planned on spending on it. I think this a complete
solution. There is no smarts in the folding logic, but the player
class is the place to make it smarter. It displays each round
(unranked, but with current "score/status" and then when the game is
over it rearranges the hands to display them based upon their best
hand and ranks them from best to worst (folded hands are just randomly
layed out at the bottom.

Thanks as always for the quiz.
Patrick

----------------------------------------------------------------------------------

#!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

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

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

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

def =~ (re)
re.match(@hand.join(' '))
end

def royal_flush?
if (md = (by_suit =~ /A(.) K\1 Q\1 J\1 T\1/))
[[10], (md[0] + ' ' + md.pre_match + ' ' +
md.post_match).gsub(/\s+/, ' ')]
else
false
end
end

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
delta = 'x' if (delta > 9 || delta < 0)   # does not really
matter for my needs
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]
end

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+/, ' ')
arranged_hand.gsub(/\s+\$/, '')   # careful to use gsub as gsub!
can return nil here
end

def straight_flush?
if (md = (/.(.)(.) 1.\2 1.\2 1.\2 1.\2/.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

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

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?
if (md = (/.(.). 1.. 1.. 1.. 1../.match(delta_transform)))
high_card = Card::face_value(md[1])
arranged_hand = fix_low_ace_display(md[0] + ' ' + md.pre_match +
' ' + md.post_match)
[[5, high_card], arranged_hand]
else
false
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)/)
# (' ' + md.pre_match + md.post_match).match(/^\s+(\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

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

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

class Player
def initialize(name, deck)
@name = name
@hand = Hand.new
2.times { @hand.take_card(deck.deal()) }
@folded = false
end

def folded?
@folded
end

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

def fold?(players)
unless (folded?)
if (players)
folded_count = players.inject(0) { |count, p| (p.folded?) ?
count + 1 : count }
@folded = rand(players.size - folded_count) > (folded_count)
else
@folded = (rand(10) <= 1)
end
end
folded?

end

def score
(folded?) ? [[0]] : @hand.score
end

def arranged_hand
@name + ' ' +
if (folded?)
@hand.just_cards + ' (folded)'
else
@hand.arranged_hand
end
end

def to_s
@name + ' ' +
if (folded?)
@hand.just_cards + ' (folded)'
else
@hand.to_s
end
end

def <=>(other)
score <=> other.score
end
end

class TexasHoldEm
def initialize(player_count)
@deck = Deck.new
@common_cards = Array.new(5) { @deck.deal }
@players = (1..player_count).inject([]) { |players, num| players
<< Player.new("Player #{num}", @deck) }
end

def game_over?
@common_cards.empty?
end

def play_round
unless game_over?
card = @common_cards.pop
@players.each do |p|
unless p.fold?(@players)
p.take_card(card)
end
end
end

game_over?
end

def rank_players!
@players = @players.sort.reverse
end

def arranged_players
@players.inject('') { |result, player| result +=
player.arranged_hand + "\n" }
end

def to_s
@players.join("\n")
end
end

if __FILE__ == \$0
srand

game = TexasHoldEm.new(5)
round = 1
until game.game_over?
puts "\nRound #{round}"
puts game
game.play_round
round += 1
end
puts "\nRound #{round}"
puts game

game.rank_players!
puts "\nFinal Ranking"
puts game.arranged_players
end

```