[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

[QUIZ] Texas Hold'Em (#24

James Gray

3/18/2005 2:42:00 PM

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rub...

3. Enjoy!

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Matthew D Moss

You work for a cable network; specifically, you are the resident hacker for a
Texas Hold'Em Championship show.

The show's producer has come to you for a favor. It seems the play-by-play
announcers just can't think very fast. All beauty, no brains. The announcers
could certainly flap their jaws well enough, if they just knew what hands the
players were holding and which hand won the round. Since this is live TV, they
need those answers quick. Time to step up to the plate. Bob, the producer,
explains what you need to do.

BOB: Each player's cards for the round will be on a separate line of the input.
Each card is a pair of characters, the first character represents the face, the
second is the suit. Cards are separated by exactly one space. Here's a sample
hand.

Kc 9s Ks Kd 9d 3c 6d
9c Ah Ks Kd 9d 3c 6d
Ac Qc Ks Kd 9d 3c
9h 5s
4d 2d Ks Kd 9d 3c 6d
7s Ts Ks Kd 9d

YOU: Okay, I was going ask what character to use for 10, but I guess 'T' is it.
And 'c', 'd', 'h' and 's' for the suits, makes sense. Why aren't seven cards
listed for every player?

BOB: Well, if a player folds, only his hole cards and the community cards he's
seen so far are shown.

YOU: Right. And why did the fifth player play with a 4 and 2? They're suited,
but geez, talk about risk...

BOB: Stay on topic. Now, the end result of your code should generate output that
looks like this:

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

YOU: Okay, so I repeat the cards, list the rank or nothing if the player folded,
and the word "winner" in parenthesis next to the winning hand. Do you want the
cards rearranged at all?

BOB: Hmmm... we can get by without it, but if you have the time, do it. Don't
bother for folded hands, but for ranked hands, move the cards used to the front
of the line, sorted by face. Kickers follow that, and the two unused cards go at
the end, just before the rank is listed.

YOU: Sounds good. One other thing, I need to brush up on the hand ranks. You
have any good references for Texas Hold'Em?

BOB: Yeah, check out these Poker Hand Rankings
(http://www.thepokerforum.com/poke...). And if you need it, here are the
Rules of Texas Hold'Em (http://www.thepokerforum.com/texas...). While
ranking, don't forget the kicker, the next highest card in their hand if
player's are tied. And of course, if -- even after the kicker -- player's are
still tied, put "(winner)" on each appropriate line of output.

YOU: Ok. I still don't understand one thing...

BOB: What's that?

YOU: Why he stayed in with only the 4 and 2 of diamonds? That's just...

BOB: Hey! Show's on in ten minutes! Get to work!

[ Editor's Note:

Matthew included a script for generating test games with his quiz. Here's that
code:

#!/usr/bin/env ruby

FACES = "AKQJT98765432"
SUITS = "cdhs"

srand

# build a deck
deck = []
FACES.each_byte do |f|
SUITS.each_byte do |s|
deck.push(f.chr + s.chr)
end
end

# shuffle deck
3.times do
shuf = []
deck.each do |c|
loc = rand(shuf.size + 1)
shuf.insert(loc, c)
end
deck = shuf.reverse
end

# deal common cards
common = Array.new(5) { deck.pop }

# deal player's hole cards
hole = Array.new(8) { Array.new(2) { deck.pop } }

# output hands
hands = []
all_fold = true
while all_fold do
hands = []
hole.each do |h|
num_common = [0, 3, 4, 5][rand(4)]
if num_common == 5
all_fold = false
end
if num_common > 0
hand = h + common[0 ... num_common]
else
hand = h
end
hands.push(hand.join(' '))
end
end

hands.each { |h| puts h }

-JEG2 ]


15 Answers

Francis Hwang

3/18/2005 4:40:00 PM

0


On Mar 18, 2005, at 9:41 AM, Ruby Quiz wrote:
> YOU: Okay, I was going ask what character to use for 10, but I guess
> 'T' is it.

T is what people usually use for 10 when discussing the hands online.

> YOU: Why he stayed in with only the 4 and 2 of diamonds? That's just...

This is a bit of didacticism nobody wants, but: 42s is a pretty lousy
starting hand, but maybe:

1. You're in the button or one off, late position makes a lot of
marginal hands playable.
2. You were in the big blind and nobody bet over the blind, or you were
in the little blind, and you figured that the small amount you'd have
to pay to see the flop would be worth it.
3. You're on an exceptionally loose-passive table, so maybe you won't
have to pay much to draw to a flush or low straight, and when you do
nail your hand you'll be able to raise to make your bet payoff. These
tables do happen, though if you're being televised, your opponents are
probably all better players than this ...
4. Or maybe you're going in with the occasional pure, ludicrous bluff,
hoping to show terrible cards at the end to advertise the fact that
you're looser than you actually are.

Francis Hwang
http://f...



Hal E. Fulton

3/18/2005 4:50:00 PM

0

Francis Hwang wrote:
>
> On Mar 18, 2005, at 9:41 AM, Ruby Quiz wrote:
>
>> YOU: Okay, I was going ask what character to use for 10, but I guess
>> 'T' is it.
>
>
> T is what people usually use for 10 when discussing the hands online.
>
>> YOU: Why he stayed in with only the 4 and 2 of diamonds? That's just...
>
>
> This is a bit of didacticism nobody wants, but: 42s is a pretty lousy
> starting hand, but maybe:
>
> 1. You're in the button or one off, late position makes a lot of
> marginal hands playable.
> 2. You were in the big blind and nobody bet over the blind, or you were
> in the little blind, and you figured that the small amount you'd have to
> pay to see the flop would be worth it.
> 3. You're on an exceptionally loose-passive table, so maybe you won't
> have to pay much to draw to a flush or low straight, and when you do
> nail your hand you'll be able to raise to make your bet payoff. These
> tables do happen, though if you're being televised, your opponents are
> probably all better players than this ...
> 4. Or maybe you're going in with the occasional pure, ludicrous bluff,
> hoping to show terrible cards at the end to advertise the fact that
> you're looser than you actually are.

That is the most amazing domain-specific reply I have ever seen.

So Mr. Lafcadio is also a cardsharp? Who'd have guessed...


Hal



James Britt

3/18/2005 5:38:00 PM

0

Hal Fulton wrote:
> ...
>
> That is the most amazing domain-specific reply I have ever seen.
>
> So Mr. Lafcadio is also a cardsharp? Who'd have guessed...

Hmm. I'll keep this in mind at the next RubyConf.





Which is going to be in Vegas, right David?





:)



James



Michel Martens

3/18/2005 5:46:00 PM

0

On Sat, 19 Mar 2005 02:37:46 +0900, James Britt
<jamesUNDERBARb@neurogami.com> wrote:
> Which is going to be in Vegas, right David?
>
> :)

