# paulfioravanti's solution

## to Poker in the Ruby Track

Published at May 16 2019 · 0 comments
Instructions
Test suite
Solution

Pick the best hand(s) from a list of poker hands.

See wikipedia for an overview of poker hands.

For installation and learning resources, refer to the Ruby resources page.

For running the tests provided, you will need the Minitest gem. Open a terminal window and run the following command to install minitest:

``````gem install minitest
``````

If you would like color output, you can `require 'minitest/pride'` in the test file, or note the alternative instruction, below, for running the test file.

Run the tests from the exercise directory using the following command:

``````ruby poker_test.rb
``````

To include color from the command line:

``````ruby -r minitest/pride poker_test.rb
``````

## Source

Inspired by the training course from Udacity. https://www.udacity.com/course/viewer#!/c-cs212/

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

### poker_test.rb

``````require 'minitest/autorun'
require_relative 'poker'

class PokerTest < Minitest::Test
def test_one_hand
high_of_jack = %w(4S 5S 7H 8D JC)
game = Poker.new([high_of_jack])
assert_equal [high_of_jack], game.best_hand
end

def test_highest_card
skip
high_of_8 = %w(4S 5H 6S 8D 2H)
high_of_queen = %w(2S 4H 6C 9D QH)
game = Poker.new([high_of_8, high_of_queen])
assert_equal [high_of_queen], game.best_hand
end

def test_highest_card_10
skip
high_of_8 = %w(4D 5S 6S 8D 3C)
high_of_10 = %w(2S 4C 7S 9H 10H)
game = Poker.new([high_of_8, high_of_10])
assert_equal [high_of_10], game.best_hand
end

def test_nothing_vs_one_pair
skip
high_of_king = %w(4S 5H 6C 8D KH)
pair_of_4 = %w(2S 4H 6S 4D JH)
game = Poker.new([high_of_king, pair_of_4])
assert_equal [pair_of_4], game.best_hand
end

def test_two_pair
skip
pair_of_2 = %w(4S 2H 6S 2D JH)
pair_of_4 = %w(2S 4H 6C 4D JD)
game = Poker.new([pair_of_2, pair_of_4])
assert_equal [pair_of_4], game.best_hand
end

def test_one_pair_vs_double_pair
skip
pair_of_8 = %w(2S 8H 6S 8D JH)
fives_and_fours = %w(4S 5H 4C 8C 5C)
game = Poker.new([pair_of_8, fives_and_fours])
assert_equal [fives_and_fours], game.best_hand
end

def test_two_double_pair
skip
eights_and_twos = %w(2S 8H 2D 8D 3H)
fives_and_fours = %w(4S 5H 4C 8S 5D)
game = Poker.new([eights_and_twos, fives_and_fours])
assert_equal [eights_and_twos], game.best_hand
end

def test_another_two_double_pair
skip
aces_and_twos = %w(2S AH 2C AD JH)
queens_and_jacks = %w(JD QH JS 8D QC)
game = Poker.new([aces_and_twos, queens_and_jacks])
assert_equal [aces_and_twos], game.best_hand
end

def test_double_pair_vs_three
skip
eights_and_twos = %w(2S 8H 2H 8D JH)
three_of_4 = %w(4S 5H 4C 8S 4H)
game = Poker.new([eights_and_twos, three_of_4])
assert_equal [three_of_4], game.best_hand
end

def test_two_three
skip
three_twos = %w(2S 2H 2C 8D JH)
three_aces = %w(4S AH AS 8C AD)
game = Poker.new([three_twos, three_aces])
assert_equal [three_aces], game.best_hand
end

def test_three_vs_straight
skip
three_of_4 = %w(4S 5H 4C 8D 4H)
straight = %w(3S 4D 2S 6D 5C)
game = Poker.new([three_of_4, straight])
assert_equal [straight], game.best_hand
end

def test_a_5_high_straight
skip
three_of_4 = %w(4S 5H 4C 8D 4H)
straight_to_5 = %w(4D AH 3S 2D 5C)
game = Poker.new([three_of_4, straight_to_5])
assert_equal [straight_to_5], game.best_hand
end

def test_two_straights
skip
straight_to_8 = %w(4S 6C 7S 8D 5H)
straight_to_9 = %w(5S 7H 8S 9D 6H)
game = Poker.new([straight_to_8, straight_to_9])
assert_equal [straight_to_9], game.best_hand
end

