#!/usr/bin/ruby

module Enumerable

  def sum
    total = 0
    for value in self
      total += value
    end
    total
  end

end

class Roller

  def Roller.roll(given = [])
    given.collect! do |roll|
      [roll]
    end
    while given.size < 5
      given << (1..6)
    end
    rolls = [0] * 5
    for rolls[0] in given[0]
      for rolls[1] in given[1]
        for rolls[2] in given[2]
          for rolls[3] in given[3]
            for rolls[4] in given[4]
              yield(rolls)
            end
          end
        end
      end
    end
  end

end

THREE_KIND = "three of a kind"
FOUR_KIND = "four of a kind"
FIVE_KIND = "five of a kind"
STRAIGHT = "straight"
FULL_HOUSE = "full house"
NOTHING = "nothing"
INSIDE_STRAIGHT = "inside straight (no pair)"
INSIDE_STRAIGHT_WITH_PAIR = "inside straight (with pair)"
OUTSIDE_STRAIGHT = "outside straight"
ONE_PAIR = "one pair"
TWO_PAIR = "two pair"

def getCounts(rolls)
  counts = [0] * 6
  for roll in rolls
    counts[roll - 1] += 1
  end
  counts.sort.reverse
end

def handType(rolls)
  counts = getCounts(rolls)
  if counts[0] == 5
    FIVE_KIND
  elsif counts[0] == 4
    FOUR_KIND
  elsif counts[0] == 3
    if counts[1] == 2
      FULL_HOUSE
    else
      THREE_KIND
    end
  elsif counts[0] == 1
    sortedRolls = rolls.sort
    if sortedRolls == [1, 2, 3, 4, 5] || sortedRolls == [2, 3, 4, 5, 6]
      STRAIGHT
    elsif(sortedRolls == [1, 2, 3, 4, 6] || 
          sortedRolls == [1, 2, 3, 5, 6] ||
          sortedRolls == [1, 2, 4, 5, 6] ||
          sortedRolls == [1, 3, 4, 5, 6])
      INSIDE_STRAIGHT
    else
      NOTHING
    end
  elsif counts[0] == 2
    if counts[1] == 2
      TWO_PAIR
    else
      uniqueRolls = rolls.sort.uniq
      if uniqueRolls == [2, 3, 4, 5]
        OUTSIDE_STRAIGHT
      elsif(uniqueRolls == [1, 2, 3, 5] ||
            uniqueRolls == [1, 2, 4, 5] ||
            uniqueRolls == [1, 3, 4, 5] ||
            uniqueRolls == [2, 3, 4, 6] ||
            uniqueRolls == [2, 3, 5, 6] ||
            uniqueRolls == [2, 4, 5, 6])
        INSIDE_STRAIGHT_WITH_PAIR
      else
        ONE_PAIR
      end
    end
  else
    raise "I don't know what type of hand this is: #{rolls}"
  end
end

def hands(given = [])
  hash = Hash.new(0)
  Roller.roll(given) do |roll|
    hash[handType(roll)] += 1
  end
  totalCount = hash.values.sum
  for name, value in hash
    hash[name] = value.to_f / totalCount
  end
  hash
end

Keepers = {
  ONE_PAIR=>[1, 1],
  TWO_PAIR=>[1, 1, 2, 2],
  INSIDE_STRAIGHT=>[1, 3, 4, 5],
  INSIDE_STRAIGHT_WITH_PAIR=>[1, 1],
  THREE_KIND=>[1, 1, 1],
  NOTHING=>[],
  FULL_HOUSE=>[1, 1, 1, 2, 2],
  STRAIGHT=>[1, 2, 3, 4, 5],
  OUTSIDE_STRAIGHT=>[2, 3, 4, 5],
  FOUR_KIND=>[1, 1, 1, 1, 1],
  FIVE_KIND=>[1, 1, 1, 1, 1],
}

Winnings = {
  FULL_HOUSE=>2,
  STRAIGHT=>2,
  THREE_KIND=>1,
  FOUR_KIND=>2,
  FIVE_KIND=>3,
}

DoublingBug = true

def showHands(baseProbability, hands, canDouble)
  puts "                              DOUBLED"
  puts "    PROBABILITY  WIN  PAYOFF   PAYOFF"
  format = "    %11.9f  %3d  %1.4f  %7.4f  %s"
  totalProbability = 0
  totalPayoff = 0
  totalDoubledPayoff = 0
  for name, probability in hands
    winning = Winnings[name] || 0
    payoff = probability * winning
    if canDouble
      if winning == 0
        doubledPayoff = -1 * probability
      else
        doubledPayoff = if DoublingBug
                          3 * payoff
                        else
                          2 * payoff
                        end
      end
    else
      doubledPayoff = 0
    end
    puts format % [probability, winning, payoff, doubledPayoff, name]
    totalProbability += probability
    totalPayoff += payoff
    totalDoubledPayoff += doubledPayoff
  end
  puts format % 
    [totalProbability, 0, totalPayoff, totalDoubledPayoff, "TOTALS"]
  bestPayoff = [totalDoubledPayoff, totalPayoff].max
  shouldDouble = bestPayoff == totalDoubledPayoff
  if canDouble
    puts "  Player should#{' not' unless shouldDouble} double"
  else
    puts "  Player can't double"
  end
  payoff = bestPayoff * baseProbability
  payoff = [totalDoubledPayoff, totalPayoff].max * baseProbability
  puts "  Payoff = #{payoff} (#{baseProbability} * #{bestPayoff})"
  payoff
end

initialHands = hands.sort do |a, b| b[1] <=> a[1] end
totalPayoff = 0
for name, probability in initialHands
  puts "%s: probability = %0.5f" % [name, probability]
  keepers = Keepers[name]
  puts "  After rolling #{5 - keepers.size}:"
  newHands = hands(Keepers[name])
  totalPayoff += showHands(probability, newHands, Winnings[name].nil?)
end
puts "Total Payoff = #{totalPayoff}"