I bet it is.

Michel.


James Gray

3/18/2005 8:04:00 PM

0

On Mar 18, 2005, at 12:02 PM, Derek Wyatt wrote:

> How do we know if a player folded on the river? (For the ones who
> didn't read the rules or aren't hold'em junkies like myself, the river
> is the 5th community card -- a player could fold here and muck his
> hand)
>
> Or do we just not care? i.e. say he won anyways even though he mucked
> it.

Hmm, the quiz doesn't seem to account for this, so we should probably
just score it, in my non Texas Hold'em Junkie opinion. ;)

James Edward Gray II



Francis Hwang

3/19/2005 12:37:00 AM

0


On Mar 18, 2005, at 11:49 AM, Hal Fulton wrote:
> So Mr. Lafcadio is also a cardsharp? Who'd have guessed...

I don't know if you could call me a cardsharp yet, otherwise I wouldn't
have to write code for a living ...

Francis Hwang
http://f...



James Britt

3/20/2005 6:21:00 AM

0

Michel Martens wrote:
> On Sat, 19 Mar 2005 02:37:46 +0900, James Britt
> <jamesUNDERBARb@neurogami.com> wrote:
>
>>Which is going to be in Vegas, right David?
>>
>>:)
>
>
> I bet it is.


Oh, that might be a bad bet:

http://www.rubycentral.org/c...

Oct. 14 - Oct. 16, 2005
(Facility TBA)
San Diego, CA


But still within driving distance for me. Nice.

James
--

http://www.ru...
http://www.r...
http://catapult.rub...
http://orbjson.rub...
http://ooo4r.rub...
http://www.jame...


Michel Martens

3/20/2005 4:06:00 PM

0

On Sun, 20 Mar 2005 15:21:16 +0900, James Britt
<jamesUNDERBARb@neurogami.com> wrote:
> Oh, that might be a bad bet:
>
> http://www.rubycentral.org/c...
>
> Oct. 14 - Oct. 16, 2005
> (Facility TBA)
> San Diego, CA
>
> But still within driving distance for me. Nice.
>
> James
> --
>
> http://www.ru...

I'd love to be able to say the same.

Michel

Hint: living in Mar del Plata, Argentina. Earning pesos instead of
dollars or euros.


Glenn Parker

3/20/2005 6:36:00 PM

0

#!/usr/bin/ruby -w
#
# Quiz 24: Texas Hold'em
# Solution by Glenn Parker

module Combine
# Generate all combinations of +pick+ elements from +items+ array.
def Combine.pick(pick, items, &block)
combine([], 0, pick, items, &block)
end

private

def Combine.combine(set, index, pick, items, &block)
if pick == 0 or index == items.length
yield set
else
set.push(items[index])
combine(set, index + 1, pick - 1, items, &block)
set.pop
combine(set, index + 1, pick, items, &block) if
pick < items.length - index
end
end
end

