0

I am working on a deck of cards. My hands are an array of strings. For example:

hand1 = ["10 of ♣", "5 of ♣", "5 of ♠", "A of ♦", "3 of ♦"]

I want to evaluate and compare the hands. How can I identify and compare this object? My example has a pair but if I do something like

hand1.select{ |value| hand1.count(value) > 1 } 

this returns an empty array. If I do

hand1.detect{ |value| hand1.count(value) > 1 } 

it returns nil because both parts are not the same.

It works if I use a hand like

hand1 = ["10 of ♣", "10 of ♣", "J of ♠", "A of ♦", "3 of ♦"] 

but having a suited pair is a problem for my program.

5
  • 3
    If I were you, I'll create a custom type for cards, instead of using strings like "10 of ♣" Commented Jul 17, 2016 at 1:42
  • Agree with @YuHao above. You should create an Object for each card. Commented Jul 17, 2016 at 1:47
  • 1
    The question is not clear. What do you want to identify? Which object do you want to compare with which object? What is the result that you want? Commented Jul 17, 2016 at 1:50
  • The cards are a class, I was saving them to a string because it has been easier, up to this point. My deck class creates the deck so that I do not have duplicates. So, How can I compare the objects then? @YuHao Commented Jul 17, 2016 at 1:55
  • @sawa I want to be able to identiy that the hand has a pair (or what the hand is based on a random draw) and compare it to another to determine a winner. I am a bit new here, so I apologize for the lack of clarity. Commented Jul 17, 2016 at 1:57

4 Answers 4

4

Direct answer

Perform a regular expression check on the strings, to see whether the numbers are equal. Something like:

hand1 = ["10 of ♣", "5 of ♣", "5 of ♠", "A of ♦", "3 of ♦"]
hand1.select do |card|
  value = card[/^\w+/] # This will equal 10, 5, A or 3
  hand1.count { |other_card| other_card.match(value) } > 1
end

This will return an Array of all cards in the hand, for which there is a matching value card in the hand. I'm unclear whether that's exactly the data you want, but you get the idea.

Better answer

Don't do this. Doing everything in Strings is not simpler; your code will end up much more confusing to read/write/test/debug.

Use the original card objects instead, and define suitable methods on the Card class (or whatever you've called it). For example, the implementation could be something like:

class Card
  attr_reader :value, :suit
  def initialize(value, suit)
     @value = value
     @suit = suit
  end

  # I'm not actually using these two methods here, but read
  # them for inspiration!...
  def to_s
    "#{value} of #{suit}"
  end

  def ==(other)
    value == other.value && suit == other.suit
  end
end

hand1 = [Card.new('10', '♣'), ....]

hand1.select do |card|
  hand1.count { |other_card| card.value == other_card.value) } > 1
end

Even better answer:

Let's take this one step further. Rather than defining an Array of cards, and then a bunch of global methods on that Array, why not define a Hand class, which holds this list of Card objects?

Your end result, which is pretty easy to expand upon, would be an additional class like:

class Hand
  attr_accessor :cards
  def initialize(cards = [])
    @cards = cards
  end

  def matching_values
    cards.select do |card|
      cards.count { |other_card| card.value == other_card.value) } > 1
    end
  end
end

You can then just deal with hand objects, and call methods such as hand.matching_values. No more dubious arrays, strings, regexes and global methods. Object Oriented Programming at its finest :)

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for all the help Tom, I am going with your even better answer. So I am calling deck = Deck.new to generate the deck, deck.shuffle, hand1 = deck.draw in my winner class. So I can move that to a Hand class, can I generate multiple hands?
Yes, absolutely. And in order to ensure that you don't have duplicate cards, just keep track of them all in the deck.
0

As Yu mentioned above, you probably want to create a Card class. It's not clear from your question how you want to evaluate the hand, but here's how you can check for a pair:

def pair_for hand
  values = hand.map{|c| c.scan(/\w+/).first }
  values.detect{|v| values.count(v) == 2 }
end

This method does not handle the case for when there are two pair, or three of a kind.

This method returns nil if there is no pair, so that's not so good. However, if you evaluate a hand from highest value to lowest, then nil might be a good choice.

5 Comments

@B Seven will this return a true or false? How do I ask IRB for it? Sorry, code noob here. I plan to make all the other hands as separate methods
@Laurie - It will return the value of a pair (5), if it exists. If not, it will return nil. To test, you can copy and paste into irb: hand = ... and the above method and pair_for hand.
@B Seven When I do that I get NoMethodError: undefined method scan' for #<Card:0x007f835904a9c8 @suit="♣", @value="J"> from (irb):29:in block in pair_for' from (irb):29:in map' from (irb):29:in pair_for' from (irb):32
I see you have created the Card class, but it is not in your question. I wrote the answer for card strings.
@B Seven , I had my card class concluding with a to string. I took that out based on the above feedback.
0

This is one way to do it:

def rank(hand)
  cards = hand.map(&:split)
  ranks = cards.group_by(&:first)
  suits = cards.group_by(&:last)

  case 
  when ranks.one? { |k, v| v.count == 2 }
    "pair"
  when suits.count == 1
    "flush"
  end
end

player1 = rank ["10 of ♣", "5 of ♣", "5 of ♠", "A of ♦", "3 of ♦"]
=> "pair" 

player2 = rank ["10 of ♣", "4 of ♣", "5 of ♣", "A of ♣", "3 of ♣"]
=> "flush" 

I did the easy ones and there is a lot of logic to go. You are certainly better off working with your class as others have stated.

Comments

0

I may be wrong about some of the rules concerning the determination of the winning hand in five-card poker (draw? stud? other?), but the code below can be easily modified to correct any errors in my assumptions.

