class CwCardUtils::SynergyProbability
Calculates probability of drawing specific card combinations for synergy analysis
def approx_combo(target_names, draws)
def approx_combo(target_names, draws) prob_missing = 0.0 target_names.each do |name| copies = count_copies([name]) miss_prob = hypergeometric(@deck_size - copies, draws).to_f / hypergeometric(@deck_size, draws) prob_missing += miss_prob end [1 - prob_missing, 0].max end
def count_copies(names)
def count_copies(names) @deck.main.sum { |card| names.include?(card.name) ? card.count : 0 } end
def factorial(n)
def factorial(n) return 1 if n.zero? (1..n).reduce(1, :*) end
def hypergeometric(n, k)
def hypergeometric(n, k) return 0 if k > n factorial(n) / (factorial(k) * factorial(n - k)) end
def initialize(deck, deck_size: 60)
def initialize(deck, deck_size: 60) @deck = deck @deck_size = deck_size end
def prob_combo(target_names, draws)
def prob_combo(target_names, draws) case target_names.size when 1 prob_single(target_names, draws) when 2 prob_two_card_combo(target_names, draws) when 3 prob_three_card_combo(target_names, draws) else # For >3 cards, fallback to approximation approx_combo(target_names, draws) end end
def prob_single(target_names, draws)
def prob_single(target_names, draws) total_copies = count_copies(target_names) 1 - hypergeometric(@deck_size - total_copies, draws).to_f / hypergeometric(@deck_size, draws) end
def prob_three_card_combo(names, draws)
def prob_three_card_combo(names, draws) copies_a = count_copies([names[0]]) copies_b = count_copies([names[1]]) copies_c = count_copies([names[2]]) total = hypergeometric(@deck_size, draws).to_f miss_a = hypergeometric(@deck_size - copies_a, draws) / total miss_b = hypergeometric(@deck_size - copies_b, draws) / total miss_c = hypergeometric(@deck_size - copies_c, draws) / total miss_ab = hypergeometric(@deck_size - (copies_a + copies_b), draws) / total miss_ac = hypergeometric(@deck_size - (copies_a + copies_c), draws) / total miss_bc = hypergeometric(@deck_size - (copies_b + copies_c), draws) / total miss_abc = hypergeometric(@deck_size - (copies_a + copies_b + copies_c), draws) / total # Inclusion–exclusion for 3 sets [1 - (miss_a + miss_b + miss_c) + (miss_ab + miss_ac + miss_bc) - miss_abc, 0].max end
def prob_two_card_combo(names, draws)
def prob_two_card_combo(names, draws) copies_a = count_copies([names[0]]) copies_b = count_copies([names[1]]) total = hypergeometric(@deck_size, draws).to_f # Probability missing A miss_a = hypergeometric(@deck_size - copies_a, draws) / total # Probability missing B miss_b = hypergeometric(@deck_size - copies_b, draws) / total # Probability missing both miss_both = hypergeometric(@deck_size - (copies_a + copies_b), draws) / total # Inclusion–exclusion [1 - (miss_a + miss_b - miss_both), 0].max end