cw_card_utils 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0aab3de52c635cd3e24f8ba808777bfc5e60cb07b0e2f02357c67f00212dcfc9
4
- data.tar.gz: 28d0d647d3c6f91a69b6853abcd73605e9f116a8251afff8c5f39a7cf77b22fe
3
+ metadata.gz: 23145f32617794cf1af83523bf179d6d9260cc75251a24207ae6ea6ce0274599
4
+ data.tar.gz: 7cd65b1d7e93b8f9c2438df235d77c44e294d543ff1f22e8b0aa16efb5e51192
5
5
  SHA512:
6
- metadata.gz: 42be3567474156c9c6417d0c7994485567608af868a3f8dadd832b964c99216397bda74d89e83210c3e170ba1b5d4dddef19c401efc70eb7380dfe8068753c7b
7
- data.tar.gz: 4218005f3d49d44613e738b2f718f5f1997a314ec81918d865f9bec17b714b6c811f9dfcc2fd6a79ced057175fa8bfc69e15e8f3d0b981b4b7c00d52cb3014af
6
+ metadata.gz: 556bc7d3c4dc4d87b752d2923eba061b23356a6e2330f33691e34ee215d7cdd9e6148cd8c4a9082ddbcda179ffd68bef162d0bc6e11c25db3bc05b95589f83d9
7
+ data.tar.gz: e81abacff1c5dae3476a1ffb9e6a779105546f7a020edcc36fb463082842645a784e922d89660c2e779c4ceaa1d51dadf14b84ed4ae33d6326fe7188ab1135b7
@@ -10,22 +10,27 @@ module CwCardUtils
10
10
 
11
11
  # Probability of drawing at least one of the target cards
12
12
  def prob_single(target_names, draws)
13
- total_copies = count_copies(target_names)
14
- 1 - hypergeometric(@deck_size - total_copies, draws).to_f / hypergeometric(@deck_size, draws)
13
+ targets = Array(target_names).uniq
14
+ draws_clamped = clamp_draws(draws)
15
+ total_copies = count_copies(targets)
16
+ total = hypergeometric(@deck_size, draws_clamped).to_f
17
+ prob = 1 - hypergeometric(@deck_size - total_copies, draws_clamped).to_f / total
18
+ prob.clamp(0.0, 1.0)
15
19
  end
16
20
 
17
21
  # Probability of drawing ALL cards in the targets list (synergy pair/trio)
18
22
  def prob_combo(target_names, draws)
19
- case target_names.size
23
+ targets = Array(target_names).uniq
24
+ case targets.size
20
25
  when 1
21
- prob_single(target_names, draws)
26
+ prob_single(targets, draws)
22
27
  when 2
23
- prob_two_card_combo(target_names, draws)
28
+ prob_two_card_combo(targets, draws)
24
29
  when 3
25
- prob_three_card_combo(target_names, draws)
30
+ prob_three_card_combo(targets, draws)
26
31
  else
27
32
  # For >3 cards, fallback to approximation
28
- approx_combo(target_names, draws)
33
+ approx_combo(targets, draws)
29
34
  end
30
35
  end
31
36
 
@@ -33,60 +38,70 @@ module CwCardUtils
33
38
 
34
39
  # Exact for 2-card combos
35
40
  def prob_two_card_combo(names, draws)
36
- copies_a = count_copies([names[0]])
37
- copies_b = count_copies([names[1]])
41
+ draws_clamped = clamp_draws(draws)
38
42
 
39
- total = hypergeometric(@deck_size, draws).to_f
43
+ copies_a = copies_by_name[names[0]]
44
+ copies_b = copies_by_name[names[1]]
45
+
46
+ total = hypergeometric(@deck_size, draws_clamped).to_f
40
47
 
41
48
  # Probability missing A
42
- miss_a = hypergeometric(@deck_size - copies_a, draws) / total
49
+ miss_a = hypergeometric(@deck_size - copies_a, draws_clamped) / total
43
50
  # Probability missing B
44
- miss_b = hypergeometric(@deck_size - copies_b, draws) / total
51
+ miss_b = hypergeometric(@deck_size - copies_b, draws_clamped) / total
45
52
  # Probability missing both
46
- miss_both = hypergeometric(@deck_size - (copies_a + copies_b), draws) / total
53
+ miss_both = hypergeometric(@deck_size - (copies_a + copies_b), draws_clamped) / total
47
54
 
48
55
  # Inclusion–exclusion
49
- [1 - (miss_a + miss_b - miss_both), 0].max
56
+ prob = 1 - (miss_a + miss_b - miss_both)
57
+ prob.clamp(0.0, 1.0)
50
58
  end
51
59
 