def test_5_high_straight_vs_other_straight
skip
straight_to_jack = %w(8H 7C 10D 9D JH)
straight_to_5 = %w(4S AH 3S 2D 5H)
game = Poker.new([straight_to_jack, straight_to_5])
assert_equal [straight_to_jack], game.best_hand
end

def test_straight_vs_flush
skip
straight_to_8 = %w(4C 6H 7D 8D 5H)
flush_to_7 = %w(2S 4S 5S 6S 7S)
game = Poker.new([straight_to_8, flush_to_7])
assert_equal [flush_to_7], game.best_hand
end

def test_two_flushes
skip
flush_to_8 = %w(3H 6H 7H 8H 5H)
flush_to_7 = %w(2S 4S 5S 6S 7S)
game = Poker.new([flush_to_8, flush_to_7])
assert_equal [flush_to_8], game.best_hand
end

def test_flush_vs_full
skip
flush_to_8 = %w(3H 6H 7H 8H 5C)
full = %w(4S 5H 4C 5D 4H)
game = Poker.new([flush_to_8, full])
assert_equal [full], game.best_hand
end

def test_two_fulls
skip
full_of_4_by_9 = %w(4H 4S 4D 9S 9D)
full_of_5_by_8 = %w(5H 5S 5D 8S 8D)
game = Poker.new([full_of_4_by_9, full_of_5_by_8])
assert_equal [full_of_5_by_8], game.best_hand
end

def test_full_vs_square
skip
full = %w(4S 5H 4D 5D 4H)
square_of_3 = %w(3S 3H 2S 3D 3C)
game = Poker.new([square_of_3, full])
assert_equal [square_of_3], game.best_hand
end

def test_two_square
skip
square_of_2 = %w(2S 2H 2C 8D 2D)
square_of_5 = %w(4S 5H 5S 5D 5C)
game = Poker.new([square_of_2, square_of_5])
assert_equal [square_of_5], game.best_hand
end

def test_square_vs_straight_flush
skip
square_of_5 = %w(4S 5H 5S 5D 5C)
straight_flush_to_10 = %w(7S 8S 9S 6S 10S)
game = Poker.new([square_of_5, straight_flush_to_10])
assert_equal [straight_flush_to_10], game.best_hand
end

def test_two_straight_flushes
skip
straight_flush_to_8 = %w(4H 6H 7H 8H 5H)
straight_flush_to_9 = %w(5S 7S 8S 9S 6S)
game = Poker.new([straight_flush_to_8, straight_flush_to_9])
assert_equal [straight_flush_to_9], game.best_hand
end

def test_highest_card_down_to_fifth_card
skip
high_of_8_low_of_3 = %w(3S 5H 6S 8D 7H)
high_of_8_low_of_2 = %w(2S 5D 6D 8C 7S)
game = Poker.new([high_of_8_low_of_3, high_of_8_low_of_2])
assert_equal [high_of_8_low_of_3], game.best_hand
end

def test_three_hand_with_tie
skip
spade_straight_to_9 = %w(9S 8S 7S 6S 5S)
diamond_straight_to_9 = %w(9D 8D 7D 6D 5D)
three_of_4 = %w(4D 4S 4H QS KS)
game = Poker.new(hands)
end
end``````
``````# frozen_string_literal: true

require "bigdecimal"
class Poker
# Assign a value to a card of less than 1 dependent on its rank
# so that:
# - cards can be compared to each other
# - the value can be tallied along with hand scores
CARD_VALUE = ->(num) { BigDecimal(num) * BigDecimal("0.01") }
private_constant :CARD_VALUE
RANKS = %w[a 2 3 4 5 6 7 8 9 10 J Q K A].freeze
private_constant :RANKS

module Score
module HighLowCard
module_function

def call(hand)
min, max = hand.cards.minmax
max.value + min.value / 2
end
end

module OnePair
POINTS = 1
private_constant :POINTS

module_function

def call(hand)
max_pair_card = Score.find_multicard(hand, 1).max
max_pair_card ? POINTS + max_pair_card.value : 0
end
end

module TwoPair
POINTS = 2
private_constant :POINTS

module_function

def call(hand)
pair_cards = Score.find_multicard(hand, 1)
pair_cards.length > 1 ? POINTS + pair_cards.max.value : 0
end
end

module ThreeOfAKind
POINTS = 3
private_constant :POINTS

module_function

def call(hand)
three_of_a_kind_card = Score.find_multicard(hand, 2).max
three_of_a_kind_card ? POINTS + three_of_a_kind_card.value : 0
end
end