The "kicker" is the tie-breaker. For example, if two hands both contain straights, the "kicker" is the card with the highest rank (for 3, 4, 5, 6, and 7, 7 is the "kicker"). If both hands have the same kicker, it's a tie. For a hand with, say, two 5's and two queens, the first kicker is the higher of the ranks (the queen), and if that rank is the same for another hand with two pairs, 5 becomes the kicker. If both hands have pairs of queens and a pair of 5's, it's a tie. Note the possibility of multiple "winners".

Hands are compared by associated arrays, which I've called "hand values". The first element of the array is a non-negative integer representing the type of hand, with 0 denoting "high card" (all cards have different ranks and not all suits are the same), 1 denoting one pair, and so on, up to 8 for a straight-flush. The second element of the array is the kicker (if there is one) and in one case (two pair) there is a third element that is the second kicker, should the first one not decide the winner. Arrays are ordered lexicographically in Ruby (see Array#<=>), so we can determine the winner (or winners in the case of a tie) by finding which array is greatest.

I've assumed a hand is represented like so:

[['J', :clubs], ['2', :spades], ['J', :diamonds], ['2', :hearts], ['J', :spades]]

This may entail a conversion from some other format (that I've not discussed).

Code

RANK_STR_TO_RANK = %w| 2 3 4 5 6 7 8 9 J Q K A |.
  each_with_index.with_object({}) { |(s,i),h| h[s] = i }
  #=> {"2"=>0, "3"=>1, "4"=>2, "5"=>3, "6"=>4, "7"=>5,
  #    "8"=>6, "9"=>7, "J"=>8, "Q"=>9, "K"=>10, "A"=>11} 

HIGH_CARD       = 0
TWO_OF_KIND     = 1
TWO_PAIR        = 2
THREE_OF_KIND   = 3
STRAIGHT        = 4
FLUSH           = 5
FULL_HOUSE      = 6
FOUR_OF_KIND    = 7
STRAIGHT_FlUSH  = 8

def winners(hands)
  hands_and_values = hands.map { |hand| [hand, hand_value(hand)] }
  winning_value = hands_and_values.map(&:last).max
  hands_and_values.select { |_, value| value == winning_value }.map(&:first)
end 

def hand_value(hand)
  ranks, suits = convert(hand).transpose
  case
  when straight_flush?(ranks, suits)    then [STRAIGHT_FlUSH, ranks.max]
  when (kicker = four_of_kind?(ranks))  then [FOUR_OF_KIND, kicker]
  when (kicker = full_house?(ranks))    then [FULL_HOUSE, kicker]
  when flush?(suits)                    then [FLUSH]
  when straight?(ranks)                 then [STRAIGHT, ranks.max]
  when (kicker = three_of_kind?(ranks)) then [THREE_OF_KIND, kicker]
  when (kickers = two_pair?(ranks))     then [TWO_PAIR, *kickers]
  else                                       [HIGH_CARD, ranks.max]
  end
end 

def convert(hand)
  hand.map { |rank_str, suit| [RANK_STR_TO_RANK[rank_str], suit] }
end

def straight_flush?(ranks, suits)
  straight?(ranks) && flush?(suits)
end

def four_of_kind?(ranks)
  h = group_by_rank(ranks)
  h.key?(4) ? h[4].first : false
end

def full_house?(ranks)
  h = group_by_rank(ranks)
  (h.key?(3) && h.key?(2)) ? h[3].first : false
end

def flush?(suits)
  suits.uniq.size == 1
end

def straight?(ranks)
  smallest, largest = ranks.minmax
  largest - smallest == 4 && ranks.uniq.size == 5 
end

def three_of_kind?(ranks)
  h = group_by_rank(ranks)
  h.key?(3) ? h[3].first : false
end

def two_pair?(ranks)
  h = group_by_rank(ranks)
  h.key?(2) && h[2].size==2 ? h[2].sort.reverse : false
end

def one_pair?(ranks)
  h = group_by_rank(ranks)
  h.key?(2) ? h[2].first : false
end

def group_by_rank(ranks)
  ranks.group_by(&:itself).each_with_object({}) { |(k,v),h| (h[v.size] ||= []) << k }
end  

Example

hands = [[['J', :clubs], ['2', :spades], ['J', :diamonds], ['2', :hearts], ['J', :spades]],
         [['Q', :hearts], ['3', :clubs], ['Q', :clubs], ['A', :hearts], ['3', :spades]],
         [['4', :hearts], ['5', :spades], ['6', :hearts], ['7', :hearts], ['8', :spades]],
         [['5', :clubs], ['9', :clubs], ['K', :clubs], ['A', :clubs], ['8', :clubs]]]

winners(hands)
  #=> [
  #    [["J", :clubs], ["2", :spades], ["J", :diamonds], ["2", :hearts], ["J", :spades]]
  #   ]

Notes

In winners, the intermediate values are as follows.

hands_and_values
  #=> [[[["J", :clubs], ["2", :spades], ["J", :diamonds], ["2", :hearts], ["J", :spades]],
  #      [6, 8]],
  #    [[["Q", :hearts], ["3", :clubs], ["Q", :clubs], ["A", :hearts], ["3", :spades]],
  #      [2, 9, 1]],
  #    [[["4", :hearts], ["5", :spades], ["6", :hearts], ["7", :hearts], ["8", :spades]],
  #      [4, 6]],
  #    [[["5", :clubs], ["9", :clubs], ["K", :clubs], ["A", :clubs], ["8", :clubs]],
  #      [5]]
  #   ]

winning_value
  #=> [6, 8]

The value of the first hand is 6, 8, 0, which means that it is a FULL_HOUSE (6) with 3 jacks (rank=>8) and 2 2's (rank=>0). If two hands have full houses, the rank of the three-of-a-kind is the "kicker", or tie-breaker.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.