games_dice 0.3.9 → 0.3.10
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.
- data/CHANGELOG.md +18 -13
- data/games_dice.gemspec +4 -4
- data/lib/games_dice/complex_die.rb +45 -78
- data/lib/games_dice/complex_die_helpers.rb +60 -0
- data/lib/games_dice/prob_helpers.rb +259 -0
- data/lib/games_dice/probabilities.rb +12 -213
- data/lib/games_dice/version.rb +1 -1
- data/spec/bunch_spec.rb +0 -1
- data/spec/complex_die_spec.rb +0 -1
- data/spec/dice_spec.rb +1 -1
- data/spec/die_result_spec.rb +1 -1
- data/spec/die_spec.rb +0 -1
- data/spec/helpers.rb +3 -1
- data/spec/map_rule_spec.rb +1 -1
- data/spec/parser_spec.rb +1 -1
- data/spec/probability_spec.rb +0 -1
- data/spec/readme_spec.rb +0 -1
- data/spec/reroll_rule_spec.rb +1 -1
- metadata +8 -6
data/CHANGELOG.md
CHANGED
@@ -1,59 +1,64 @@
|
|
1
1
|
# GamesDice Changelog
|
2
2
|
|
3
|
-
## 0.3.
|
3
|
+
## 0.3.10 ( 29 July 2013 )
|
4
|
+
|
5
|
+
* Non-functional changes to improve code quality metrics on CodeClimate
|
6
|
+
* Altered specs to improve accuracy of coverage metrics on Coveralls
|
7
|
+
|
8
|
+
## 0.3.9 ( 23 July 2013 )
|
4
9
|
|
5
10
|
* New methods for inspecting and iterating over potential values in GamesDice::Die
|
6
11
|
* Code metric integration and badges for github
|
7
12
|
* Non-functional changes to improve code quality metrics on CodeClimate
|
8
13
|
|
9
|
-
## 0.3.7
|
14
|
+
## 0.3.7 ( 17 July 2013 )
|
10
15
|
|
11
16
|
* Compatibility between pure Ruby and native extension code when handling bad method params
|
12
17
|
* Added this changelog to documentation
|
13
18
|
|
14
|
-
## 0.3.6
|
19
|
+
## 0.3.6 ( 15 July 2013 )
|
15
20
|
|
16
21
|
* Extension building skipped, with fallback to pure Ruby, for JRuby compatibility
|
17
22
|
|
18
|
-
## 0.3.5
|
23
|
+
## 0.3.5 ( 14 July 2013 )
|
19
24
|
|
20
25
|
* Adjust C code to avoid warnings about C90 compatibility (warnings seen on Travis)
|
21
26
|
* Note MIT license in gemspec
|
22
27
|
* Add class method GamesDice::Probabilities.implemented_in
|
23
28
|
|
24
|
-
## 0.3.3
|
29
|
+
## 0.3.3 ( 11 July 2013 )
|
25
30
|
|
26
31
|
* Standardised code for Ruby 1.8.7 compatibility in GamesDice::Probabilities
|
27
32
|
* Bug fix for probability calculations where distributions are added with mulipliers e.g. '2d6 - 1d8'
|
28
33
|
|
29
|
-
## 0.3.2
|
34
|
+
## 0.3.2 ( 10 July 2013 )
|
30
35
|
|
31
36
|
* Bug fix for Ruby 1.8.7 compatibility in GamesDice::Probabilities
|
32
37
|
|
33
|
-
## 0.3.1
|
38
|
+
## 0.3.1 ( 10 July 2013 )
|
34
39
|
|
35
40
|
* Bug fix for Ruby 1.8.7 compatibility in GamesDice::Probabilities
|
36
41
|
|
37
|
-
## 0.3.0
|
42
|
+
## 0.3.0 ( 10 July 2013 )
|
38
43
|
|
39
44
|
* Implemented GamesDice::Probabilities as native extension
|
40
45
|
|
41
|
-
## 0.2.4
|
46
|
+
## 0.2.4 ( 18 June 2013 )
|
42
47
|
|
43
48
|
* Minor speed improvements to GamesDice::Probabilities
|
44
49
|
|
45
|
-
## 0.2.3
|
50
|
+
## 0.2.3 ( 12 June 2013 )
|
46
51
|
|
47
52
|
* More YARD documentation
|
48
53
|
|
49
|
-
## 0.2.2
|
54
|
+
## 0.2.2 ( 10 June 2013 )
|
50
55
|
|
51
56
|
* Extended YARD documentation
|
52
57
|
|
53
|
-
## 0.2.1
|
58
|
+
## 0.2.1 ( 5 June 2013 )
|
54
59
|
|
55
60
|
* Started basic YARD documentation
|
56
61
|
|
57
|
-
## 0.2.0
|
62
|
+
## 0.2.0 ( 30 May 2013 )
|
58
63
|
|
59
64
|
* First version with a complete feature set
|
data/games_dice.gemspec
CHANGED
@@ -19,16 +19,16 @@ Gem::Specification.new do |gem|
|
|
19
19
|
gem.add_development_dependency "yard", ">= 0.8.6"
|
20
20
|
gem.add_development_dependency "coveralls", ">= 0.6.7"
|
21
21
|
gem.add_development_dependency "json", ">= 1.7.7"
|
22
|
-
gem.add_development_dependency "rake-compiler"
|
22
|
+
gem.add_development_dependency "rake-compiler", ">= 0.8.3"
|
23
23
|
|
24
24
|
# Red Carpet renders README.md, and is optional even when developing the gem.
|
25
25
|
# However, it has a C extension, and v3.0.0 is does not compile for 1.8.7. This only affects the gem build process, so
|
26
26
|
# is only really used in environments like Travis, and is safe to wrap like this in the gemspec.
|
27
27
|
if RUBY_DESCRIPTION !~ /jruby/
|
28
|
-
if RUBY_VERSION
|
29
|
-
gem.add_development_dependency "redcarpet", ">=2.3.0", "<3.0.0"
|
30
|
-
else
|
28
|
+
if RUBY_VERSION >= "1.9.0"
|
31
29
|
gem.add_development_dependency "redcarpet", ">=2.3.0"
|
30
|
+
else
|
31
|
+
gem.add_development_dependency "redcarpet", ">=2.3.0", "<3.0.0"
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "games_dice/complex_die_helpers"
|
2
|
+
|
1
3
|
# This class models a die that is built up from a simpler unit by adding rules to re-roll
|
2
4
|
# and interpret the value shown.
|
3
5
|
#
|
@@ -20,6 +22,7 @@
|
|
20
22
|
#
|
21
23
|
|
22
24
|
class GamesDice::ComplexDie
|
25
|
+
include GamesDice::ComplexDieHelpers
|
23
26
|
|
24
27
|
# @!visibility private
|
25
28
|
# arbitrary limit to speed up probability calculations. It should
|
@@ -104,18 +107,14 @@ class GamesDice::ComplexDie
|
|
104
107
|
reroll_probs = recursive_probabilities
|
105
108
|
prob_hash = {}
|
106
109
|
reroll_probs.each do |v,p|
|
107
|
-
|
108
|
-
prob_hash[m] ||= 0.0
|
109
|
-
prob_hash[m] += p
|
110
|
+
add_mapped_to_prob_hash( prob_hash, v, p )
|
110
111
|
end
|
111
112
|
elsif @rerolls
|
112
113
|
prob_hash = recursive_probabilities
|
113
114
|
elsif @maps
|
114
115
|
prob_hash = {}
|
115
116
|
@basic_die.probabilities.each do |v,p|
|
116
|
-
|
117
|
-
prob_hash[m] ||= 0.0
|
118
|
-
prob_hash[m] += p
|
117
|
+
add_mapped_to_prob_hash( prob_hash, v, p )
|
119
118
|
end
|
120
119
|
else
|
121
120
|
@probabilities = @basic_die.probabilities
|
@@ -136,29 +135,42 @@ class GamesDice::ComplexDie
|
|
136
135
|
|
137
136
|
private
|
138
137
|
|
138
|
+
def add_mapped_to_prob_hash prob_hash, v, p
|
139
|
+
m, n = calc_maps(v)
|
140
|
+
prob_hash[m] ||= 0.0
|
141
|
+
prob_hash[m] += p
|
142
|
+
end
|
143
|
+
|
139
144
|
def roll_apply_rerolls
|
140
145
|
return unless @rerolls
|
141
146
|
subtracting = false
|
142
147
|
rerolls_remaining = @rerolls.map { |rule| rule.limit }
|
143
148
|
|
144
149
|
loop do
|
145
|
-
|
146
|
-
rule_idx = @rerolls.zip(rerolls_remaining).find_index do |rule,remaining|
|
147
|
-
next if rule.type == :reroll_subtract && @result.rolls.length > 1
|
148
|
-
remaining > 0 && rule.applies?( @basic_die.result )
|
149
|
-
end
|
150
|
+
rule_idx = find_matching_reroll_rule( @basic_die.result, @result.rolls.length ,rerolls_remaining )
|
150
151
|
break unless rule_idx
|
151
152
|
|
152
153
|
rule = @rerolls[ rule_idx ]
|
153
154
|
rerolls_remaining[ rule_idx ] -= 1
|
154
155
|
subtracting = true if rule.type == :reroll_subtract
|
156
|
+
roll_apply_reroll_rule rule, subtracting
|
157
|
+
end
|
158
|
+
end
|
155
159
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
160
|
+
def roll_apply_reroll_rule rule, is_subtracting
|
161
|
+
# Apply the rule (note reversal for additions, after a subtract)
|
162
|
+
if is_subtracting && rule.type == :reroll_add
|
163
|
+
@result.add_roll( @basic_die.roll, :reroll_subtract )
|
164
|
+
else
|
165
|
+
@result.add_roll( @basic_die.roll, rule.type )
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Find which rule, if any, is being triggered
|
170
|
+
def find_matching_reroll_rule check_value, num_rolls, rerolls_remaining
|
171
|
+
@rerolls.zip(rerolls_remaining).find_index do |rule,remaining|
|
172
|
+
next if rule.type == :reroll_subtract && num_rolls > 1
|
173
|
+
remaining > 0 && rule.applies?( check_value )
|
162
174
|
end
|
163
175
|
end
|
164
176
|
|
@@ -214,6 +226,8 @@ class GamesDice::ComplexDie
|
|
214
226
|
end
|
215
227
|
|
216
228
|
# This isn't 100% accurate, but does cover most "normal" scenarios, and we're only falling back to it when we have to
|
229
|
+
# The inaccuracy is that min_result..max_result may contain 'holes' which have extreme map values that cannot actually
|
230
|
+
# occur. In practice it is likely a non-issue unless someone went out of their way to invent a dice scheme that broke it.
|
217
231
|
def logical_minmax
|
218
232
|
return [@basic_die.min,@basic_die.max] unless @rerolls || @maps
|
219
233
|
return minmax_mappings( @basic_die.all_values ) unless @rerolls
|
@@ -225,79 +239,32 @@ class GamesDice::ComplexDie
|
|
225
239
|
def logical_rerolls_minmax
|
226
240
|
min_result = @basic_die.min
|
227
241
|
max_result = @basic_die.max
|
228
|
-
min_subtract
|
242
|
+
min_subtract = find_minimum_possible_subtract
|
243
|
+
max_add = find_maximum_possible_adds
|
229
244
|
if min_subtract
|
230
245
|
min_result = [ min_subtract - max_add, min_subtract - max_result ].min
|
231
246
|
end
|
232
247
|
[ min_result, max_add + max_result ]
|
233
248
|
end
|
234
249
|
|
235
|
-
def
|
250
|
+
def find_minimum_possible_subtract
|
236
251
|
min_subtract = nil
|
237
|
-
|
238
|
-
|
239
|
-
min_reroll,max_reroll = @basic_die.all_values.select { |v| rule.applies?( v ) }.minmax
|
252
|
+
@rerolls.select { |r| r.type == :reroll_subtract }.each do |rule|
|
253
|
+
min_reroll = @basic_die.all_values.select { |v| rule.applies?( v ) }.min
|
240
254
|
next unless min_reroll
|
241
|
-
|
242
|
-
min_subtract = min_reroll if min_subtract.nil?
|
243
|
-
min_subtract = min_reroll if min_subtract > min_reroll
|
244
|
-
else
|
245
|
-
total_add += max_reroll * rule.limit
|
246
|
-
end
|
247
|
-
end
|
248
|
-
[ min_subtract, total_add ]
|
249
|
-
end
|
250
|
-
|
251
|
-
def recursive_probabilities probabilities={},prior_probability=1.0,depth=0,prior_result=nil,rerolls_left=nil,roll_reason=:basic,subtracting=false
|
252
|
-
each_probability = prior_probability / @basic_die.sides
|
253
|
-
depth += 1
|
254
|
-
if depth >= 20 || each_probability < 1.0e-12
|
255
|
-
@probabilities_complete = false
|
256
|
-
stop_recursing = true
|
257
|
-
end
|
258
|
-
|
259
|
-
@basic_die.each_value do |v|
|
260
|
-
# calculate value, recurse if there is a reroll
|
261
|
-
result_so_far, rerolls_remaining = calc_result_so_far(prior_result, rerolls_left, v, roll_reason )
|
262
|
-
|
263
|
-
# Find which rule, if any, is being triggered
|
264
|
-
rule_idx = @rerolls.zip(rerolls_remaining).find_index do |rule,remaining|
|
265
|
-
next if rule.type == :reroll_subtract && result_so_far.rolls.length > 1
|
266
|
-
remaining > 0 && rule.applies?( v )
|
267
|
-
end
|
268
|
-
|
269
|
-
if rule_idx && ! stop_recursing
|
270
|
-
rule = @rerolls[ rule_idx ]
|
271
|
-
rerolls_remaining[ rule_idx ] -= 1
|
272
|
-
is_subtracting = true if subtracting || rule.type == :reroll_subtract
|
273
|
-
|
274
|
-
# Apply the rule (note reversal for additions, after a subtract)
|
275
|
-
if subtracting && rule.type == :reroll_add
|
276
|
-
recursive_probabilities probabilities,each_probability,depth,result_so_far,rerolls_remaining,:reroll_subtract,is_subtracting
|
277
|
-
else
|
278
|
-
recursive_probabilities probabilities,each_probability,depth,result_so_far,rerolls_remaining,rule.type,is_subtracting
|
279
|
-
end
|
280
|
-
# just accumulate value on a regular roll
|
281
|
-
else
|
282
|
-
t = result_so_far.total
|
283
|
-
probabilities[ t ] ||= 0.0
|
284
|
-
probabilities[ t ] += each_probability
|
285
|
-
end
|
286
|
-
|
255
|
+
min_subtract = [min_reroll,min_subtract].compact.min
|
287
256
|
end
|
288
|
-
|
257
|
+
min_subtract
|
289
258
|
end
|
290
259
|
|
291
|
-
def
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
result_so_far = GamesDice::DieResult.new(v,roll_reason)
|
298
|
-
rerolls_remaining = @rerolls.map { |rule| rule.limit }
|
260
|
+
def find_maximum_possible_adds
|
261
|
+
total_add = 0
|
262
|
+
@rerolls.select { |r| r.type == :reroll_add }.each do |rule|
|
263
|
+
max_reroll = @basic_die.all_values.select { |v| rule.applies?( v ) }.max
|
264
|
+
next unless max_reroll
|
265
|
+
total_add += max_reroll * rule.limit
|
299
266
|
end
|
300
|
-
|
267
|
+
total_add
|
301
268
|
end
|
302
269
|
|
303
270
|
end # class ComplexDie
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# @!visibility private
|
2
|
+
module GamesDice::ComplexDieHelpers
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def recursive_probabilities probabilities={},prior_probability=1.0,depth=0,prior_result=nil,rerolls_left=nil,roll_reason=:basic,subtracting=false
|
7
|
+
each_probability = prior_probability / @basic_die.sides
|
8
|
+
depth += 1
|
9
|
+
if depth >= 20 || each_probability < 1.0e-16
|
10
|
+
@probabilities_complete = false
|
11
|
+
stop_recursing = true
|
12
|
+
end
|
13
|
+
|
14
|
+
@basic_die.each_value do |v|
|
15
|
+
recurse_probs_for_value( v, roll_reason, probabilities, each_probability, depth, prior_result, rerolls_left, subtracting, stop_recursing )
|
16
|
+
end
|
17
|
+
probabilities
|
18
|
+
end
|
19
|
+
|
20
|
+
def recurse_probs_for_value v, roll_reason, probabilities, each_probability, depth, prior_result, rerolls_left, subtracting, stop_recursing
|
21
|
+
# calculate value, recurse if there is a reroll
|
22
|
+
result_so_far, rerolls_remaining = calc_result_so_far(prior_result, rerolls_left, v, roll_reason )
|
23
|
+
|
24
|
+
# Find which rule, if any, is being triggered
|
25
|
+
rule_idx = find_matching_reroll_rule( v, result_so_far.rolls.length, rerolls_remaining )
|
26
|
+
|
27
|
+
if rule_idx && ! stop_recursing
|
28
|
+
recurse_probs_with_rule( probabilities, each_probability, depth, result_so_far, rerolls_remaining, rule_idx, subtracting )
|
29
|
+
else
|
30
|
+
t = result_so_far.total
|
31
|
+
probabilities[ t ] ||= 0.0
|
32
|
+
probabilities[ t ] += each_probability
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def recurse_probs_with_rule probabilities, each_probability, depth, result_so_far, rerolls_remaining, rule_idx, subtracting
|
37
|
+
rule = @rerolls[ rule_idx ]
|
38
|
+
rerolls_remaining[ rule_idx ] -= 1
|
39
|
+
is_subtracting = true if subtracting || rule.type == :reroll_subtract
|
40
|
+
|
41
|
+
# Apply the rule (note reversal for additions, after a subtract)
|
42
|
+
if subtracting && rule.type == :reroll_add
|
43
|
+
recursive_probabilities probabilities, each_probability, depth, result_so_far, rerolls_remaining, :reroll_subtract, is_subtracting
|
44
|
+
else
|
45
|
+
recursive_probabilities probabilities, each_probability, depth, result_so_far, rerolls_remaining, rule.type, is_subtracting
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def calc_result_so_far prior_result, rerolls_left, v, roll_reason
|
50
|
+
if prior_result
|
51
|
+
result_so_far = prior_result.clone
|
52
|
+
result_so_far.add_roll(v,roll_reason)
|
53
|
+
rerolls_remaining = rerolls_left.clone
|
54
|
+
else
|
55
|
+
result_so_far = GamesDice::DieResult.new(v,roll_reason)
|
56
|
+
rerolls_remaining = @rerolls.map { |rule| rule.limit }
|
57
|
+
end
|
58
|
+
[result_so_far, rerolls_remaining]
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
# @!visibility private
|
2
|
+
module GamesDice::ProbabilityValidations
|
3
|
+
|
4
|
+
# @!visibility private
|
5
|
+
# the Array, Offset representation of probabilities.
|
6
|
+
def to_ao
|
7
|
+
[ @probs, @offset ]
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.included(klass)
|
11
|
+
klass.extend ClassMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def check_probs_array probs_array
|
17
|
+
raise TypeError unless probs_array.is_a?( Array )
|
18
|
+
probs_array.map!{ |n| Float(n) }
|
19
|
+
total = probs_array.inject(0.0) do |t,x|
|
20
|
+
if x < 0.0 || x > 1.0
|
21
|
+
raise ArgumentError, "Found probability value #{x} which is not in range 0.0..1.0"
|
22
|
+
end
|
23
|
+
t+x
|
24
|
+
end
|
25
|
+
if (total-1.0).abs > 1e-6
|
26
|
+
raise ArgumentError, "Total probabilities too far from 1.0 for a valid distribution"
|
27
|
+
end
|
28
|
+
probs_array
|
29
|
+
end
|
30
|
+
|
31
|
+
def check_keep_mode kmode
|
32
|
+
raise "Keep mode #{kmode.inspect} not recognised" unless [:keep_best,:keep_worst].member?( kmode )
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
# Convert hash to array,offset notation
|
37
|
+
def prob_h_to_ao h
|
38
|
+
rmin,rmax = h.keys.minmax
|
39
|
+
o = rmin
|
40
|
+
s = 1 + rmax - rmin
|
41
|
+
raise ArgumentError, "Range of possible results too large" if s > 1000000
|
42
|
+
a = Array.new( s, 0.0 )
|
43
|
+
h.each { |k,v| a[k-rmin] = Float(v) }
|
44
|
+
[a,o]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Convert array,offset notation to hash
|
48
|
+
def prob_ao_to_h a, o
|
49
|
+
h = Hash.new
|
50
|
+
a.each_with_index { |v,i| h[i+o] = v if v > 0.0 }
|
51
|
+
h
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def check_is_gdp *probs
|
57
|
+
probs.each do |prob|
|
58
|
+
unless prob.is_a?( GamesDice::Probabilities )
|
59
|
+
raise TypeError, "parameter is not a GamesDice::Probabilities"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# @!visibility private
|
67
|
+
# This module is a set of related private methods for GamesDice::Probabilities that
|
68
|
+
# calculate how two distributions can be combined.
|
69
|
+
module GamesDice::ProbabilityCalcAddDistributions
|
70
|
+
private
|
71
|
+
|
72
|
+
def calc_combined_extremes m_a, pd_a, m_b, pd_b
|
73
|
+
[ [ :min, :min ], [ :min, :max ], [ :max, :min ], [ :max, :max ] ].map do |pda_meth, pdb_meth|
|
74
|
+
m_a * pd_a.send(pda_meth) + m_b * pd_b.send(pdb_meth)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_distributions_internal combined_min, combined_max, m_a, pd_a, m_b, pd_b
|
79
|
+
new_probs = Array.new( 1 + combined_max - combined_min, 0.0 )
|
80
|
+
probs_a, offset_a = pd_a.to_ao
|
81
|
+
probs_b, offset_b = pd_b.to_ao
|
82
|
+
|
83
|
+
probs_a.each_with_index do |pa,i|
|
84
|
+
probs_b.each_with_index do |pb,j|
|
85
|
+
k = m_a * (i + offset_a) + m_b * (j + offset_b) - combined_min
|
86
|
+
pc = pa * pb
|
87
|
+
new_probs[ k ] += pc
|
88
|
+
end
|
89
|
+
end
|
90
|
+
GamesDice::Probabilities.new( new_probs, combined_min )
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
# @!visibility private
|
96
|
+
# This module is a set of related private methods for GamesDice::Probabilities that
|
97
|
+
# calculate how a distribution can be combined with itself.
|
98
|
+
module GamesDice::ProbabilityCalcSums
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def repeat_sum_internal( n )
|
103
|
+
pd_power = self
|
104
|
+
pd_result = nil
|
105
|
+
|
106
|
+
use_power = 1
|
107
|
+
loop do
|
108
|
+
if ( use_power & n ) > 0
|
109
|
+
if pd_result
|
110
|
+
pd_result = GamesDice::Probabilities.add_distributions( pd_result, pd_power )
|
111
|
+
else
|
112
|
+
pd_result = pd_power
|
113
|
+
end
|
114
|
+
end
|
115
|
+
use_power = use_power << 1
|
116
|
+
break if use_power > n
|
117
|
+
pd_power = GamesDice::Probabilities.add_distributions( pd_power, pd_power )
|
118
|
+
end
|
119
|
+
pd_result
|
120
|
+
end
|
121
|
+
|
122
|
+
def repeat_n_sum_k_internal( n, k, kmode )
|
123
|
+
if k >= n
|
124
|
+
return repeat_sum_internal( n )
|
125
|
+
end
|
126
|
+
new_probs = Array.new( @probs.count * k, 0.0 )
|
127
|
+
new_offset = @offset * k
|
128
|
+
d = n - k
|
129
|
+
|
130
|
+
each do | q, p_maybe |
|
131
|
+
repeat_n_sum_k_each_q( q, p_maybe, n, k, kmode, d, new_probs, new_offset )
|
132
|
+
end
|
133
|
+
|
134
|
+
GamesDice::Probabilities.new( new_probs, new_offset )
|
135
|
+
end
|
136
|
+
|
137
|
+
def repeat_n_sum_k_each_q q, p_maybe, n, k, kmode, d, new_probs, new_offset
|
138
|
+
# keep_distributions is array of Probabilities, indexed by number of keepers > q, which is in 0...k
|
139
|
+
keep_distributions = calc_keep_distributions( k, q, kmode )
|
140
|
+
p_table = calc_p_table( q, p_maybe, kmode )
|
141
|
+
(0...k).each do |kn|
|
142
|
+
repeat_n_sum_k_each_q_kn( k, kn, d, new_probs, new_offset, keep_distributions, p_table )
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def repeat_n_sum_k_each_q_kn k, kn, d, new_probs, new_offset, keep_distributions, p_table
|
147
|
+
keepers = [2] * kn + [1] * (k-kn)
|
148
|
+
p_so_far = keepers.inject(1.0) { |p,idx| p * p_table[idx] }
|
149
|
+
return unless p_so_far > 0.0
|
150
|
+
(0..d).each do |dn|
|
151
|
+
repeat_n_sum_k_each_q_kn_dn( keepers, kn, d, dn, p_so_far, new_probs, new_offset, keep_distributions, p_table )
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def repeat_n_sum_k_each_q_kn_dn keepers, kn, d, dn, p_so_far, new_probs, new_offset, keep_distributions, p_table
|
156
|
+
discards = [1] * (d-dn) + [0] * dn
|
157
|
+
sequence = keepers + discards
|
158
|
+
p_sequence = discards.inject( p_so_far ) { |p,idx| p * p_table[idx] }
|
159
|
+
return unless p_sequence > 0.0
|
160
|
+
p_sequence *= GamesDice::Combinations.count_variations( sequence )
|
161
|
+
kd = keep_distributions[kn]
|
162
|
+
kd.each { |r,p_r| new_probs[r-new_offset] += p_r * p_sequence }
|
163
|
+
end
|
164
|
+
|
165
|
+
def calc_keep_distributions k, q, kmode
|
166
|
+
kd_probabilities = calc_keep_definite_distributions q, kmode
|
167
|
+
|
168
|
+
keep_distributions = [ GamesDice::Probabilities.new( [1.0], q * k ) ]
|
169
|
+
if kd_probabilities && k > 1
|
170
|
+
(1...k).each do |n|
|
171
|
+
extra_o = GamesDice::Probabilities.new( [1.0], q * ( k - n ) )
|
172
|
+
n_probs = kd_probabilities.repeat_sum( n )
|
173
|
+
keep_distributions[n] = GamesDice::Probabilities.add_distributions( extra_o, n_probs )
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
keep_distributions
|
178
|
+
end
|
179
|
+
|
180
|
+
def calc_keep_definite_distributions q, kmode
|
181
|
+
kd_probabilities = nil
|
182
|
+
case kmode
|
183
|
+
when :keep_best
|
184
|
+
p_definites = p_gt(q)
|
185
|
+
kd_probabilities = given_ge( q + 1 ) if p_definites > 0.0
|
186
|
+
when :keep_worst
|
187
|
+
p_definites = p_lt(q)
|
188
|
+
kd_probabilities = given_le( q - 1 ) if p_definites > 0.0
|
189
|
+
end
|
190
|
+
kd_probabilities
|
191
|
+
end
|
192
|
+
|
193
|
+
def calc_p_table q, p_maybe, kmode
|
194
|
+
case kmode
|
195
|
+
when :keep_best
|
196
|
+
p_kept = p_gt(q)
|
197
|
+
p_rejected = p_lt(q)
|
198
|
+
when :keep_worst
|
199
|
+
p_kept = p_lt(q)
|
200
|
+
p_rejected = p_gt(q)
|
201
|
+
end
|
202
|
+
[ p_rejected, p_maybe, p_kept ]
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
# @!visibility private
|
209
|
+
# Helper module with optimised Ruby for counting variations of arrays, such as those returned by
|
210
|
+
# Array#repeated_combination
|
211
|
+
#
|
212
|
+
# @example How many ways can [3,3,6] be arranged?
|
213
|
+
# GamesDice::Combinations.count_variations( [3,3,6] )
|
214
|
+
# => 3
|
215
|
+
#
|
216
|
+
# @example When prob( a ) and result( a ) are same for any arrangement of Array a
|
217
|
+
# items = [1,2,3,4,5,6]
|
218
|
+
# items.repeated_combination(5).each do |a|
|
219
|
+
# this_result = result( a )
|
220
|
+
# this_prob = prob( a ) * GamesDice::Combinations.count_variations( a )
|
221
|
+
# # Do something useful with this knowledge! E.g. save it to probability array.
|
222
|
+
# end
|
223
|
+
#
|
224
|
+
module GamesDice::Combinations
|
225
|
+
@@variations_cache = {}
|
226
|
+
@@factorial_cache = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
|
227
|
+
|
228
|
+
# Counts variations of an array. A unique variation is an arrangement of the array elements which
|
229
|
+
# is detectably different (using ==) from any other. So [1,1,1] has only 1 unique arrangement,
|
230
|
+
# but [1,2,3] has 6 possibilities.
|
231
|
+
# @param [Array] array List of things that can be arranged
|
232
|
+
# @return [Integer] Number of unique arrangements
|
233
|
+
def self.count_variations array
|
234
|
+
all_count = array.count
|
235
|
+
group_sizes = group_counts( array )
|
236
|
+
cache_key = all_count.to_s + ":" + group_sizes.join(',')
|
237
|
+
@@variations_cache[cache_key] ||= variations_of( all_count, group_sizes )
|
238
|
+
end
|
239
|
+
|
240
|
+
private
|
241
|
+
|
242
|
+
def self.variations_of all_count, groups
|
243
|
+
all_arrangements = factorial( all_count )
|
244
|
+
# The reject is an optimisation to avoid calculating and multplying by factorial(1) (==1)
|
245
|
+
identical_arrangements = groups.reject {|x| x==1 }.inject(1) { |prod,g| prod * factorial(g) }
|
246
|
+
all_arrangements/identical_arrangements
|
247
|
+
end
|
248
|
+
|
249
|
+
# Returns counts of unique items in array e.g. [8,8,8,7,6,6] returns [1,2,3]
|
250
|
+
# Sort is for caching
|
251
|
+
def self.group_counts array
|
252
|
+
array.group_by {|x| x}.values.map {|v| v.count}.sort
|
253
|
+
end
|
254
|
+
|
255
|
+
def self.factorial n
|
256
|
+
# Can start range from 2 because we have pre-cached the result for n=1
|
257
|
+
@@factorial_cache[n] ||= (2..n).inject(:*)
|
258
|
+
end
|
259
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'games_dice/prob_helpers'
|
2
|
+
|
1
3
|
# This class models probability distributions for dice systems.
|
2
4
|
#
|
3
5
|
# An object of this class represents a single distribution, which might be the result of a complex
|
@@ -19,6 +21,9 @@
|
|
19
21
|
# probs.p_ge( 10 ) # => 0.16666666666666669
|
20
22
|
#
|
21
23
|
class GamesDice::Probabilities
|
24
|
+
include GamesDice::ProbabilityValidations
|
25
|
+
include GamesDice::ProbabilityCalcSums
|
26
|
+
extend GamesDice::ProbabilityCalcAddDistributions
|
22
27
|
|
23
28
|
# Creates new instance of GamesDice::Probabilities.
|
24
29
|
# @param [Array<Float>] probs Each entry in the array is the probability of getting a result
|
@@ -30,12 +35,6 @@ class GamesDice::Probabilities
|
|
30
35
|
@offset = Integer(offset)
|
31
36
|
end
|
32
37
|
|
33
|
-
# @!visibility private
|
34
|
-
# the Array, Offset representation of probabilities.
|
35
|
-
def to_ao
|
36
|
-
[ @probs, @offset ]
|
37
|
-
end
|
38
|
-
|
39
38
|
# Iterates through value, probability pairs
|
40
39
|
# @yieldparam [Integer] result A result that may be possible in the dice scheme
|
41
40
|
# @yieldparam [Float] probability Probability of result, in range 0.0..1.0
|
@@ -177,14 +176,11 @@ class GamesDice::Probabilities
|
|
177
176
|
# @param [GamesDice::Probabilities] pd_b Second distribution
|
178
177
|
# @return [GamesDice::Probabilities]
|
179
178
|
def self.add_distributions pd_a, pd_b
|
180
|
-
|
181
|
-
raise TypeError, "parameter to add_distributions is not a GamesDice::Probabilities"
|
182
|
-
end
|
183
|
-
|
179
|
+
check_is_gdp( pd_a, pd_b )
|
184
180
|
combined_min = pd_a.min + pd_b.min
|
185
181
|
combined_max = pd_a.max + pd_b.max
|
186
182
|
|
187
|
-
add_distributions_internal combined_min, combined_max, 1, pd_a, 1, pd_b
|
183
|
+
add_distributions_internal( combined_min, combined_max, 1, pd_a, 1, pd_b )
|
188
184
|
end
|
189
185
|
|
190
186
|
# Combines two distributions with multipliers to create a third, that represents the distribution
|
@@ -195,19 +191,13 @@ class GamesDice::Probabilities
|
|
195
191
|
# @param [GamesDice::Probabilities] pd_b Second distribution
|
196
192
|
# @return [GamesDice::Probabilities]
|
197
193
|
def self.add_distributions_mult m_a, pd_a, m_b, pd_b
|
198
|
-
|
199
|
-
raise TypeError, "parameter to add_distributions_mult is not a GamesDice::Probabilities"
|
200
|
-
end
|
201
|
-
|
194
|
+
check_is_gdp( pd_a, pd_b )
|
202
195
|
m_a = Integer(m_a)
|
203
196
|
m_b = Integer(m_b)
|
204
197
|
|
205
|
-
combined_min, combined_max =
|
206
|
-
m_a * pd_a.min + m_b * pd_b.min, m_a * pd_a.max + m_b * pd_b.min,
|
207
|
-
m_a * pd_a.min + m_b * pd_b.max, m_a * pd_a.max + m_b * pd_b.max,
|
208
|
-
].minmax
|
198
|
+
combined_min, combined_max = calc_combined_extremes( m_a, pd_a, m_b, pd_b ).minmax
|
209
199
|
|
210
|
-
add_distributions_internal combined_min, combined_max, m_a, pd_a, m_b, pd_b
|
200
|
+
add_distributions_internal( combined_min, combined_max, m_a, pd_a, m_b, pd_b )
|
211
201
|
end
|
212
202
|
|
213
203
|
# Returns a symbol for the language name that this class is implemented in. The C version of the
|
@@ -225,23 +215,7 @@ class GamesDice::Probabilities
|
|
225
215
|
n = Integer( n )
|
226
216
|
raise "Cannot combine probabilities less than once" if n < 1
|
227
217
|
raise "Probability distribution too large" if ( n * @probs.count ) > 1000000
|
228
|
-
|
229
|
-
pd_result = nil
|
230
|
-
|
231
|
-
use_power = 1
|
232
|
-
loop do
|
233
|
-
if ( use_power & n ) > 0
|
234
|
-
if pd_result
|
235
|
-
pd_result = GamesDice::Probabilities.add_distributions( pd_result, pd_power )
|
236
|
-
else
|
237
|
-
pd_result = pd_power
|
238
|
-
end
|
239
|
-
end
|
240
|
-
use_power = use_power << 1
|
241
|
-
break if use_power > n
|
242
|
-
pd_power = GamesDice::Probabilities.add_distributions( pd_power, pd_power )
|
243
|
-
end
|
244
|
-
pd_result
|
218
|
+
repeat_sum_internal( n )
|
245
219
|
end
|
246
220
|
|
247
221
|
# Calculates distribution generated by summing best k results of n iterations
|
@@ -256,190 +230,15 @@ class GamesDice::Probabilities
|
|
256
230
|
# Technically this is a limitation of C code, but Ruby version is most likely slow and inaccurate beyond 170
|
257
231
|
raise "Too many dice to calculate numbers of arrangements" if n > 170
|
258
232
|
check_keep_mode( kmode )
|
259
|
-
|
260
|
-
if k >= n
|
261
|
-
return repeat_sum( n )
|
262
|
-
end
|
263
|
-
new_probs = Array.new( @probs.count * k, 0.0 )
|
264
|
-
new_offset = @offset * k
|
265
|
-
d = n - k
|
266
|
-
|
267
|
-
each do | q, p_maybe |
|
268
|
-
next unless p_maybe > 0.0
|
269
|
-
|
270
|
-
# keep_distributions is array of Probabilities, indexed by number of keepers > q, which is in 0...k
|
271
|
-
keep_distributions = calc_keep_distributions( k, q, kmode )
|
272
|
-
p_table = calc_p_table( q, p_maybe, kmode )
|
273
|
-
|
274
|
-
(0...k).each do |n|
|
275
|
-
keepers = [2] * n + [1] * (k-n)
|
276
|
-
p_so_far = keepers.inject(1.0) { |p,idx| p * p_table[idx] }
|
277
|
-
next unless p_so_far > 0.0
|
278
|
-
(0..d).each do |dn|
|
279
|
-
discards = [1] * (d-dn) + [0] * dn
|
280
|
-
sequence = keepers + discards
|
281
|
-
p_sequence = discards.inject( p_so_far ) { |p,idx| p * p_table[idx] }
|
282
|
-
next unless p_sequence > 0.0
|
283
|
-
p_sequence *= GamesDice::Combinations.count_variations( sequence )
|
284
|
-
kd = keep_distributions[n]
|
285
|
-
kd.each { |r,p_r| new_probs[r-new_offset] += p_r * p_sequence }
|
286
|
-
end
|
287
|
-
end
|
288
|
-
end
|
289
|
-
GamesDice::Probabilities.new( new_probs, new_offset )
|
233
|
+
repeat_n_sum_k_internal( n, k, kmode )
|
290
234
|
end
|
291
235
|
|
292
236
|
private
|
293
237
|
|
294
|
-
def self.add_distributions_internal combined_min, combined_max, m_a, pd_a, m_b, pd_b
|
295
|
-
new_probs = Array.new( 1 + combined_max - combined_min, 0.0 )
|
296
|
-
probs_a, offset_a = pd_a.to_ao
|
297
|
-
probs_b, offset_b = pd_b.to_ao
|
298
|
-
|
299
|
-
probs_a.each_with_index do |pa,i|
|
300
|
-
probs_b.each_with_index do |pb,j|
|
301
|
-
k = m_a * (i + offset_a) + m_b * (j + offset_b) - combined_min
|
302
|
-
pc = pa * pb
|
303
|
-
new_probs[ k ] += pc
|
304
|
-
end
|
305
|
-
end
|
306
|
-
GamesDice::Probabilities.new( new_probs, combined_min )
|
307
|
-
end
|
308
|
-
|
309
|
-
def check_probs_array probs_array
|
310
|
-
raise TypeError unless probs_array.is_a?( Array )
|
311
|
-
probs_array.map!{ |n| Float(n) }
|
312
|
-
total = probs_array.inject(0.0) do |t,x|
|
313
|
-
if x < 0.0 || x > 1.0
|
314
|
-
raise ArgumentError, "Found probability value #{x} which is not in range 0.0..1.0"
|
315
|
-
end
|
316
|
-
t+x
|
317
|
-
end
|
318
|
-
if (total-1.0).abs > 1e-6
|
319
|
-
raise ArgumentError, "Total probabilities too far from 1.0 for a valid distribution"
|
320
|
-
end
|
321
|
-
probs_array
|
322
|
-
end
|
323
|
-
|
324
|
-
def calc_keep_distributions k, q, kmode
|
325
|
-
kd_probabilities = calc_keep_definite_distributions q, kmode
|
326
|
-
|
327
|
-
keep_distributions = [ GamesDice::Probabilities.new( [1.0], q * k ) ]
|
328
|
-
if kd_probabilities && k > 1
|
329
|
-
(1...k).each do |n|
|
330
|
-
extra_o = GamesDice::Probabilities.new( [1.0], q * ( k - n ) )
|
331
|
-
n_probs = kd_probabilities.repeat_sum( n )
|
332
|
-
keep_distributions[n] = GamesDice::Probabilities.add_distributions( extra_o, n_probs )
|
333
|
-
end
|
334
|
-
end
|
335
|
-
|
336
|
-
keep_distributions
|
337
|
-
end
|
338
|
-
|
339
|
-
def calc_keep_definite_distributions q, kmode
|
340
|
-
kd_probabilities = nil
|
341
|
-
case kmode
|
342
|
-
when :keep_best
|
343
|
-
p_definites = p_gt(q)
|
344
|
-
kd_probabilities = given_ge( q + 1 ) if p_definites > 0.0
|
345
|
-
when :keep_worst
|
346
|
-
p_definites = p_lt(q)
|
347
|
-
kd_probabilities = given_le( q - 1 ) if p_definites > 0.0
|
348
|
-
end
|
349
|
-
kd_probabilities
|
350
|
-
end
|
351
|
-
|
352
|
-
def calc_p_table q, p_maybe, kmode
|
353
|
-
case kmode
|
354
|
-
when :keep_best
|
355
|
-
p_kept = p_gt(q)
|
356
|
-
p_rejected = p_lt(q)
|
357
|
-
when :keep_worst
|
358
|
-
p_kept = p_lt(q)
|
359
|
-
p_rejected = p_gt(q)
|
360
|
-
end
|
361
|
-
[ p_rejected, p_maybe, p_kept ]
|
362
|
-
end
|
363
|
-
|
364
|
-
# Convert hash to array,offset notation
|
365
|
-
def self.prob_h_to_ao h
|
366
|
-
rmin,rmax = h.keys.minmax
|
367
|
-
o = rmin
|
368
|
-
s = 1 + rmax - rmin
|
369
|
-
raise ArgumentError, "Range of possible results too large" if s > 1000000
|
370
|
-
a = Array.new( s, 0.0 )
|
371
|
-
h.each { |k,v| a[k-rmin] = Float(v) }
|
372
|
-
[a,o]
|
373
|
-
end
|
374
|
-
|
375
|
-
# Convert array,offset notation to hash
|
376
|
-
def self.prob_ao_to_h a, o
|
377
|
-
h = Hash.new
|
378
|
-
a.each_with_index { |v,i| h[i+o] = v if v > 0.0 }
|
379
|
-
h
|
380
|
-
end
|
381
|
-
|
382
238
|
def calc_expected
|
383
239
|
total = 0.0
|
384
240
|
@probs.each_with_index { |v,i| total += (i+@offset)*v }
|
385
241
|
total
|
386
242
|
end
|
387
243
|
|
388
|
-
def check_keep_mode kmode
|
389
|
-
raise "Keep mode #{kmode.inspect} not recognised" unless [:keep_best,:keep_worst].member?( kmode )
|
390
|
-
end
|
391
|
-
|
392
244
|
end # class GamesDice::Probabilities
|
393
|
-
|
394
|
-
# @!visibility private
|
395
|
-
# Helper module with optimised Ruby for counting variations of arrays, such as those returned by
|
396
|
-
# Array#repeated_combination
|
397
|
-
#
|
398
|
-
# @example How many ways can [3,3,6] be arranged?
|
399
|
-
# GamesDice::Combinations.count_variations( [3,3,6] )
|
400
|
-
# => 3
|
401
|
-
#
|
402
|
-
# @example When prob( a ) and result( a ) are same for any arrangement of Array a
|
403
|
-
# items = [1,2,3,4,5,6]
|
404
|
-
# items.repeated_combination(5).each do |a|
|
405
|
-
# this_result = result( a )
|
406
|
-
# this_prob = prob( a ) * GamesDice::Combinations.count_variations( a )
|
407
|
-
# # Do something useful with this knowledge! E.g. save it to probability array.
|
408
|
-
# end
|
409
|
-
#
|
410
|
-
module GamesDice::Combinations
|
411
|
-
@@variations_cache = {}
|
412
|
-
@@factorial_cache = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
|
413
|
-
|
414
|
-
# Counts variations of an array. A unique variation is an arrangement of the array elements which
|
415
|
-
# is detectably different (using ==) from any other. So [1,1,1] has only 1 unique arrangement,
|
416
|
-
# but [1,2,3] has 6 possibilities.
|
417
|
-
# @param [Array] array List of things that can be arranged
|
418
|
-
# @return [Integer] Number of unique arrangements
|
419
|
-
def self.count_variations array
|
420
|
-
all_count = array.count
|
421
|
-
group_sizes = group_counts( array )
|
422
|
-
cache_key = all_count.to_s + ":" + group_sizes.join(',')
|
423
|
-
@@variations_cache[cache_key] ||= variations_of( all_count, group_sizes )
|
424
|
-
end
|
425
|
-
|
426
|
-
private
|
427
|
-
|
428
|
-
def self.variations_of all_count, groups
|
429
|
-
all_arrangements = factorial( all_count )
|
430
|
-
# The reject is an optimisation to avoid calculating and multplying by factorial(1) (==1)
|
431
|
-
identical_arrangements = groups.reject {|x| x==1 }.inject(1) { |prod,g| prod * factorial(g) }
|
432
|
-
all_arrangements/identical_arrangements
|
433
|
-
end
|
434
|
-
|
435
|
-
# Returns counts of unique items in array e.g. [8,8,8,7,6,6] returns [1,2,3]
|
436
|
-
# Sort is for caching
|
437
|
-
def self.group_counts array
|
438
|
-
array.group_by {|x| x}.values.map {|v| v.count}.sort
|
439
|
-
end
|
440
|
-
|
441
|
-
def self.factorial n
|
442
|
-
# Can start range from 2 because we have pre-cached the result for n=1
|
443
|
-
@@factorial_cache[n] ||= (2..n).inject(:*)
|
444
|
-
end
|
445
|
-
end
|
data/lib/games_dice/version.rb
CHANGED
data/spec/bunch_spec.rb
CHANGED
data/spec/complex_die_spec.rb
CHANGED
data/spec/dice_spec.rb
CHANGED
data/spec/die_result_spec.rb
CHANGED
data/spec/die_spec.rb
CHANGED
data/spec/helpers.rb
CHANGED
data/spec/map_rule_spec.rb
CHANGED
data/spec/parser_spec.rb
CHANGED
data/spec/probability_spec.rb
CHANGED
data/spec/readme_spec.rb
CHANGED
data/spec/reroll_rule_spec.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: games_dice
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.10
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-07-
|
12
|
+
date: 2013-07-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -98,7 +98,7 @@ dependencies:
|
|
98
98
|
requirements:
|
99
99
|
- - ! '>='
|
100
100
|
- !ruby/object:Gem::Version
|
101
|
-
version:
|
101
|
+
version: 0.8.3
|
102
102
|
type: :development
|
103
103
|
prerelease: false
|
104
104
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -106,7 +106,7 @@ dependencies:
|
|
106
106
|
requirements:
|
107
107
|
- - ! '>='
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
109
|
+
version: 0.8.3
|
110
110
|
- !ruby/object:Gem::Dependency
|
111
111
|
name: redcarpet
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,6 +164,7 @@ files:
|
|
164
164
|
- lib/games_dice.rb
|
165
165
|
- lib/games_dice/bunch.rb
|
166
166
|
- lib/games_dice/complex_die.rb
|
167
|
+
- lib/games_dice/complex_die_helpers.rb
|
167
168
|
- lib/games_dice/constants.rb
|
168
169
|
- lib/games_dice/dice.rb
|
169
170
|
- lib/games_dice/die.rb
|
@@ -171,6 +172,7 @@ files:
|
|
171
172
|
- lib/games_dice/map_rule.rb
|
172
173
|
- lib/games_dice/marshal.rb
|
173
174
|
- lib/games_dice/parser.rb
|
175
|
+
- lib/games_dice/prob_helpers.rb
|
174
176
|
- lib/games_dice/probabilities.rb
|
175
177
|
- lib/games_dice/reroll_rule.rb
|
176
178
|
- lib/games_dice/version.rb
|
@@ -201,7 +203,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
201
203
|
version: '0'
|
202
204
|
segments:
|
203
205
|
- 0
|
204
|
-
hash: -
|
206
|
+
hash: -2123252871276968084
|
205
207
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
206
208
|
none: false
|
207
209
|
requirements:
|
@@ -210,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
212
|
version: '0'
|
211
213
|
segments:
|
212
214
|
- 0
|
213
|
-
hash: -
|
215
|
+
hash: -2123252871276968084
|
214
216
|
requirements: []
|
215
217
|
rubyforge_project:
|
216
218
|
rubygems_version: 1.8.24
|