52
60
  # Exact for 3-card combos
53
61
  def prob_three_card_combo(names, draws)
54
- copies_a = count_copies([names[0]])
55
- copies_b = count_copies([names[1]])
56
- copies_c = count_copies([names[2]])
62
+ draws_clamped = clamp_draws(draws)
63
+
64
+ copies_a = copies_by_name[names[0]]
65
+ copies_b = copies_by_name[names[1]]
66
+ copies_c = copies_by_name[names[2]]
57
67
 
58
- total = hypergeometric(@deck_size, draws).to_f
68
+ total = hypergeometric(@deck_size, draws_clamped).to_f
59
69
 
60
- miss_a = hypergeometric(@deck_size - copies_a, draws) / total
61
- miss_b = hypergeometric(@deck_size - copies_b, draws) / total
62
- miss_c = hypergeometric(@deck_size - copies_c, draws) / total
70
+ miss_a = hypergeometric(@deck_size - copies_a, draws_clamped) / total
71
+ miss_b = hypergeometric(@deck_size - copies_b, draws_clamped) / total
72
+ miss_c = hypergeometric(@deck_size - copies_c, draws_clamped) / total
63
73
 
64
- miss_ab = hypergeometric(@deck_size - (copies_a + copies_b), draws) / total
65
- miss_ac = hypergeometric(@deck_size - (copies_a + copies_c), draws) / total
66
- miss_bc = hypergeometric(@deck_size - (copies_b + copies_c), draws) / total
74
+ miss_ab = hypergeometric(@deck_size - (copies_a + copies_b), draws_clamped) / total
75
+ miss_ac = hypergeometric(@deck_size - (copies_a + copies_c), draws_clamped) / total
76
+ miss_bc = hypergeometric(@deck_size - (copies_b + copies_c), draws_clamped) / total
67
77
 
68
- miss_abc = hypergeometric(@deck_size - (copies_a + copies_b + copies_c), draws) / total
78
+ miss_abc = hypergeometric(@deck_size - (copies_a + copies_b + copies_c), draws_clamped) / total
69
79
 
70
80
  # Inclusion–exclusion for 3 sets
71
- [1 - (miss_a + miss_b + miss_c) +
81
+ prob = 1 - (miss_a + miss_b + miss_c) +
72
82
  (miss_ab + miss_ac + miss_bc) -
73
- miss_abc, 0].max
83
+ miss_abc
84
+ prob.clamp(0.0, 1.0)
74
85
  end
75
86
 
76
- # Approximation for >3 cards (same as old method)
87
+ # Approximation for >3 cards
77
88
  def approx_combo(target_names, draws)
78
- prob_missing = 0.0
79
- target_names.each do |name|
80
- copies = count_copies([name])
81
- miss_prob = hypergeometric(@deck_size - copies, draws).to_f / hypergeometric(@deck_size, draws)
82
- prob_missing += miss_prob
89
+ draws_clamped = clamp_draws(draws)
90
+ total = hypergeometric(@deck_size, draws_clamped).to_f
91
+
92
+ prob_missing = target_names.sum do |name|
93
+ copies = copies_by_name[name]
94
+ hypergeometric(@deck_size - copies, draws_clamped).to_f / total
83
95
  end
84
- [1 - prob_missing, 0].max
96
+
97
+ prob = 1 - prob_missing
98
+ prob.clamp(0.0, 1.0)
85
99
  end
86
100
 
87
101
  # Utility: count how many copies of given cards are in the deck
88
102
  def count_copies(names)
89
- @deck.main.sum { |card| names.include?(card.name) ? card.count : 0 }
103
+ unique = names.uniq
104
+ @deck.main.sum { |card| unique.include?(card.name) ? card.count : 0 }
90
105
  end
91
106
 
92
107
  # Hypergeometric combination helper
@@ -95,9 +110,19 @@ module CwCardUtils
95
110
  factorial(n) / (factorial(k) * factorial(n - k))
96
111
  end
97
112
 
113
+ def clamp_draws(draws)
114
+ return 0 if draws.to_i < 0
115
+ [draws.to_i, @deck_size].min
116
+ end
117
+
98
118
  def factorial(n)
99
119
  return 1 if n.zero?
100
120
  (1..n).reduce(1, :*)
101
121
  end
122
+
123
+ # Only need to calculate this once for the deck passed in.
124
+ def copies_by_name
125
+ @copies_by_name ||= @deck.main.each_with_object(Hash.new(0)) { |card, h| h[card.name] += card.count }
126
+ end
102
127
  end
103
128
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CwCardUtils
4
- VERSION = "0.1.10"
4
+ VERSION = "0.1.11"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cw_card_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Stenhouse