module Straight
ACES = ->(card) { card.rank == "A" }
private_constant :ACES
POINTS = 5
private_constant :POINTS
SINGLE_CARD_GAP = CARD_VALUE.call(1)
private_constant :SINGLE_CARD_GAP

module_function

def call(hand)
straight = straight(hand)
straight ? POINTS + straight.last.value : 0
end

def straight(hand)
cards = hand.cards.sort
numbered_straight(cards) || ace_low_straight(cards)
end

def numbered_straight(cards)
straight =
cards
.each_cons(2)
.all? { |(card1, card2)| card2 - card1 == SINGLE_CARD_GAP }
straight ? cards : nil
end
private_class_method :numbered_straight

def ace_low_straight(cards)
aces, other_cards = cards.partition(&ACES)
return nil unless aces.length == 1

ace_low = Card.new("a#{aces.first.suit}")
numbered_straight(other_cards.prepend(ace_low))
end
private_class_method :ace_low_straight
end

module Flush
POINTS = 8
private_constant :POINTS

module_function

def call(hand)
flush?(hand) ? POINTS + hand.cards.max.value : 0
end

def flush?(hand)
hand.suits.uniq.one?
end
end

module FullHouse
POINTS = 13
private_constant :POINTS

module_function

def call(hand)
three_set, other_cards = split_hand(hand)
return 0 if not_full_house?(three_set, other_cards, hand.ranks)

throw(:halt, POINTS + three_set.first.value)
end

def split_hand(hand)
hand
.cards
.sort
.partition { |card| Score.multiple?(card, hand.ranks, 2) }
end
private_class_method :split_hand

def not_full_house?(three_set, other_cards, ranks)
three_set.empty? ||
other_cards.none? { |card| Score.multiple?(card, ranks, 1) }
end
end

module FourOfAKind
POINTS = 21
private_constant :POINTS

module_function

def call(hand)
square_cards, _other_cards = split_hand(hand)
if (square = square_cards.first)
throw(:halt, POINTS + square.value)
else
0
end
end

def split_hand(hand)
hand
.cards
.partition { |card| Score.multiple?(card, hand.ranks, 3) }
end
end

module StraightFlush
POINTS = 34
private_constant :POINTS

module_function

def call(hand)
if (straight = Straight.straight(hand)) && Flush.flush?(hand)
throw(:halt, POINTS + straight.last.value)
else
0
end
end
end

module_function

def calculate(hand)
catch(:halt) do
constants.sum { |mod| const_get(mod).call(hand) }
end
end

def find_multicard(hand, floor)
hand
.cards
.each
.with_object([hand.ranks, floor])
.uniq
end

acc << card if multiple?(card, ranks, floor)
end

def multiple?(card, ranks, floor)
ranks.count(card.rank) > floor
end
end
private_constant :Score

class Card
include Comparable

# Lookahead to non-digit character in string
SUIT = /(?=\D)/.freeze
private_constant :SUIT

def initialize(card)
@rank, @suit = card.split(SUIT)
end

def to_s
rank + suit
end

def value(rank = self.rank)
RANKS.index(rank).then(&CARD_VALUE)
end

def -(other)
value - value(other.rank)
end

private

def hash
value.hash
end

def eql?(other)
value.eql?(value(other.rank))
end

def <=>(other)
value <=> value(other.rank)
end
end
private_constant :Card

class Hand
include Comparable

def initialize(cards)
@cards = cards.map { |card| Card.new(card) }
@score = Score.calculate(self)
end

def to_a
cards.map(&:to_s)
end

def ranks
cards.map(&:rank)
end

def suits
cards.map(&:suit)
end

protected

private

def <=>(other)
score <=> other.score
end
end
private_constant :Hand

def initialize(hands)
@hands = hands.map { |hand| Hand.new(hand) }
end

def best_hand
first_hand, *rest = hands
initial_best_hands = initial_best_hands(first_hand)

rest
.each_with_object(initial_best_hands, &method(:compare_best_hand))
.fetch(:best_hands)
end

private

def initial_best_hands(first_hand)
{ best_hands: [first_hand.to_a], best_hand: first_hand }
end

def compare_best_hand(hand, acc)
best_hand = acc[:best_hand]
if hand == best_hand
acc[:best_hands] << hand.to_a
elsif hand > best_hand
acc[:best_hands] = [hand.to_a]
acc[:best_hand] = hand
else
acc
end
end
end``````