Poker

A Poker program written in Ruby.

Rank (1..14, 14 is Ace)
Suit (spade, heart, diamond, club)
hand (cards)

NoHandError = Class.new(StandardError)

# Return the best hand: poker([hand, ...]) => "hand"
def poker hands
  raise NoHandError if hands.empty?
  return hands.max_by { |hand| hand_rank hand }
end

# return a value indicating the ranking of a hand
# 9 diffrent kinds of hand
# straight flush: 8
# 4 of a kind:    7
# full house:     6
# flush:          5
# straight:       4
# 3 of a kind:    3
# 2 pair:         2
# kind:           1
# high card:      0
def hand_rank(hand)
  ranks = card_ranks(hand) # card_ranks return the ranks in sorted order

  if straight(ranks) && flush(hand)
    [8, ranks.max] # 2 3 4 5 6 => [8, 6], 6 7 8 9 T => [8, T]
  elsif kind(4, ranks)
    [7, kind(4, ranks), kind(1, ranks)] # 9 9 9 9 3 => [7, 9, 3]
  elsif kind(3, ranks) && kind(2, ranks) # 8 8 8 K K => [6, 8, 13]
    [6, kind(3, ranks), kind(2, ranks)]
  elsif flush(hand)
    [5, ranks]
  elsif straight(ranks)
    [4, ranks.max]
  elsif kind(3, ranks)
    [3, kind(3, ranks), ranks]
  elsif two_pair(ranks)
    [2, kind(2, ranks), ranks]
  elsif kind(2, ranks)
    [1, kind(2, ranks), ranks]
  else
    [0, ranks]
  end
end

# Return a array of ranks, sorted with higher first.
# Example usage:
# ["6C", "7C", "8C", "9C", "TC"] => [10, 9, 8, 7, 6]
def card_ranks(cards)
  ranks = cards.map { |card| '--23456789TJQKA'.index(card[0]) }
  ranks.sort { |a, b| b <=> a }
end

# Return True if the ordered ranks form a 5-card straight.
def straight(ranks)
  (ranks.max - ranks.min) == 4 && ranks.uniq.size == 5
end

# Return True if all the cards have the same suit.
def flush(hand)
  suits = hand.map { |card| card[1] }
  suits.uniq.one?
end

# Return the first rank that this hand has exactly n of.
# Return None if there is no n-of-a-kind in the hand.
def kind(n, ranks)
  ranks.each { |r| return r if ranks.count(r) == n }
  return nil
end

# If there are two pair, return the two ranks as a
# array: [highest, lowest]; otherwise return nil.
def two_pair(ranks)
  pair = kind(2, ranks)
  lowpair = kind(2, ranks.reverse)

  if pair && lowpair != pair
    [pair, lowpair]
  end
end

Tests

require "minitest/autorun"
require_relative 'poker'

class TestPoker < Minitest::Test
  def setup
    @sf = "6C 7C 8C 9C TC".split # straight flush
    @fk = "9D 9H 9S 9C 7D".split # four of a kind
    @fh = "TD TC TG 7C 7D".split # full house
    @tp = "5S 5D 9H 9C 6S".split # two_pairs

    @fk_ranks = card_ranks(@fk)
    @tp_ranks = card_ranks(@tp)
  end

  def test_kind1
    assert kind(4, @fk_ranks) == 9
  end

  def test_kind2
    assert kind(3, @fk_ranks) == nil
  end

  def test_kind3
    assert kind(2, @fk_ranks) == nil
  end

  def test_kind1
    assert kind(1, @fk_ranks) == 7
  end

  def test_two_pair1
    assert two_pair(@fk_ranks) == nil
  end

  def test_two_pair2
    assert two_pair(@tp_ranks) == [9, 5]
  end

  def test_straight1
    assert straight([9, 8, 7, 6, 5]) == true
  end

  def test_straight2
    assert straight([9, 8, 8, 6, 5]) == false
  end

  def test_flush1
    assert flush(@sf) == true
  end

  def test_flush2
    assert flush(@fk) == false
  end

  def test_card_ranks1
    assert card_ranks(@sf) == [10, 9, 8, 7, 6]
  end

  def test_card_ranks2
    assert card_ranks(@fk) == [9, 9, 9, 9, 7]
  end

  def test_card_ranks3
    assert card_ranks(@fh) == [10, 10, 10, 7, 7]
  end

  # Test cases for the functions in poker program
  def test_hands
    assert poker([@sf, @fk, @fh]) == @sf
  end

  def test_hands2
    assert poker([@fk, @fh]) == @fk
  end

  def test_identical_hands
    assert poker([@fh, @fh]) == @fh
  end

  def test_one_player
    assert poker([@sf]) == @sf
  end

  def test_hundred_players
    assert poker(Array.new(1, @sf) + Array.new(99, @fh)) == @sf
  end

  def test_no_players
    assert_raises(NoHandError) { poker([]) }
  end

  def test_hand_rank1
    assert hand_rank(@sf) == [8, 10]
  end

  def test_hand_rank2
    assert hand_rank(@fk) == [7, 9, 7]
  end

  def test_hand_rank3
    assert hand_rank(@fh) == [6, 10, 7]
  end
end