games_dice 0.3.12 → 0.4.0

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.
@@ -1,244 +0,0 @@
1
- require 'games_dice/prob_helpers'
2
-
3
- # This class models probability distributions for dice systems.
4
- #
5
- # An object of this class represents a single distribution, which might be the result of a complex
6
- # combination of dice.
7
- #
8
- # @example Distribution for a six-sided die
9
- # probs = GamesDice::Probabilities.for_fair_die( 6 )
10
- # probs.min # => 1
11
- # probs.max # => 6
12
- # probs.expected # => 3.5
13
- # probs.p_ge( 4 ) # => 0.5
14
- #
15
- # @example Adding two distributions
16
- # pd6 = GamesDice::Probabilities.for_fair_die( 6 )
17
- # probs = GamesDice::Probabilities.add_distributions( pd6, pd6 )
18
- # probs.min # => 2
19
- # probs.max # => 12
20
- # probs.expected # => 7.0
21
- # probs.p_ge( 10 ) # => 0.16666666666666669
22
- #
23
- class GamesDice::Probabilities
24
- include GamesDice::ProbabilityValidations
25
- include GamesDice::ProbabilityCalcSums
26
- extend GamesDice::ProbabilityCalcAddDistributions
27
-
28
- # Creates new instance of GamesDice::Probabilities.
29
- # @param [Array<Float>] probs Each entry in the array is the probability of getting a result
30
- # @param [Integer] offset The result associated with index of 0 in the array
31
- # @return [GamesDice::Probabilities]
32
- def initialize( probs = [1.0], offset = 0 )
33
- # This should *probably* be validated in future, but that would impact performance
34
- @probs = check_probs_array probs.clone
35
- @offset = Integer(offset)
36
- end
37
-
38
- # Iterates through value, probability pairs
39
- # @yieldparam [Integer] result A result that may be possible in the dice scheme
40
- # @yieldparam [Float] probability Probability of result, in range 0.0..1.0
41
- # @return [GamesDice::Probabilities] this object
42
- def each
43
- @probs.each_with_index { |p,i| yield( i+@offset, p ) if p > 0.0 }
44
- return self
45
- end
46
-
47
- # A hash representation of the distribution. Each key is an integer result,
48
- # and the matching value is probability of getting that result. A new hash is generated on each
49
- # call to this method.
50
- # @return [Hash]
51
- def to_h
52
- GamesDice::Probabilities.prob_ao_to_h( @probs, @offset )
53
- end
54
-
55
- # @!attribute [r] min
56
- # Minimum result in the distribution
57
- # @return [Integer]
58
- def min
59
- @offset
60
- end
61
-
62
- # @!attribute [r] max
63
- # Maximum result in the distribution
64
- # @return [Integer]
65
- def max
66
- @offset + @probs.count() - 1
67
- end
68
-
69
- # @!attribute [r] expected
70
- # Expected value of distribution.
71
- # @return [Float]
72
- def expected
73
- @expected ||= calc_expected
74
- end
75
-
76
- # Probability of result equalling specific target
77
- # @param [Integer] target
78
- # @return [Float] in range (0.0..1.0)
79
- def p_eql target
80
- i = Integer(target) - @offset
81
- return 0.0 if i < 0 || i >= @probs.count
82
- @probs[ i ]
83
- end
84
-
85
- # Probability of result being greater than specific target
86
- # @param [Integer] target
87
- # @return [Float] in range (0.0..1.0)
88
- def p_gt target
89
- p_ge( Integer(target) + 1 )
90
- end
91
-
92
- # Probability of result being equal to or greater than specific target
93
- # @param [Integer] target
94
- # @return [Float] in range (0.0..1.0)
95
- def p_ge target
96
- target = Integer(target)
97
- return @prob_ge[target] if @prob_ge && @prob_ge[target]
98
- @prob_ge = {} unless @prob_ge
99
-
100
- return 1.0 if target <= min
101
- return 0.0 if target > max
102
- @prob_ge[target] = @probs[target-@offset,@probs.count-1].inject(0.0) {|so_far,p| so_far + p }
103
- end
104
-
105
- # Probability of result being equal to or less than specific target
106
- # @param [Integer] target
107
- # @return [Float] in range (0.0..1.0)
108
- def p_le target
109
- target = Integer(target)
110
- return @prob_le[target] if @prob_le && @prob_le[target]
111
- @prob_le = {} unless @prob_le
112
-
113
- return 1.0 if target >= max
114
- return 0.0 if target < min
115
- @prob_le[target] = @probs[0,1+target-@offset].inject(0.0) {|so_far,p| so_far + p }
116
- end
117
-
118
- # Probability of result being less than specific target
119
- # @param [Integer] target
120
- # @return [Float] in range (0.0..1.0)
121
- def p_lt target
122
- p_le( Integer(target) - 1 )
123
- end
124
-
125
- # Probability distribution derived from this one, where we know (or are only interested in
126
- # situations where) the result is greater than or equal to target.
127
- # @param [Integer] target
128
- # @return [GamesDice::Probabilities] new distribution.
129
- def given_ge target
130
- target = Integer(target)
131
- target = min if min > target
132
- p = p_ge(target)
133
- raise "There is no valid distribution given a result >= #{target}" unless p > 0.0
134
- mult = 1.0/p
135
- new_probs = @probs[target-@offset,@probs.count-1].map { |x| x * mult }
136
- GamesDice::Probabilities.new( new_probs, target )
137
- end
138
-
139
- # Probability distribution derived from this one, where we know (or are only interested in
140
- # situations where) the result is less than or equal to target.
141
- # @param [Integer] target
142
- # @return [GamesDice::Probabilities] new distribution.
143
- def given_le target
144
- target = Integer(target)
145
- target = max if max < target
146
- p = p_le(target)
147
- raise "There is no valid distribution given a result <= #{target}" unless p > 0.0
148
- mult = 1.0/p
149
- new_probs = @probs[0..target-@offset].map { |x| x * mult }
150
- GamesDice::Probabilities.new( new_probs, @offset )
151
- end
152
-
153
- # Creates new instance of GamesDice::Probabilities.
154
- # @param [Hash] prob_hash A hash representation of the distribution, each key is an integer result,
155
- # and the matching value is probability of getting that result
156
- # @return [GamesDice::Probabilities]
157
- def self.from_h prob_hash
158
- raise TypeError, "from_h expected a Hash" unless prob_hash.is_a? Hash
159
- probs, offset = prob_h_to_ao( prob_hash )
160
- GamesDice::Probabilities.new( probs, offset )
161
- end
162
-
163
- # Distribution for a die with equal chance of rolling 1..N
164
- # @param [Integer] sides Number of sides on die
165
- # @return [GamesDice::Probabilities]
166
- def self.for_fair_die sides
167
- sides = Integer(sides)
168
- raise ArgumentError, "sides must be at least 1" unless sides > 0
169
- raise ArgumentError, "sides can be at most 100000" if sides > 100000
170
- GamesDice::Probabilities.new( Array.new( sides, 1.0/sides ), 1 )
171
- end
172
-
173
- # Combines two distributions to create a third, that represents the distribution created when adding
174
- # results together.
175
- # @param [GamesDice::Probabilities] pd_a First distribution
176
- # @param [GamesDice::Probabilities] pd_b Second distribution
177
- # @return [GamesDice::Probabilities]
178
- def self.add_distributions pd_a, pd_b
179
- check_is_gdp( pd_a, pd_b )
180
- combined_min = pd_a.min + pd_b.min
181
- combined_max = pd_a.max + pd_b.max
182
-
183
- add_distributions_internal( combined_min, combined_max, 1, pd_a, 1, pd_b )
184
- end
185
-
186
- # Combines two distributions with multipliers to create a third, that represents the distribution
187
- # created when adding weighted results together.
188
- # @param [Integer] m_a Weighting for first distribution
189
- # @param [GamesDice::Probabilities] pd_a First distribution
190
- # @param [Integer] m_b Weighting for second distribution
191
- # @param [GamesDice::Probabilities] pd_b Second distribution
192
- # @return [GamesDice::Probabilities]
193
- def self.add_distributions_mult m_a, pd_a, m_b, pd_b
194
- check_is_gdp( pd_a, pd_b )
195
- m_a = Integer(m_a)
196
- m_b = Integer(m_b)
197
-
198
- combined_min, combined_max = calc_combined_extremes( m_a, pd_a, m_b, pd_b ).minmax
199
-
200
- add_distributions_internal( combined_min, combined_max, m_a, pd_a, m_b, pd_b )
201
- end
202
-
203
- # Returns a symbol for the language name that this class is implemented in. The C version of the
204
- # code is noticeably faster when dealing with larger numbers of possible results.
205
- # @return [Symbol] Either :c or :ruby
206
- def self.implemented_in
207
- :ruby
208
- end
209
-
210
- # Adds a distribution to itself repeatedly, to simulate a number of dice
211
- # results being summed.
212
- # @param [Integer] n Number of repetitions, must be at least 1
213
- # @return [GamesDice::Probabilities] new distribution
214
- def repeat_sum n
215
- n = Integer( n )
216
- raise "Cannot combine probabilities less than once" if n < 1
217
- raise "Probability distribution too large" if ( n * @probs.count ) > 1000000
218
- repeat_sum_internal( n )
219
- end
220
-
221
- # Calculates distribution generated by summing best k results of n iterations
222
- # of the distribution.
223
- # @param [Integer] n Number of repetitions, must be at least 1
224
- # @param [Integer] k Number of best results to keep and sum
225
- # @return [GamesDice::Probabilities] new distribution
226
- def repeat_n_sum_k n, k, kmode = :keep_best
227
- n = Integer( n )
228
- k = Integer( k )
229
- raise "Cannot combine probabilities less than once" if n < 1
230
- # Technically this is a limitation of C code, but Ruby version is most likely slow and inaccurate beyond 170
231
- raise "Too many dice to calculate numbers of arrangements" if n > 170
232
- check_keep_mode( kmode )
233
- repeat_n_sum_k_internal( n, k, kmode )
234
- end
235
-
236
- private
237
-
238
- def calc_expected
239
- total = 0.0
240
- @probs.each_with_index { |v,i| total += (i+@offset)*v }
241
- total
242
- end
243
-
244
- end # class GamesDice::Probabilities