On Mar 23, 2005, at 8:20 AM, Patrick Hurley wrote: > I liked the delta transform as well, of course it was the cause of the > bug (delta 0 when pairs were encountered) - if fixed it by shuffling > the delta zeros (excluding the first card) to the back of the hand. And since I never saw that version of the program hit the list, here it is: #!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 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 royal_flush? if (md = (by_suit =~ /A(.) K\1 Q\1 J\1 T\1/)) [[10], arrange_hand(md)] 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 # 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 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 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 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 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 __END__ James Edward Gray II