# One card, with a face [2-9TJQKA] and a suit [shdc].
class Card
attr_reader :face, :suit

Face_Ranks = {
:A => 12, :K => 11, :Q => 10, :J => 9,
:T => 8, :"9" => 7, :"8" => 6, :"7" => 5,
:"6" => 4, :"5" => 3, :"4" => 2, :"3" => 1,
:"2" => 0
}

Suit_Ranks = {
:s => 3, :h => 2, :d => 1, :c => 0
}

def initialize(face_suit)
@face = face_suit[0].chr.to_sym
raise "Invalid face \"#{@face}\"" unless Face_Ranks.has_key?(@face)
@suit = face_suit[1].chr.to_sym
raise "Invalid suit \"#{@suit}\"" unless Suit_Ranks.has_key?(@suit)
freeze
end

def rank # Overall ranking in the deck.
index * 4 + Suit_Ranks[@suit]
end

def index # Ranking, independent of suit.
Face_Ranks[@face]
end

def to_s
@face.to_s + @suit.to_s
end
end

# A typed collection of up to five cards.
class Hand
include Comparable # Hands can be compared.

attr_reader :hand_type, :cards

Hand_Names = [
"Folded",
"High Card",
"Pair",
"Two Pair",
"Three of a Kind",
"Straight",
"Flush",
"Full House",
"Four of a Kind",
"Straight Flush",
"Royal Flush"
]

# Define constants by converting "High Card" to Hand::High_Card = 0.
Hand_Names.each_with_index do |n, i|
const_set(n.tr(" ", "_"), i)
end

def initialize(hand_type, cards)
@hand_type = hand_type
@cards = cards.dup
freeze
end

def to_s
@cards.join(" ") + " " + Hand_Names[@hand_type]
end

def <=>(other)
if @hand_type != other.hand_type
# Hand ranking dominates.
return @hand_type <=> other.hand_type

elsif @hand_type == Flush
# Compare corresponding cards, highest to lowest.
@cards.reverse.zip(other.cards.reverse) do |a, b|
return a.index <=> b.index if a.index != b.index
end
return 0

elsif @hand_type == Two_Pair
# Compare the two highest pairs, then the remaining pairs
self_indices = [@cards[0].index, @cards[2].index].sort!
other_indices = [other.cards[0].index, other.cards[2].index].sort!
if self_indices[1] != other_indices[1]
return self_indices[1] <=> other_indices[1]
else
return self_indices[0] <=> other_indices[0]
end

else
# All others types of hand are compared using their first card.
return @cards[0].index <=> other.cards[0].index
end
end
end

# A collection of seven cards, from which Hands are extracted.
class Deal
attr_reader :all_cards, :best_hand, :kickers

def initialize(card_string)
# Parse and sort the cards. The sorting order chosen here is
# important when extracting and comparing hands later.
@all_cards = card_string.split(/ /).collect do |face_suit|
Card.new(face_suit)
end.sort_by { |card| card.rank }
@hands = []
if @all_cards.length == 7
# Extract all possible hands if we got 7 cards.
find_high_card
find_groups
find_two_pairs_and_full_house
find_straight_and_flush
else
# Otherwise, make a folded hand.
add_hand(Hand::Folded, @all_cards)
end
# Pick the best possible hand and determine the kickers.
@best_hand = @hands.max
@kickers = (@all_cards - @best_hand.cards).sort_by do |card|
-card.rank
end
end

private

def add_hand(hand_type, cards)
@hands << Hand.new(hand_type, cards)
end

def find_high_card
add_hand(Hand::High_Card, [ @all_cards[-1] ])
end

def find_groups
# Find the longest run of each face in @all_cards.
start = 0
while @all_cards[start]
for stop in ((start + 1)..@all_cards.length)
next if @all_cards[stop] and
(@all_cards[start].face == @all_cards[stop].face)
case (stop - start)
when 4:
add_hand(Hand::Four_of_a_Kind, @all_cards[start...stop])
when 3:
add_hand(Hand::Three_of_a_Kind, @all_cards[start...stop])
when 2:
add_hand(Hand::Pair, @all_cards[start...stop])
end
break
end
start = stop
end
end

def find_two_pairs_and_full_house
pairs = @hands.find_all do |h|
h.hand_type == Hand::Pair
end
threes = @hands.find_all do |h|
h.hand_type == Hand::Three_of_a_Kind
end
# Find up to three combinations of two pairs.
if (pairs.length > 1)
Combine.pick(2, pairs) do |pair_hands|
add_hand(Hand::Two_Pair,
pair_hands[0].cards + pair_hands[1].cards)
end
end
# Each combination of a pair and three-of-a-kind is a full house.
pairs.each do |pair|
threes.each do |three|
add_hand(Hand::Full_House, three.cards + pair.cards)
end
end
# Two three-of-a-kinds yield two possible full-houses.
if (threes.length > 1)
add_hand(Hand::Full_House,
threes[0].cards + threes[1].cards[0..1])
add_hand(Hand::Full_House,
threes[1].cards + threes[0].cards[0..1])
end
# We could combine four-of-a-kind and a pair for a full-house
# but four-of-a-kind already beats a full-house.
end

