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:
3. Enjoy!
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
The card game of Euchre has an unusual ordering of cards in the hand. This
week's Ruby Quiz is to take a random Euchre hand and sort it.
The first thing you need to know is that Euchre is played with a small deck of
cards. Four suits are used Diamonds (d), Clubs (c), Spades (s), and Hearts (h),
but each suit has only the cards Nine (9), Ten (T), Jack (J), Queen (Q), King
(K), and Ace (A). The cards are generally ordered as I just listed them, Nine
being the low card and Ace the high card. The exception is the "Bowers".
When a Euchre hand is started, the first task is to select a Trump suit. How
that's done is not important, just know that one suit is always different from
the rest. Trump is the best suit, valued higher than the other three suits
(which are basically equal). In the Trump suit, the card order changes.
The first oddity of Trump is that the Jack of the selected suit becomes the
Right Bower, the highest ranked Trump card. The second oddity is that the other
Jack of the same color (Diamonds and Hearts are red while Clubs and Spades are
black) becomes the Left Bower, the second highest Trump card. This card is
considered to be of the Trump suit for the rest of the hand. For example, if
Spades is selected as Trump, the order of Spades becomes (lowest to highest):
9s, Ts, Qs, Ks, As, Jc, and Js. All other suits run Nine to Ace, save that
Clubs will be short a Jack.
The three non-Trump suits are equal, but it is good interface to sort the by
suit alternating red, black, red, and black, I think. Especially with a GUI,
this makes it easier to understand the hand.
Input (on STDIN) will be a line containing the Trump suit, followed by five
lines containing a Euchre hand. For example:
Diamonds
Kc
Jh
Kd
Td
Ah
Your script should output (to STDOUT) the Trump suit, followed by the cards in
sorted order (highest card first):
Diamonds
Jh
Kd
Td
Kc
Ah
Here's a script that will feed your program random hands:
#!/usr/local/bin/ruby -w
# build a Euchre deck
cards = Array.new
%w{9 T J Q K A}.each do |face|
%w{d c s h}.each do |suit|
cards << face + suit
end
end
# choose trump
puts %w{Diamonds Clubs Spades Hearts}[rand(4)]
# deal a hand
cards = cards.sort_by { rand }
puts cards[0..4]
One last thought: If accuracy is our ultimate goal here, how will you know your
output is correct?
Hello James,
I have to admit that I do not totally understand. Why is in the
example the King of Clubs higher than the Ace of hearts. I thought an
Ace would be higher than a King and Clubs and Hearts are equal except
for the Jack if the trump is Diamond.
And secondly I do not understand the paragraph about the red-black sort order.
cheers,
Brian
--
http://ruby.brian-schroeder.de/
Stringed instrument chords: http://chordlist.brian-schroeder.de/
>> Your script should output (to STDOUT) the Trump suit, followed by
>> the cards in
>> sorted order (highest card first):
>>
>> Diamonds
>> Jh
>> Kd
>> Td
>> Kc
>> Ah
> Hello James,
Hello Brian.
> I have to admit that I do not totally understand.
That's probably just my terrible explination. Let's see if I can
clear it up...
> Why is in the example the King of Clubs higher than the Ace of hearts.
Cards are sorted first buy suit, then by value. Trump is a better
suit then the rest, but the others are equal and can come in any
order. The suit order above is Diamonds (Trump), Clubs, then
Hearts. Inside those suits, cards are sorted by face value.
> And secondly I do not understand the paragraph about the red-black
> sort order.
Technically, this is another (correct) answer to the above:
Diamonds
Jh
Kd
Td
Ah
Kc
The cards are still sorted in order of value. However, now we have
put the red suits together. I think it's better to break them up,
when we can. That's why my example shows Clubs between the two suits.
Does that help?
James Edward Gray II
Now I understand. I always sort my cards first by value then by suite.
That was the problem.
cheers,
Brian
> > [snip]
>
> Does that help?
Yes
>
> James Edward Gray II
This one seemed pretty easy, so I gave it a try.
My first idea was to solve the whole sorting in the <=> method of the
Card class. But it was too confusing this way, because I had to include
all possible comparisons. Also the Card itself had to know what the
trump suit was.
Then I tried a different approach. I wrote a Card#weighting method which
returned a number, for trump jack a 41, for trump color jack a 40, and
so on. Then i sorted the card array with sort_by and the weighting. But
I wasn't satisfied yet, because again, the card had to know what suit
was trump. And the numbering was ugly, too. :)
So my final idea was to just implement the <=> method for sorting
without regard to trump. Then in my Hand class, which knows about the
trump, I wrote a sort! method for the sorting with regard to trump suit.
Now I'm interested to see other solutions :)
Robin Stocker
module Euchre
class Hand
attr_accessor :cards
def initialize( trump )
@cards = []
@trump = Card.new( trump )
end
def <<( card )
@cards << card
end
def sort!
@cards =
# First the trump jack..
@cards.select{ |c| trump_suit?(c) and c.jack? } |
# then the jack of the trump color..
@cards.select{ |c| trump_color?(c) and c.jack? } |
# then all the trump cards..
@cards.select{ |c| trump_suit?(c) }.sort.reverse |
# then a different color, so the colors alternate..
@cards.select{ |c| !trump_color?(c) and
c.suit =~ /d|c/ }.sort.reverse |
# then the cards with the same color as the trump..
@cards.select{ |c| trump_color?(c) }.sort.reverse |
# and finally the rest.
@cards.sort.reverse
end
def trump_suit?( card ) card.suit == @trump.suit end
def trump_color?( card ) card.color == @trump.color end
def to_s
@cards.join("\n")
end
end
class Card
attr_accessor :suit, :face
Suits = ['d', 'h', 'c', 's']
Faces = ['9', 'T', 'J', 'K', 'A']
Colors = {'d' => :red, 'h' => :red, 'c' => :black, 's' => :black}
def initialize( suit, face=nil )
@suit = suit.downcase
@face = face.upcase if face
end
def jack?() @face == 'J' end
def color() Colors[@suit] end
# Sort first by suit and then by face.
def <=>( other )
rel = Suits.index(@suit) - Suits.index(other.suit)
rel = Faces.index(@face) - Faces.index(other.face) if rel == 0
rel
end
def to_s
@face + @suit
end
end
end
if __FILE__ == $0
lines = readlines
trump = lines.shift.slice(/\w+/)
hand = Euchre::Hand.new(trump[0,1])
lines.join.scan(/(\w)(\w)/) do |face, suit|
hand << Euchre::Card.new(suit, face)
end
hand.sort!
puts trump
puts hand
end
> Now I'm interested to see other solutions :)
> Robin Stocker
This is mine, I didn't bother putting it into a class.
=end
# Return rank of the same color.
def opposite(rank)
{?d => ?h, ?h => ?d, ?c => ?s, ?s => ?c}.fetch rank
end
# Return rank of different color.
def neighbor(rank)
{?d => ?c, ?c => ?h, ?h => ?s, ?s => ?d}.fetch rank
end
def relative_rank(trump, suit, rank)
case suit
when trump: rank == ?J ? 1000 : 500
when neighbor(trump): 400
when opposite(trump): rank == ?J ? 900 : 300
when opposite(neighbor(trump)): 200
end +
[?9, ?T, ?J, ?Q, ?K, ?A].index(rank)
end
def sort_cards(trump, cards)
cards.sort_by { |c| -relative_rank(trump, c[1], c[0]) }
end
puts sort_cards(gets.strip.downcase[0], readlines.map { |s| s.strip })
__END__
--
Christian Neukirchen <chneuk...@gmail.com> http://chneukirchen.org
Hmm. My original solution was to assign integer values to each card
and suit, and sort_by that mapping.
#!/usr/bin/ruby -w
NUMS = %w(9 T J Q K A)
SUITS = %w(h c d s)
COLOURS = Hash[*%w(s B d R c B h R)]
def card_value(card, trump)
num, suit = card.split('')
value = NUMS.index(num)
value += 10 * SUITS.index(suit)
if num == 'J'
value += 150 if COLOURS[suit] == COLOURS[trump]
value += 10 if suit == trump
elsif suit == trump
value += 100
end
return value
end
trump = gets.chomp
tsuit = trump[0,1].downcase
puts trump
puts $stdin.readlines.sort_by { |card| -card_value(card,tsuit) }
When I started to think about it though, that seemed a little silly to
me. If you ever use more than ten numbers, or more than 5 cards,
things start to get messy and sneaky bugs pop up. I realised that what
I was really doing was creating an array of base-10 numbers to
represent the cards. Why not just create an actual array and be done
with it?
So, here's my version 2:
#!/usr/bin/ruby -w
NUMS = %w(9 T J Q K A)
SUITS = %w(h c d s)
COLOURS = Hash[*%w(s B d R c B h R)]
def card_value(card, trump)
num, suit = card.split('')
value = Array.new
value << (num == 'J' && value[3] == 1 ? 1 : 0)
value << (num == 'J' && COLOURS[suit] == COLOURS[trump] ? 1 : 0)
value << (suit == trump ? 1 : 0)
value << SUITS.index(suit)
value << NUMS.index(num)
end
trump = gets.chomp
tsuit = trump[0,1].downcase
puts trump
puts $stdin.readlines.sort_by { |card| card_value(card,tsuit) }.reverse
Though, incidentally, I'm not sure how the performance of the two
would compare. I also wish I could think of a neater way to include
those boolean values than the ? 1 : 0 thing. I considered defining <=>
for TrueClass and FalseClass, but thought that might be a little icky.
Sam
SUITS = %w{d c h s}.map {|s| s[0]}
CARDS = %w{A K Q J T 9}.map {|c| c[0]}
trump,hand = STDIN.readline, STDIN.readlines
puts trump
trump = SUITS.index(trump.downcase[0])
# If the suit after the trump suit is missing, we swap it with the
# other suit of the same color. This ensures that we always have a
# correct color alternation when possible.
unless hand.find {|card| card[1] == SUITS[(trump+1)%4]}
tmp = SUITS[(trump+1)%4]
SUITS[(trump+1)%4] = SUITS[(trump+3)%4]
SUITS[(trump+3)%4] = tmp
end
hand.map { |card|
suit = (SUITS.index(card[1]) - trump)%4
num = CARDS.index(card[0])
if num==3 && suit==2
suit,num = 0,-1 # Left bower
elsif num==3 && suit==0
num = -2 # Right bower
end
[suit,num,card.chomp]
}.sort.each{|c| puts "#{c[2]}\n" }
The second version is my highly golfed version which I think is about
as small as my algorithm can get. It is 242 bytes. I would be very
interested to see if anyone can go smaller, either by tweaking mine
more, or more likely, by golfing another algorithm. Obviously the
golfed code has zero error handling, so it will probably choke on any
bad input.
Normal code:
class String
def to_suit
self[0..0].downcase
end
end
class EuchreSort
# These represent preferred sorting order
SUITS = %w{Diamonds Clubs Hearts Spades} # Alphabetical, then by color
CARDS = %w{A K Q J T 9} # Highest to lowest
def initialize(trump)
@trump = trump
trump_index = SUITS.index(trump)
raise "Invalid trump suit: #{trump}" unless trump_index
@right_bower = "J#{trump.to_suit}"
# The ordering used in SUITS ensures this works
@left_bower = "J#{SUITS[(trump_index+2)%4].to_suit}"
# Apply weights to suits starting from the trump, wrapping
# around as needed
@suit_weights = {}
weight = 10
trump_index.upto(trump_index+3) do |i|
@suit_weights[SUITS[i%4].to_suit] = weight
weight += 10
end
end
def sort(hand)
weights = {}
hand.each do |card|
raise "Invalid card: #{card}" if card !~ /\A[#{CARDS.join}]{1}[dchs]{1}\z/
weights[card] =
case card
when @right_bower: 0
when @left_bower: 1
else
@suit_weights[card[1..1]] + CARDS.index(card[0..0])
end
end
hand.sort_by {|c| weights[c]}
end
end
if $0 == __FILE__
hand = STDIN.collect {|i|i.chomp}
trump = hand.shift
es = EuchreSort.new(trump)
puts trump
puts es.sort(hand)
end
Golfed code (I'm not sure how Gmail will wrap this, but it should be
all on one line):
a=$<.read.split("\n");s=%w{d c h
s};c='AKQJT9';t=a.shift;u=(t[0]+32).chr;i=s.index(u);v=s[(i+2)%4];w=[1];i.upto(i+3){|j|w<<s[j%4]};m={};a.each{|k|m[k]=k=~/(J#{u})|(J#{v})/?$1?0:1:w.index(k[1..1])*10+c.index(k[0..0])};puts
t,a.sort_by{|k|m[k]}
Ryan
> Hi
>
> This one seemed pretty easy, so I gave it a try.
Just FYI, this solution seems to have trouble with certain inputs:
Neo:~/Documents/Ruby/Ruby Quiz$ ruby solutions/euchre_hand.rb >
test_hand.txt
Neo:~/Documents/Ruby/Ruby Quiz$ ruby solutions/Robin\ Stocker/
euchre_hands.rb test_hand.txt
solutions/Robin Stocker/euchre_hands.rb:58:in `-': nil can't be
coerced into Fixnum (TypeError)
from solutions/Robin Stocker/euchre_hands.rb:58:in `<=>'
from solutions/Robin Stocker/euchre_hands.rb:27:in `sort'
from solutions/Robin Stocker/euchre_hands.rb:27:in `sort!'
from solutions/Robin Stocker/euchre_hands.rb:80
Neo:~/Documents/Ruby/Ruby Quiz$ cat test_hand.txt
hearts
9s
9d
Kd
Ah
Qd
That's really not meant as a complaint. I appreciate you sharing
your solution. It works more often than not and always seems to
produce correct answers when it does. I just thought you might like
to know.
James Edward Gray II
> =begin
> Robin Stocker <ro...@nibor.org> writes:
>
>> Now I'm interested to see other solutions :)
>> Robin Stocker
>
> This is mine, I didn't bother putting it into a class.
Hmm, is this correct:
Neo:~/Documents/Ruby/Ruby Quiz$ ruby solutions/euchre_hand.rb >
test_hand.txtNeo:~/Documents/Ruby/Ruby Quiz$ ruby solutions/Christian
\ Neukirchen/euchre_hands.rb test_hand.txt
Qh
9h
Qd
9d
Qc
Neo:~/Documents/Ruby/Ruby Quiz$ cat test_hand.txt
hearts
Qd
9h
9d
Qc
Qh
I would have answered:
hearts
Qh
9h
Qc
Qd
9d
Which doesn't put two red suits together. Getting back to the
question of the quiz, how do we know when we're right?
James Edward Gray II
> On Nov 20, 2005, at 9:15 AM, Christian Neukirchen wrote:
>
>> =begin
>> Robin Stocker <ro...@nibor.org> writes:
>>
>>> Now I'm interested to see other solutions :)
>>> Robin Stocker
>>
>> This is mine, I didn't bother putting it into a class.
> I would have answered:
>
> hearts
> Qh
> 9h
> Qc
> Qd
> 9d
>
> Which doesn't put two red suits together. Getting back to the
> question of the quiz, how do we know when we're right?
Difficult. Mine doesn't (yet?) reorder if there are no cards of every
color.
> James Edward Gray II
Ah, thank you... I just noticed that I forgot the queen in my Faces array :)
I also updated the script to get the alternating colors right.
Thanks for finding this (stupid) error.
Robin Stocker
module Euchre
class Hand
attr_accessor :cards
def initialize( trump )
@cards = []
@trump = Card.new( trump )
end
def <<( card )
@cards << card
end
def sort!
second_suit = @cards.find{ |c| !trump_color?(c) }.suit
@cards =
# First the trump jack..
@cards.select{ |c| trump_suit?(c) and c.jack? } |
# then the jack of the trump color..
@cards.select{ |c| trump_color?(c) and c.jack? } |
# then all the trump cards..
@cards.select{ |c| trump_suit?(c) }.sort.reverse |
# then a different color, so the colors alternate..
@cards.select{ |c| c.suit == second_suit }.sort.reverse |
# then the cards with the same color as the trump..
@cards.select{ |c| trump_color?(c) }.sort.reverse |
# and finally the rest.
@cards.sort.reverse
end
def trump_suit?( card ) card.suit == @trump.suit end
def trump_color?( card ) card.color == @trump.color end
def to_s
@cards.join("\n")
end
end
class Card
attr_accessor :suit, :face
Suits = ['d', 'h', 'c', 's']
Faces = ['9', 'T', 'Q', 'J', 'K', 'A']
What I did was first think about the best test to prove my solution. If
you have a completely shuffled euchre deck for a specfic trump, the
entire deck would take on a known order. So I wrote four tests:
require 'test/unit'
require 'euchre'
HEARTS_AS_TRUMP_DECK = [
'Jh','Jd','Ah','Kh','Qh','Th','9h',
'As','Ks','Qs','Js','Ts','9s',
'Ad','Kd','Qd','Td','9d',
'Ac','Kc','Qc','Jc','Tc','9c'
]
SPADES_AS_TRUMP_DECK = [
'Js','Jc','As','Ks','Qs','Ts','9s',
'Ad','Kd','Qd','Jd','Td','9d',
'Ac','Kc','Qc','Tc','9c',
'Ah','Kh','Qh','Jh','Th','9h'
]
DIAMONDS_AS_TRUMP_DECK = [
'Jd','Jh','Ad','Kd','Qd','Td','9d',
'Ac','Kc','Qc','Jc','Tc','9c',
'Ah','Kh','Qh','Th','9h',
'As','Ks','Qs','Js','Ts','9s',
]
CLUBS_AS_TRUMP_DECK = [
'Jc','Js','Ac','Kc','Qc','Tc','9c',
'Ah','Kh','Qh','Jh','Th','9h',
'As','Ks','Qs','Ts','9s',
'Ad','Kd','Qd','Jd','Td','9d'
]
class TestEuchre < Test::Unit::TestCase
def setup
@ed = EuchreDeck.new
@eh = EuchreHand.new
@ed.shuffle
while( card = @ed.deal )
@eh.add_card( card )
end
end
def test_hearts_as_trump
@eh.trump = "Hearts"
assert_equal( HEARTS_AS_TRUMP_DECK, @eh.hand )
end
def test_spades_as_trump
@eh.trump = "Spades"
assert_equal( SPADES_AS_TRUMP_DECK, @eh.hand )
end
def test_diamonds_as_trump
@eh.trump = "Diamonds"
assert_equal( DIAMONDS_AS_TRUMP_DECK, @eh.hand )
end
def test_clubs_as_trump
@eh.trump = "Clubs"
assert_equal( CLUBS_AS_TRUMP_DECK, @eh.hand )
end
end
I took the original input program and created a EuchreDeck class:
class EuchreDeck
def initialize
# build a Euchre deck
@cards = Array.new
%w{9 T J Q K A}.each do |face|
%w{d c s h}.each do |suit|
@cards << face + suit
end
end
end
def shuffle
@cards = @cards.sort_by { rand }
end
def deal
@cards.shift
end
end
I wrote a EuchreHand class that would take cards dealt to it and
originize them by a computed card value using sort.
class EuchreHand
Suit = Struct.new( :suit, :alternate_suit_1, :off_suit,
:alternate_suit_2 )
@@suits = {
"Diamonds"=>Suit.new("d","c","h","s"),
"Clubs"=>Suit.new("c","h","s","d"),
"Spades"=>Suit.new("s","d","c","h"),
"Hearts"=>Suit.new("h","s","d","c")
}
@@face_values_trump = {
"J" => 6,
"A" => 4,
"K" => 3,
"Q" => 2,
"T" => 1,
"9" => 0
}
@@face_values_regular = {
"A" => 5,
"K" => 4,
"Q" => 3,
"J" => 2,
"T" => 1,
"9" => 0
}
MAX_CARDS_PER_SUIT = 7
def initialize
@trump = nil
@hand = []
end
def left_brower?( card )
card == "J#{@trump.off_suit}"
end
def trump?( card )
card[1].chr == @trump.suit
end
def trump=( suit_string )
@trump = @@suits[ suit_string ]
end
def trump
@@suits.index(@trump)
end
def add_card( card )
@hand.push( card )
end
def card_value( card )
face = card[0].chr
suit = card[1].chr
if left_brower?(card) then
suit_value = @trump.to_a.reverse.index( @trump.suit ) *
MAX_CARDS_PER_SUIT
face_value = @@face_values_trump[ face ] - 1
elsif trump?(card) then
suit_value = @trump.to_a.reverse.index( @trump.suit ) *
MAX_CARDS_PER_SUIT
face_value = @@face_values_trump[ face ]
else
suit_value = @trump.to_a.reverse.index( suit ) *
MAX_CARDS_PER_SUIT
face_value = @@face_values_regular[ face ]
end
suit_value + face_value
end
def hand
@hand.sort {|x,y| card_value(y)<=>card_value(x) }
end
end
Once my tests passed, I rewrote the original input program as:
require 'euchre'
# choose trump
puts %w{Diamonds Clubs Spades Hearts}[rand(4)]
ed = EuchreDeck.new
ed.shuffle
5.times{ puts ed.deal }
And the sort program as:
require 'euchre'
eh = EuchreHand.new
eh.trump = gets.strip
while card = gets
eh.add_card( card.strip )
end
puts eh.trump
puts eh.hand
It would be a fun to attempt to create a EuchreBot to actually play.
--Dale
> I used to play euchre a lot in college so this was fun. It might be
> fun
> writing an entire euchre game.
[snip]
> It would be a fun to attempt to create a EuchreBot to actually play.
I agree. Maybe if you and I got a server together, building AI
players would make a good quiz for it? Drop me and email if this
interests you...
James Edward Gray II
P.S. Nice proof of your solution!
It also sorts the the cards by a score computed depending on the trump
suit, lower is better.
Dominik
The code:
class EuchreCard
SUIT_COLOR = {
:diamonds => :red,
:hearts => :red,
:clubs => :black,
:spades => :black
}
SUIT_ORDER = [:diamonds, :clubs, :hearts, :spades]
RANK_ORDER = [:nine, :ten, :jack, :queen, :king, :ace]
attr_reader :rank, :suit
def initialize(str)
str = str.to_s.downcase
@rank =
if str[0] == ?9
:nine
else
RANK_ORDER.find { |rank| rank.to_s[0] == str[0] }
end
@suit = SUIT_ORDER.find { |suit| suit.to_s[0] == str[1] }
raise "unknown card rank" unless rank
raise "unknown card suit" unless suit
end
def to_s
unless rank == :nine
rank.to_s[0, 1].upcase
else
"9"
end + suit.to_s[0, 1]
end
def sort_score(trump)
if rank == :jack && suit == trump
0
elsif rank == :jack && SUIT_COLOR[suit] == SUIT_COLOR[trump]
1
else
ti = SUIT_ORDER.index(trump)
raise "unknown trump suit: #{trump}" unless ti
suit_score = (SUIT_ORDER.index(suit) - ti) % 4
10 + suit_score * 10 - RANK_ORDER.index(rank)
end
end
end
if $0 == __FILE__
trump = gets.strip.downcase.to_sym
unless EuchreCard::SUIT_COLOR.has_key? trump
warn "unknown trump suit: #{trump}"
exit 1
end
cards = readlines.map { |line| EuchreCard.new(line.strip) }
cards = cards.sort_by { |card| card.sort_score(trump) }
puts trump.to_s.capitalize, cards
end
I made the opposite decision to Robin Stocker and decided to stick with
the <=> approach. The cards do have to know about the trump suit, but
the deck class automatically handles that.
Here it is:
class Card
@@values = {'A' => :ace, 'K' => :king, 'Q' => :queen, 'J' => :jack,
'T' => :ten, '9' => :nine}
@@value_values = {:ace => 6, :king => 5, :queen => 4, :jack => 3, :ten
=> 2, :nine => 1}
@@suits = {'h' => :hearts, 'd' => :diamonds, 'c' => :clubs, 's' =>
:spades}
@@suit_values = {:hearts => 2, :diamonds => -2, :clubs => 1, :spades
=> -1}
# The suit_values is used to compactly calculate the order suits
should be sorted in
def initialize(string = "Jh", trump = :hearts)
@suit = :hearts
@value = :ace
@trump = trump
if string
self.parse(string)
end
end
def parse(string)
cardarray = string.upcase.scan(/[AKQJT9][HDCS]/)
if cardarray.length > 0
@value = @@values[cardarray[0][0,1]]
@suit = @@suits[(cardarray[0][1,1]).downcase]
end
end
def rank_value
case @suit
when @trump
return @value == :jack ? 40 : (@@value_values[@value] + 30)
when @@suit_values.index(@@suit_values[@trump] * -1)
# True when card is of the trump's suit's colour companion
return @value == :jack ? 39 : (@@value_values[@value] + 10)
when @@suit_values.index((@@suit_values[@trump] % 2) + 1)
# Triggers for clubs if trump is red or hearts if trump is black
return @@value_values[@value] + 20
else
# The last remaining suit comes last
return @@value_values[@value]
end
end
def <=>(other)
self.rank_value <=> other.rank_value
end
def printable
@@values.index(@value) + @@suits.index(@suit)
end
include Comparable
attr_accessor :trump, :value, :suit
end
class Hand
def initialize(trump = :hearts, *cards)
@cards = Array.new
@trump = trump
cards.each do |cs|
@cards << Card.new(cs, trump)
end
@cards.sort!
end
def add_card(card)
@cards << Card.new(card, @trump)
end
def display
@cards.sort!
@cards.reverse.each do |card|
puts card.printable
end
end
def inspect
handstring = String.new
@cards.each do |card|
handstring << card.printable << ', '
end
handstring.chop!
handstring.chop!
end
def trump=(newtrump)
@trump = newtrump
@cards.each {|c| c.trump = newtrump}
end
attr_accessor :cards
attr_reader :trump
end
if __FILE__ == $0
until (trumpline = readline()) != ""
end
case trumpline.downcase[0,1]
when 'h'
trump = :hearts
when 'd'
trump = :diamonds
when 'c'
trump = :clubs
when 's'
trump = :spades
else
raise StandardError, "Bad Input"
end
h = Hand.new(trump)
5.times do |t|
cardline = readline()
h.add_card(cardline)
end
puts trump.to_s.capitalize
h.display
end
It doesn't really "solve" the problem, instead it cheats ;-) , but on the
other side it is short and should be correct if the order hash is correct
class Euchre
OTHER_COLOR = {'c' => 's', 's' => 'c', 'h' => 'd', 'd' => 'h'}
attr_reader :trump_suit
def initialize(suit_name)
@trump_suit = suit_name
@trump = @trump_suit[0,1].downcase
end
def <<(card)
(@hand ||= []) << card
end
def hand
suits = @hand.map {|card| card[1,1]}.uniq
i = suits.index(@trump) and suits.push(suits.slice!(i))
suits[-3],suits[-2] = suits[-2],suits[-3] if suits.length > 2 and
OTHER_COLOR[suits[-1]] == suits[-2]
@hand.sort_by do |x|
rank, suit = x.split('')
if rank == 'J' and @trump == suit : 50
elsif rank == 'J' and OTHER_COLOR[suit] == @trump : 40
else '9TJQKA'.index(rank) + suits.index(suit)*10
end
end.reverse
end
end
euchre = Euchre.new(gets.strip)
5.times { euchre << gets.strip }
puts euchre.trump_suit, euchre.hand
Golfed, but weighing in a good 80 characters more than Ryan's (but,
hey, it's my first golf outing in Ruby.) Like his, it should be one
line.
a=[];6.times{a<<gets.strip};puts t=a.shift;o=Hash[*%w{C S S C H D D
H}];t=t[0,1];s=a.map{|c|c[1,1].upcase}.uniq;i=s.index(t)and
s.push(s.slice!(i));s.length>2&&o[s[-1]]==s[-2]&&(s[-3],s[-2]=s[-2],s[-3]);puts
a.sort_by{|c|r,z=c.upcase.split('');r=='J'&&t==z&&50||r=='J'&&o[z]==t&&40||'9TJQKA'.index(r)+s.index(z)*9}.reverse
>> correct.
>
> A couple of my early attempts depended on the idea that the trump suit
> absolutely determined the ranking of the cards, independent of knowing
> anything else. It doesn't work, though.
Indeed, I didn't realize that till now (even though James wrote a similar
reply to Christian's solution)...
So my first solution is "wrong", too. I will post new versions.
Thanks,
Dominik
The fixes were:
1. Using s[x,1] instead of s[x..x] to get a single character from a
string. This saved 2 bytes.
2. Calculating the weights INSIDE the sort_by, instead of using a
separate hash. This seems so obvious now I slapped my head when I saw
Zed's code doing this. This saved a whopping 26 bytes.
So, if anyone is interested:
a=$<.read.split("\n");s=%w{d c h
s};c='AKQJT9';t=a.shift;u=(t[0]+32).chr;i=s.index(u);v=s[(i+2)%4];w=[1];i.upto(i+3){|j|w<<s[j%4]};puts
t,a.sort_by{|k|k=~/(J#{u})|(J#{v})/?$1?0:1:w.index(k[1,1])*10+c.index(k[0,1])}
I will be highly impressed if someone can go shorter than this. Also
if anyone wants it I can post a message "decoding" the above.
Ryan
a=$<.read.split("\n");s=%w{d c h
s};c='AKQJT9';t=a.shift;u=(t[0]+32).chr;i=s.index(u);v=s[i-2];w=[1];4.times{|j|w<<s[j-i]};puts
t,a.sort_by{|k|k=~/J[#{u}#{v}]/?$1?0:1:w.index(k[1,1])*10+c.index(k[0,1])}
Damn, now I can see why you did it that way. Sorry. Please disregard.
No, your idea is right: he could still save one character using
/J(#{u})|(#{v})/
Regards,
Pit
class EuchreHand
SUIT = [?c,?d,?s,?h]
VAL = [?A,?Q,?K,?J,?T,?9]
def initialize input
@trump = input.shift
@hand = {}
4.times {|s| @hand[SUIT[s]]=[]}
input.each{|card| @hand[card[1]] << card[0] }
end
def display
puts @trump
t=SUIT.index(@trump.downcase[0])
get_jacks(SUIT[t])
get_jacks(SUIT[(t+2)%4])
if @hand[SUIT[(t+1)%4]].empty?
t.downto(0){|s| show_suit s}
(3).downto(t+1) {|s| show_suit s}
else
(t..3).each {|s| show_suit s}
(0...t).each {|s| show_suit s}
end
end
def get_jacks suit
if @hand[suit].include? ?J
puts [?J,suit].pack('c*')
@hand[suit].delete(?J)
end
end
def show_suit s
suit = SUIT[s]
@hand[suit].sort{|a,b| VAL.index(a)<=>VAL.index(b)}.each{|v|
puts [v,suit].pack('c*')
}
end
end
if __FILE__ == $0
input =readlines
e = EuchreHand.new(input)
e.display
end
----
-Adam
This is the last time I play golf. It's addictive and I should be working.
a=$<.read.split("\n");s=%w{d c h
s};c='AKQJT9';t=a.shift;u=(t[0]+32).chr;i=s.index(u);v=s[i-2];w=[1];puts
t,a.sort_by{|k|(k=~/J[#{u+v}]/?0:99)+(s.index(k[1,1])-i)%4*10+c.index(k[0,1])}
184 characters. And this time I think it works.
class Card
attr_reader :suite, :value
def initialize(string)
@value = string[0..-2].upcase
@suite = string[-1..-1].downcase
end
def to_i
case @value
when 'J': 11
when 'Q': 12
when 'K': 13
when 'A': 1
else
@value.to_i
end
end
def to_s
@value + @suite
end
end
class EuchreHand
def initialize(trump, cards)
@trump_string = trump
@trump = trump[0..0].downcase
@cards = cards
end
def to_s
([@trump_string] +...@cards.sort_by {|c| sort_value(c)}).join("\n")
end
private
#there are 6 sections of the sort: jack1, jack2, trump, opp1, same,
opp2
def sort_value(card)
return [0] if card.value == 'J' && card.suite == @trump
same, opposite = [['h','d'],['c','s']].partition {|x|
x.include?(@trump)}
same[0].delete(@trump)
same = same[0][0]
opposite = opposite[0]
return [1] if card.value == 'J' && card.suite
suites = [@trump, opposite[0], same, opposite[1]]
return [2+suites.index(card.suite),-card.to_i]
end
end
cards = ARGF.readlines
puts EuchreHand.new(cards.shift, cards.map {|s| Card.new(s)})
#####################################################################################
This email has been scanned by MailMarshal, an email content filter.
#####################################################################################
Doh!
This quiz looked so simple I just couldn't resist putting in the
short amount of time needed to solve it. Also, I love to play Euchre.
I see other solutions trying very hard to make sure that two suits
of the same color do not appear next to each other. As a Euchre player,
I would be quite upset if the order of the suits changed during play.
For example, if hearts were trump, and the hand is (9h 9s 9d 9c Tc), and
the 9s is played, the hand should stay (9h 9d 9c Tc), not be reordered
to (9h 9c Tc 9d). I tend to always keep my suits in the same order (s h
c d) and simply rotate the order so that the trump suit is first (e.g.
(c d s h) if clubs are trump).
I also made sure the routine works with fewer (or more) than five
cards since cards are actually played from the hand during a game.
----------------------------------------------------------------
# Constants
RANKS = 'AKQJT9'
SUITS = 'SHCD'
def euchre_sort(cards)
# Split out trump
trump = cards.shift
# Quick and dirty validation
trump_index = SUITS.index(trump[0,1].upcase)
unless trump_index
puts "Invalid trump (#{trump})."
exit
end
cards.each do |card|
if card !~ /^[#{RANKS}][#{SUITS}]$/i
puts "Invalid card (#{card})."
exit
end
end
# Return sorted hand
right_bower = "J#{SUITS[trump_index,1]}"
left_bower = "J#{SUITS[trump_index - 2,1]}"
[trump] +
cards.sort_by do |card|
upcase_card = card.upcase
case upcase_card
when right_bower then -2
when left_bower then -1
else RANKS.index(upcase_card[0,1]) +
10 * ((SUITS.index(upcase_card[1,1]) - trump_index) % 4)
end
end
end
puts euchre_sort(STDIN.read.split)
----------------------------------------------------------------
This solution (minus error handling) is also very amenable to
golfing at 187 characters:
R='AKQJT9';S='shcd';i=$<.read.split;t=i.shift;x=S.index(t[0,1]
downcase);r="J#{S[x,1]}";l="J#{S[x-2,1]}";puts(t,i.sort_by{|c|
c==r ?-2:c==l ?-1:R.index(c[0,1])+10*((S.index(c[1,1])-x)%4)})
- Warren Brown
Brilliant. I'm extremely impressed. I learned quite a bit from it too,
which is one of the reasons I like code golfing so much. Plus it is
fun and rather addictive, as you have learned.
For those wondering, here is what I learned from David's changes to my code:
1. The extra newlines don't need to be cleaned out from the input
since "puts" seems to ignore them.
2. For compactness it is better to use strings rather than %w{} arrays.
3. Ruby's array and string indexing already takes care of
"wrap-around" when indexing since negative indexes work. So given a
string s of length 4, s[(i+2)%4] and s[i-2] are equivalent for values
of i from 0 to 5.
4. You can get the index of either a string or a fixnum representing a
character using String#index.
5. Using % on a negative number yields a positive number, which is
cleverly used above in calculating the weight. My math is rusty I
guess, or in fact, I'm not sure I ever learned what performing modulus
on a negative number is supposed to return. But I Googled it and
learned now.
But there is a problem in my code (both the above and the longer
version) which I just noticed (and that Zed brought up earlier): if a
suit is missing in certain cases the suits are not interlaced
properly. I'm wondering if I will bother going back to the drawing
board or not :)
Ryan
Dominik
order = {
"Spades" => "JsJcAsKsQsTs9sAdKdQdJdTd9dAcKcQcTc9cAhKhQhJhTh9h",
"Hearts" => "JhJdAhKhQhTh9hAsKsQsJsTs9sAdKdQdTd9dAcKcQcJcTc9c",
"Clubs" => "JcJsAcKcQcTc9cAhKhQhJhTh9hAsKsQsTs9sAdKdQdJdTd9d",
"Diamonds" => "JdJhAdKdQdTd9dAcKcQcJcTc9cAhKhQhTh9hAsKsQsJsTs9s"
}
trump = gets.strip
cards = readlines.map { |l| l.strip }
o = order[trump].dup
# do we have a card of the 2nd suit
unless cards.any? { |card| card[1] == o[15] }
# if not replace second suit by the last
o[14, 12] = o[36, 12]
end
puts trump, cards.sort_by { |card| o.index(card) }
class Hand
attr :cards
SUITS = %w{s h c d} # should be ordered by alternating colors
RANKS = %w{9 T J Q K A} # must be ordered low to high
def initialize(cards, trump)
@trump_c = trump[0,1].downcase
@complement = SUITS[SUITS.index(@trump_c) - 2]
first_off_suit = SUITS.find{|suit|
![@trump_c,@complement].include?(suit) &&
cards.find{|card|card[1,1]==suit}}
@suit_values = {@trump_c => 100, first_off_suit => 50, @complement =>
25}
@cards = cards.sort_by{|card| get_val(card) * -1}.unshift(trump)
end
private
def get_val(card)
val = RANKS.index(card[0,1]) + (@suit_values[card[1,1]] ||= 0)
return ["J#{@trump_c}", "J#{@complement}"].include?(card) ? val+500 :
val
end
end
cards = STDIN.read.split
puts Hand.new(cards, cards.shift).cards
Fixed now:
class Card
attr_reader :suite, :value
def initialize(string)
@value = string[0..-2].upcase
@suite = string[-1..-1].downcase
end
def to_i
%w{9 T J Q K A}.index(@value)
end
def to_s
@value + @suite
end
end
class EuchreHand
def initialize(trump, cards)
@trump_string = trump
@trump = trump[0..0].downcase
@cards = cards
same, opposite = [['h','d'],['c','s']].partition {|x|
x.include?(@trump)}
same[0].delete(@trump)
@same = same[0][0]
@suite_order = [@trump, opposite[0][0], same, opposite[0][1]]
end
def to_s
([@trump_string] +sorted_cards).join("\n")
end
def sorted_cards
@cards.sort_by {|c| sort_value(c)}
end
private
#there are 6 sections of the sort: jack1, jack2, trump, opp1, same,
opp2
def sort_value(card)
return [0] if card.value == 'J' && card.suite == @trump
return [1] if card.value == 'J' && card.suite == @same
return [2+@suite_order.index(card.suite),-card.to_i]
class Card
attr_reader :suite, :value
def initialize(string)
@value = string[0..-2].upcase
@suite = string[-1..-1].downcase
end
def to_i
%w{9 T J Q K A}.index(@value)
end
def to_s
@value + @suite
end
end
class EuchreHand
def initialize(trump, cards)
@trump_string = trump
@trump = trump[0..0].downcase
@cards = cards
same, opposite = [['h','d'],['c','s']].partition {|x|
x.include?(@trump)}
@same = same.flatten.reject{|s| s == @trump}[0]
opposite = opposite.flatten.select {|s| cards.any?{|c| c.suite
== s}}
@suite_order = [@trump, @same].zip(opposite).flatten
end
def to_s
([@trump_string] +sorted_cards).join("\n")
end
def sorted_cards
@cards.sort_by {|c| sort_value(c)}
end
private
#there are 6 sections of the sort: jack1, jack2, trump, opp1, same,
opp2
def sort_value(card)
return [0] if card.value == 'J' && card.suite == @trump
return [1] if card.value == 'J' && card.suite == @same
return [2+@suite_order.index(card.suite),-card.to_i]
end
end
cards = ARGF.readlines
#~ cards = %w{Diamonds Ah Jd Jh Kc 9c}
I addressed this by generating a suit order in alternating colors
starting with the trump suit. Then some hand checking of the various
possibilities showed me that the only adjustment needed was to swap the
second and fourth suits if the hand was void in the second suit. Given
that adjustment, any combination of suits in the hand should yield
properly alternating colors.
> I see other solutions trying very hard to make sure that two suits
> of the same color do not appear next to each other. As a Euchre
> player,
> I would be quite upset if the order of the suits changed during play.
Hold on there Tiger... I didn't say anything about changing the suit
order. We're only working with the initial sort here.
I believe a "correct" implementation caches the initial suit order
for each player and holds that until the hand is played out (even if
black/red suits eventually touch). Note that you will have to order
the hand twice though, once after the deal and once after Trump is
chosen.
Of course, this is all just *MY* opinion of right. We've already
heard of several others. This quiz just isn't about those... ;)
James Edward Gray II
>> I see other solutions trying very hard to make sure
>> that two suits of the same color do not appear next
>> to each other. As a Euchre player, I would be quite
>> upset if the order of the suits changed during play.
>
> Hold on there Tiger... I didn't say anything about
> changing the suit order. We're only working with the
> initial sort here.
>
> I believe a "correct" implementation caches the
> initial suit order for each player and holds that
> until the hand is played out (even if black/red
> suits eventually touch). Note that you will have to
> order the hand twice though, once after the deal and
> once after Trump is chosen.
Well, the sort before the trump is chosen is outside the scope of
this quiz (and trivial to implement), so we're only talking about the
sort after trump has been chosen. However you are right, the sort
routine would only be called once per hand, not once per card. There
are several other card games where sort is called once per card, and I
didn't stop to think that Euchre wasn't one of them.
That being said, in a card game where suits are important, I would
still prefer the suits in my hand to remain in a predictable order
rather than get shuffled around (no pun intended) depending on what
cards were in my hand.
> Of course, this is all just *MY* opinion of right.
> We've already heard of several others. This quiz
> just isn't about those... ;)
And likewise this is just *my* opinion of right. But I would argue
that this quiz *is* about other opinions, since the original quiz
stated:
> The three non-Trump suits are equal, but it is good
? interface to sort the by suit alternating red, black,
> red, and black, I think.
If the "I think" had been left off, this would have simply been a
requirement of the quiz. But since it is there, I would argue that this
topic is open for discussion. I just gave a solution that showed a
differing viewpoint. I'm sorry if I stepped on any toes.
- Warren Brown
P.S. Tiger??? :o)
> I'm sorry if I stepped on any toes.
No offense taken. I just wanted it to be clear I'm against shifting
hands too.
> P.S. Tiger??? :o)
I'm just full of wacky expressions. ;)
James Edward Gray II
Of course, my wife would also say, "How can you call that Jack a Club (assuming
Clubs are Trump), when it has Spades printed on it?" She's got me there.
My wife's analysis of Euchre's card order is really right on: It's unusual.
Because of that, the traditional sort() tool won't get you all the way to a
solution. That makes us think about other options.
Don't get me wrong, many solutions bent sort() to their will. However, as I
said in the quiz, I was looking as much at how to know the answer is correct as
I was at just having an answer. To me, the weighted sorts are hard to prove and
no one did it to my satisfaction. Given that, I want to look at the other major
approach people hit on.
The following is the beginning of Bob Showalter's solution:
#!/usr/local/bin/ruby -w
# Euchre hand sorter
CARDS = %w(A K Q J T 9)
suits = %w(Spades Hearts Clubs Diamonds)
# read and check input data
trump = gets.chomp
raise "Invalid Trump Suit" unless suits.include? trump
hand = []
5.times do
card = gets.chomp
raise "Invalid card #{card}" unless
card.length == 2 &&
CARDS.include?(card[0,1]) &&
suits.find { |suit| suit[0,1].downcase == card[1,1] }
raise "Duplicate card #{card}" if hand.include? card
hand << card
end
# ...
Bob begins by defining the traditional card order, and the four suits.
The next chunk of code reads the trump suit and hand into variables. This code
is careful to ensure proper input. Suits must be one of the known four and
cards must be two characters, have a known face and suit, and not be duplicated.
# ...
# rotate trump suit to front
suits.push(suits.shift) while suits.first != trump
# if hand is void in second suit, swap second and fourth
# (this keeps output in alternating colors)
unless hand.find { |card| card[1,1] == suits[1][0,1].downcase }
suits[1], suits[3] = suits[3], suits[1]
end
# ...
This chunk of code does two things. First, the suits are "rotated" until trump
is the first suit. Note that this is why "CARDS" was declared as a constant but
"suits" is just a variable. Good hints for the reader in this code.
The second thing the code does is the trick many hit on to defeat my extra
red/black requirement. If we have four suits ordered red, black, red, black (or
black, red, black, red), the only edge case we can do anything about is when the
player doesn't have the second suit. Here's the mapping of possible orderings
(assuming Spades is trump) to show what I mean:
all four suits (black, red, black, red ordering works fine)
Spades, Hearts, and Clubs (already correct)
Hearts, Clubs, and Diamonds (already correct)
Spades, Hearts, and Diamonds (can't move trump to fix)
Spades, Clubs, and Diamonds (the fixable case)
just two suits (moves won't change anything)
a single suit (already correct)
In the fixable case, swapping the (missing) second and (present) fourth suits
resolves the issue. That's what you're seeing in the code above.
# ...
# generate a sort order
deck = []
suits.each do |suit|
CARDS.each do |card|
deck << card + suit[0,1].downcase
end
end
# ...
Given that the correct suit order has been established, the entire Euchre deck
is built, in traditional card order.
# ...
# move bowers to front
deck.insert(0, deck.delete_at(3))
deck.insert(1, deck.delete_at(15))
# ...
With the full deck in proper suit order, the only thing that remains is to shift
the bowers to the head of the pack. Their location is assured, thus the use of
magic numbers above, though index() is an alternative.
# ...
# output sorted hand (n.b. Array#& (intersection) seemed to work, but
# is the order guaranteed?)
puts trump
puts deck.select { |card| hand.include? card }
With the work out of the way, showing the results is trivial. Print trump and
then just walk the deck printing any card in the hand.
I think that solution is very straight-forward and easy to accept as correct,
even without a large group of tests. Let's look at one more variation of the
same theme, by Dominik Bathon:
order = {
"Spades" => "JsJcAsKsQsTs9sAdKdQdJdTd9dAcKcQcTc9cAhKhQhJhTh9h",
"Hearts" => "JhJdAhKhQhTh9hAsKsQsJsTs9sAdKdQdTd9dAcKcQcJcTc9c",
"Clubs" => "JcJsAcKcQcTc9cAhKhQhJhTh9hAsKsQsTs9sAdKdQdJdTd9d",
"Diamonds" => "JdJhAdKdQdTd9dAcKcQcJcTc9cAhKhQhTh9hAsKsQsJsTs9s"
}
trump = gets.strip
cards = readlines.map { |l| l.strip }
o = order[trump].dup
# do we have a card of the 2nd suit
unless cards.any? { |card| card[1] == o[15] }
# if not replace second suit by the last
o[14, 12] = o[36, 12]
end
puts trump, cards.sort_by { |card| o.index(card) }
This is basically the same thing, with prebuilt decks. The code here just
selects which of the four possible decks to use base on the trump indication in
the input.
Again we see the second and fourth suits swapped, if needed. This time it is
done with a simple String copy.
Finally, cards are sorted, by their position in the complete deck.
My thanks to all the card sharks who solved the quiz and even taught me
alternate hand ordering schemes.
Tomorrow we're off to the Pinewood Derby, so everyone pick up a car kit sometime
this evening...
Having much to learn, I have since discovered that it works fine even if
SUITS is declared as a constant. You only get a warning if you reassign
something else to SUITS; you can maniuplate the array that SUITS
references without getting a warning.
Yes, but I think it was more correct as is, since it showed that it was
a variable and could change.
James Edward Gray II
--
Posted via http://www.ruby-forum.com/.