module Math
  class << self
    def fac n
      return 1 if n < 2
      (1..n).inject { |prod, i| prod * i }
    end
    
    def choose n, k
      fac(n) / (fac(n-k) * fac(k))
    end
  end
end

class Dice
  module Random
    def self.hit?
      rand(6) + 1 >= 5
    end
    
    def self.roll n
      hits = 0
      n.times { hits += 1 if hit? }
      hits
    end
  end
  
  CHANCE_PER_SIDE = 1.0 / 6
  CHANCE_TO_HIT   = 2 * CHANCE_PER_SIDE
  CHANCE_TO_MISS  = 1 - CHANCE_TO_HIT
  
  def self.hits_expected n
    n * CHANCE_TO_HIT
  end
  
  def self.chance_for_hits n, hits
    misses = n - hits
    (CHANCE_TO_MISS ** misses) * (CHANCE_TO_HIT ** hits) * Math.choose(n, hits)
  end
  
  def self.chance_to_win n, threshold = 1
    (threshold..n).inject(0) do |sum, i|
      sum + chance_for_hits(n, i)
    end
  end
  
  CHANCE_FOR_ONE = 1 * CHANCE_PER_SIDE
  def self.chance_for_ones n, ones
    not_ones = n - ones
    ((1 - CHANCE_FOR_ONE) ** not_ones) * (CHANCE_FOR_ONE ** ones) * Math.choose(n, ones)
  end
  
  CHANCE_TO_GLITCH_ON_MISS = 1.0 / 4
  def self.chance_for_ones_when_miss n, ones
    not_ones = n - ones
    ((1 - CHANCE_TO_GLITCH_ON_MISS) ** not_ones) * (CHANCE_TO_GLITCH_ON_MISS ** ones) * Math.choose(n, ones)
  end
  
  def self.chance_to_glitch n, critical = false
    min_ones = (n / 2.0).ceil
    
    if critical
      chance_for_hits(n, 0) *
        (min_ones..n).map do |ones|
          chance_for_ones_when_miss(n, ones)
        end.reduce(:+)
    else
      (min_ones..n).map do |ones|
        chance_for_ones(n, ones)
      end.reduce(:+)
    end
  end
  
  def self.chance_to_glitch_sr5 n, critical = false
    min_ones = (n / 2.0).floor + 1
    
    if critical
      chance_for_hits(n, 0) *
        (min_ones..n).map do |ones|
          chance_for_ones_when_miss(n, ones)
        end.reduce(:+)
    else
      (min_ones..n).map do |ones|
        chance_for_ones(n, ones)
      end.reduce(:+)
    end
  end
end

for i in 1..20
  p 100 * Dice.chance_to_glitch_sr5(i, true)
end
