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,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