soffes/hands

View on GitHub
lib/hands/hand_detection.rb

Summary

Maintainability
A
1 hr
Test Coverage
module Hands
  module HandDetection
    # @return [Hash] A hash with `:type` and `:cards` keys.
    def best_hand
      response = {}
      HAND_ORDER.reverse.each do |type|
        cards = self.send(type.to_sym)
        next unless cards
        response[:type] = type
        response[:cards] = cards
        break
      end
      response
    end

    # @return [Array] Array of {Card} objects with the highest {Card} first
    def high_card
      self.cards.sort.reverse
    end

    # @return [Array, Nil] Array of {Card} objects with the pair first and kickers in decending order or `nil` if there isn't a pair in the {Hand}
    def pair
      self.pairs(1)
    end

    # @return [Array, Nil] Array of {Card} objects with the pairs first and kickers in decending order or `nil` if there isn't two pair in the {Hand}
    def two_pair
      self.pairs(2)
    end

    # @return [Array, Nil] Array of {Card} objects with the three of a kind first and kickers in decending order or `nil` if there isn't three of a kind in the {Hand}
    def three_of_a_kind
      self.kinds(3)
    end

    # @return [Array, Nil] Array of {Card} objects with the straight in decending order or `nil` if there isn't a straight in the {Hand}
    def straight
      # Require at least 5 cards
      return nil unless self.cards.length >= 5
      
      # Sort
      cs = self.cards.sort.reverse

      # Ace's low
      return cs if cs.first.value == 'a' and cs[1].value == '5'

      # Check succession
      return nil unless succession?

      # Avoid wrap-around
      values = self.cards.collect(&:value)
      return nil if values.include?('k') and values.include?(2)

      cs
    end

    # @return [Array, Nil] Array of {Card} objects with the flush in decending order or `nil` if there isn't a flush in the {Hand}
    def flush
      # If all of the {Card}s are the same suit, we have a flush
      return nil unless self.cards.length >= 5 and self.suits.length == 1
      self.cards.sort.reverse
    end

    # @return [Array, Nil] Array of {Card} objects with the full house in decending order or `nil` if there isn't a full house in the {Hand}
    def full_house
      dupes = self.duplicates
      return nil unless self.cards.length >= 5 and dupes.length == 2
      
      # Ensure all {Card}s are one of the duplicates
      self.cards.each do |card|
        return nil unless dupes.include? card.value
      end

      self.cards.sort.reverse
    end

    # @return [Array, Nil] Array of {Card} objects with the four of a kind first the kicker in decending order or `nil` if there isn't four of a kind in the {Hand}
    def four_of_a_kind
      self.kinds(4)
    end

    # @return [Array, Nil] Array of {Card} objects with the straight flush in decending order or `nil` if there isn't a straight flush in the {Hand}
    def straight_flush
      return nil unless self.flush
      self.straight
    end

    # @return [Array, Nil] Array of {Card} objects with the royal flush in decending order or `nil` if there isn't a straight flush in the {Hand}
    def royal_flush
      # Require a straight flush first
      sf = self.straight_flush
      return nil unless sf

      # Make sure the highest card is an ace
      return nil unless sf.first.value == 'a'

      sf
    end

    # Hand's index
    #
    # Mainly used for internal reasons when comparing {Hand}.
    #
    # @return [Integer] index of the {Hand}'s rank
    # @see HAND_ORDER
    def hand_index
      best = self.best_hand
      return -1 if best.nil?
      HAND_ORDER.index(best[:type].to_s)
    end

  protected

    def duplicates
      pairs = self.cards.collect(&:value)
      pairs.uniq.select{ |e| (pairs - [e]).size < pairs.size - 1 }
    end

    def pairs(min)
      dupes = self.duplicates
      return nil unless dupes.length >= min

      hand = self.cards.select do |card|
        dupes.include?(card.value)
      end

      hand = hand.sort.reverse
      hand << (self.cards - hand).sort.reverse
      hand.flatten
    end

    def kinds(num)
      dupes = self.duplicates
      return nil unless dupes.length == 1

      hand = self.cards.select do |card|
        dupes.include?(card.value)
      end

      return nil unless hand.length == num

      hand = hand.sort.reverse
      hand << (self.cards - hand).sort.reverse
      hand.flatten
    end
    
    def succession?
      cs = self.cards.sort.reverse
      4.times do |i|
        return false unless cs[i].value_index == cs[i + 1].value_index + 1
      end
      true
    end
  end
end