def find_straight_and_flush
# Examine all combinations of five cards
Combine.pick(5, @all_cards) do |cards|
is_flush = true
is_straight = true
1.upto(4) do |i|
is_straight = false if
(cards[i].index != cards[i - 1].index + 1)
is_flush = false if
(cards[i].suit != cards[0].suit)
end
# Add the best hand found in this iteration.
case
when (is_straight and is_flush and cards[0].face == :"T")
add_hand(Hand::Royal_Flush, cards)
when (is_straight and is_flush)
add_hand(Hand::Straight_Flush, cards)
when (is_flush)
add_hand(Hand::Flush, cards)
when (is_straight)
add_hand(Hand::Straight, cards)
end
end
end

end

# A card player that holds a Hand and some kickers.
class Player
attr_reader :hand, :kickers
attr_accessor :wins

def initialize(hand, kickers)
@hand = hand
@kickers = kickers
@wins = false
end

# Return <=> value comparing kickers from another Player.
def compare_kickers(other)
@kickers.zip(other.kickers) do |a_kicker, b_kicker|
return 1 if a_kicker.index > b_kicker.index
return -1 if a_kicker.index < b_kicker.index
end
return 0
end
end

# Read the input.

players = []
while line = gets
line.chomp!
# Take first 20 chars only, making it easy to use previously
# printed results as input for re-testing.
deal = Deal.new(line[0, 20])
players << Player.new(deal.best_hand, deal.kickers)
end

# Find the winner(s).

winners = []
players.each do |player|
if winners.empty?
winners << player
elsif player.hand > winners[0].hand
winners.clear
winners << player
elsif player.hand == winners[0].hand
# Try to resolve ties based on kickers.
comparison = player.compare_kickers(winners[0])
if comparison >= 0
winners.clear if comparison > 0
winners << player
end
end
end
winners.each { |player| player.wins = true }

# Report the results.

players.each do |player|
# Print cards sorted by face with kickers at the end.
print((player.hand.cards + player.kickers).join(" "))
# Print description of hand and (winner) flag
if player.hand.hand_type > 0
print " ", Hand::Hand_Names[player.hand.hand_type]
print " (winner)" if player.wins
end
print "\n"
end



Matthew Moss

3/21/2005 1:30:00 AM

0

I'll add my (partial, again) solution... I didn't get a whole lot of
time to work on it, despite having written the original proposal. But
it was fun, so I may work a bit more on it. In any case, just to add
my ideas and approach, my current code determines the rank of each
hand, but that's it -- didn't get to refactoring and determining the
actual winner or sorting cards, checking high, kickers, etc.

#!/usr/bin/env ruby

SUITS = %w(c d h s)
FACES = %w(A K Q J T 9 8 7 6 5 4 3 2)

RANKS = {
: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',
:high_card => 'High Card',
:fold => ''
}

class Hand
def initialize(line)
@cards = line.split

@faces = Hash.new { [] }
@suits = Hash.new { [] }
@count = Hash.new { [] }

@cards.each do |card|
f = FACES.index(card[0].chr)
s = SUITS.index(card[1].chr)
@faces[f] = @faces[f] << s
@suits[s] = @suits[s] << f
end

@faces.keys.each do |face|
n = @faces[face].size
@count[n] = @count[n] << face
end

@rank = rank_hand
end

def rank_hand
return :fold if @cards.size < 7

return :royal_flush if @suits.keys.any? do |suit|
(0..5).all? do |face|
@suits[suit].include? face
end
end

return :straight_flush if @suits.keys.any? do |suit|
high = @suits[suit].min
(high..high+5).all? do |face|
@suits[suit].include? face
end
end

return :four_of_a_kind if not @count[4].empty?

return :full_house if @count[3].size == 2 or (@count[3].size == 1
and not @count[2].empty?)

return :flush if @suits.keys.any? do |suit|
@suits[suit].size >= 5
end

return :straight if @faces.keys.any? do |high|
(high..high+5).all? do |face|
@faces.keys.include? face
end
end

return :three_of_a_kind if @count[3].size == 1

return :two_pair if @count[2].size >= 2

return :pair if @count[2].size == 1

:high_card
end

attr_reader :cards, :rank
end


def main
hands = $<.collect { |l| Hand.new(l.chomp) }
hands.each do |h|
puts "#{h.cards.join(' ')} #{RANKS[h.rank]}"
end
end

main