James Gray
3/23/2005 3:08:00 PM
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