games_dice 0.3.12 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,44 +1,40 @@
1
- require 'helpers'
2
-
3
- describe GamesDice::RerollRule do
4
-
5
- describe "#new" do
6
-
7
- it "should accept self-consistent operator/value pairs as a trigger" do
8
- GamesDice::RerollRule.new( 5, :>, :reroll_subtract )
9
- GamesDice::RerollRule.new( (1..5), :member?, :reroll_replace )
10
- end
11
-
12
- it "should reject inconsistent operator/value pairs for a trigger" do
13
- lambda { GamesDice::RerollRule.new( 5, :member?, :reroll_subtract ) }.should raise_error( ArgumentError )
14
- lambda { GamesDice::RerollRule.new( (1..5), :>, :reroll_replace ) }.should raise_error( ArgumentError )
15
- end
16
-
17
- it "should reject bad re-roll types" do
18
- lambda { GamesDice::RerollRule.new( 5, :>, :reroll_again ) }.should raise_error( ArgumentError )
19
- lambda { GamesDice::RerollRule.new( (1..5), :member?, 42 ) }.should raise_error( ArgumentError )
20
- end
21
-
22
- end
23
-
24
- describe '#applies?' do
25
-
26
- it "should return true if a trigger condition is met" do
27
- rule = GamesDice::RerollRule.new( 5, :>, :reroll_subtract )
28
- rule.applies?(4).should == true
29
-
30
- rule = GamesDice::RerollRule.new( (1..5), :member?, :reroll_subtract )
31
- rule.applies?(4).should == true
32
- end
33
-
34
- it "should return false if a trigger condition is not met" do
35
- rule = GamesDice::RerollRule.new( 5, :>, :reroll_subtract )
36
- rule.applies?(7).should == false
37
-
38
- rule = GamesDice::RerollRule.new( (1..5), :member?, :reroll_subtract )
39
- rule.applies?(6).should == false
40
- end
41
-
42
- end
43
-
44
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'helpers'
4
+
5
+ describe GamesDice::RerollRule do
6
+ describe '#new' do
7
+ it 'should accept self-consistent operator/value pairs as a trigger' do
8
+ GamesDice::RerollRule.new(5, :>, :reroll_subtract)
9
+ GamesDice::RerollRule.new((1..5), :member?, :reroll_replace)
10
+ end
11
+
12
+ it 'should reject inconsistent operator/value pairs for a trigger' do
13
+ expect(-> { GamesDice::RerollRule.new(5, :member?, :reroll_subtract) }).to raise_error(ArgumentError)
14
+ expect(-> { GamesDice::RerollRule.new((1..5), :>, :reroll_replace) }).to raise_error(ArgumentError)
15
+ end
16
+
17
+ it 'should reject bad re-roll types' do
18
+ expect(-> { GamesDice::RerollRule.new(5, :>, :reroll_again) }).to raise_error(ArgumentError)
19
+ expect(-> { GamesDice::RerollRule.new((1..5), :member?, 42) }).to raise_error(ArgumentError)
20
+ end
21
+ end
22
+
23
+ describe '#applies?' do
24
+ it 'should return true if a trigger condition is met' do
25
+ rule = GamesDice::RerollRule.new(5, :>, :reroll_subtract)
26
+ expect(rule.applies?(4)).to be true
27
+
28
+ rule = GamesDice::RerollRule.new((1..5), :member?, :reroll_subtract)
29
+ expect(rule.applies?(4)).to be true
30
+ end
31
+
32
+ it 'should return false if a trigger condition is not met' do
33
+ rule = GamesDice::RerollRule.new(5, :>, :reroll_subtract)
34
+ expect(rule.applies?(7)).to be false
35
+
36
+ rule = GamesDice::RerollRule.new((1..5), :member?, :reroll_subtract)
37
+ expect(rule.applies?(6)).to be false
38
+ end
39
+ end
40
+ end
metadata CHANGED
@@ -1,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: games_dice
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.12
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Slater
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-17 00:00:00.000000000 Z
11
+ date: 2021-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rspec
14
+ name: coveralls
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 2.13.0
19
+ version: 0.6.7
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 2.13.0
26
+ version: 0.6.7
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.7.7
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.7.7
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -39,75 +53,75 @@ dependencies:
39
53
  - !ruby/object:Gem::Version
40
54
  version: 1.9.1
41
55
  - !ruby/object:Gem::Dependency
42
- name: yard
56
+ name: rake-compiler
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - ">="
46
60
  - !ruby/object:Gem::Version
47
- version: 0.8.6
61
+ version: 0.8.3
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - ">="
53
67
  - !ruby/object:Gem::Version
54
- version: 0.8.6
68
+ version: 0.8.3
55
69
  - !ruby/object:Gem::Dependency
56
- name: coveralls
70
+ name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - ">="
60
74
  - !ruby/object:Gem::Version
61
- version: 0.6.7
75
+ version: 2.13.0
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - ">="
67
81
  - !ruby/object:Gem::Version
68
- version: 0.6.7
82
+ version: 2.13.0
69
83
  - !ruby/object:Gem::Dependency
70
- name: json
84
+ name: rubocop
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - ">="
74
88
  - !ruby/object:Gem::Version
75
- version: 1.7.7
89
+ version: 1.2.1
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - ">="
81
95
  - !ruby/object:Gem::Version
82
- version: 1.7.7
96
+ version: 1.2.1
83
97
  - !ruby/object:Gem::Dependency
84
- name: rake-compiler
98
+ name: yard
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
101
  - - ">="
88
102
  - !ruby/object:Gem::Version
89
- version: 0.8.3
103
+ version: 0.8.6
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
- version: 0.8.3
110
+ version: 0.8.6
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: redcarpet
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
115
  - - ">="
102
116
  - !ruby/object:Gem::Version
103
- version: 2.3.0
117
+ version: 3.5.1
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
122
  - - ">="
109
123
  - !ruby/object:Gem::Version
110
- version: 2.3.0
124
+ version: 3.5.1
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: parslet
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -132,6 +146,7 @@ extensions:
132
146
  extra_rdoc_files: []
133
147
  files:
134
148
  - ".gitignore"
149
+ - ".rubocop.yml"
135
150
  - ".travis.yml"
136
151
  - ".yardopts"
137
152
  - CHANGELOG.md
@@ -155,8 +170,6 @@ files:
155
170
  - lib/games_dice/map_rule.rb
156
171
  - lib/games_dice/marshal.rb
157
172
  - lib/games_dice/parser.rb
158
- - lib/games_dice/prob_helpers.rb
159
- - lib/games_dice/probabilities.rb
160
173
  - lib/games_dice/reroll_rule.rb
161
174
  - lib/games_dice/version.rb
162
175
  - spec/bunch_spec.rb
@@ -175,7 +188,7 @@ homepage: https://github.com/neilslater/games_dice
175
188
  licenses:
176
189
  - MIT
177
190
  metadata: {}
178
- post_install_message:
191
+ post_install_message:
179
192
  rdoc_options: []
180
193
  require_paths:
181
194
  - lib
@@ -183,16 +196,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
183
196
  requirements:
184
197
  - - ">="
185
198
  - !ruby/object:Gem::Version
186
- version: '0'
199
+ version: 2.6.0
187
200
  required_rubygems_version: !ruby/object:Gem::Requirement
188
201
  requirements:
189
202
  - - ">="
190
203
  - !ruby/object:Gem::Version
191
204
  version: '0'
192
205
  requirements: []
193
- rubyforge_project:
194
- rubygems_version: 2.6.12
195
- signing_key:
206
+ rubygems_version: 3.2.3
207
+ signing_key:
196
208
  specification_version: 4
197
209
  summary: Simulates and explains dice rolls from simple "1d6" to complex "roll 7 ten-sided
198
210
  dice, take best 3, results of 10 roll again and add on".
@@ -209,4 +221,3 @@ test_files:
209
221
  - spec/probability_spec.rb
210
222
  - spec/readme_spec.rb
211
223
  - spec/reroll_rule_spec.rb
212
- has_rdoc:
@@ -1,259 +0,0 @@
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