games_dice 0.3.12 → 0.4.2

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.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +38 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +53 -0
  5. data/.yardopts +1 -1
  6. data/CHANGELOG.md +18 -0
  7. data/Gemfile +12 -0
  8. data/README.md +11 -33
  9. data/Rakefile +10 -23
  10. data/ext/games_dice/extconf.rb +4 -22
  11. data/ext/games_dice/games_dice.c +1 -1
  12. data/ext/games_dice/probabilities.c +128 -9
  13. data/ext/games_dice/probabilities.h +1 -1
  14. data/games_dice.gemspec +22 -36
  15. data/lib/games_dice/bunch.rb +203 -247
  16. data/lib/games_dice/bunch_helpers.rb +68 -0
  17. data/lib/games_dice/complex_die.rb +151 -270
  18. data/lib/games_dice/complex_die_helpers.rb +260 -60
  19. data/lib/games_dice/constants.rb +10 -10
  20. data/lib/games_dice/dice.rb +153 -143
  21. data/lib/games_dice/die.rb +101 -97
  22. data/lib/games_dice/die_result.rb +206 -189
  23. data/lib/games_dice/map_rule.rb +72 -70
  24. data/lib/games_dice/marshal.rb +41 -13
  25. data/lib/games_dice/parser.rb +245 -218
  26. data/lib/games_dice/reroll_rule.rb +76 -77
  27. data/lib/games_dice/version.rb +4 -1
  28. data/lib/games_dice.rb +23 -25
  29. data/spec/bunch_spec.rb +399 -420
  30. data/spec/complex_die_spec.rb +314 -305
  31. data/spec/dice_spec.rb +33 -34
  32. data/spec/die_result_spec.rb +174 -181
  33. data/spec/die_spec.rb +81 -81
  34. data/spec/helpers.rb +25 -25
  35. data/spec/map_rule_spec.rb +40 -44
  36. data/spec/parser_spec.rb +106 -82
  37. data/spec/probability_spec.rb +522 -526
  38. data/spec/readme_spec.rb +410 -390
  39. data/spec/reroll_rule_spec.rb +40 -44
  40. metadata +17 -129
  41. data/.travis.yml +0 -14
  42. data/lib/games_dice/prob_helpers.rb +0 -259
  43. data/lib/games_dice/probabilities.rb +0 -244
@@ -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 'accept self-consistent operator/value pairs as a trigger' do
8
+ expect { described_class.new(5, :>, :reroll_subtract) }.not_to raise_error
9
+ expect { described_class.new(1..5, :member?, :reroll_replace) }.not_to raise_error
10
+ end
11
+
12
+ it 'reject inconsistent operator/value pairs for a trigger' do
13
+ expect { described_class.new(5, :member?, :reroll_subtract) }.to raise_error(ArgumentError)
14
+ expect { described_class.new(1..5, :>, :reroll_replace) }.to raise_error(ArgumentError)
15
+ end
16
+
17
+ it 'reject bad re-roll types' do
18
+ expect { described_class.new(5, :>, :reroll_again) }.to raise_error(ArgumentError)
19
+ expect { described_class.new(1..5, :member?, 42) }.to raise_error(ArgumentError)
20
+ end
21
+ end
22
+
23
+ describe '#applies?' do
24
+ it 'return true if a trigger condition is met' do
25
+ rule = described_class.new(5, :>, :reroll_subtract)
26
+ expect(rule.applies?(4)).to be true
27
+
28
+ rule = described_class.new(1..5, :member?, :reroll_subtract)
29
+ expect(rule.applies?(4)).to be true
30
+ end
31
+
32
+ it 'return false if a trigger condition is not met' do
33
+ rule = described_class.new(5, :>, :reroll_subtract)
34
+ expect(rule.applies?(7)).to be false
35
+
36
+ rule = described_class.new(1..5, :member?, :reroll_subtract)
37
+ expect(rule.applies?(6)).to be false
38
+ end
39
+ end
40
+ end
metadata CHANGED
@@ -1,129 +1,32 @@
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.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Slater
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2017-10-17 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rspec
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 2.13.0
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: 2.13.0
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 1.9.1
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: 1.9.1
41
- - !ruby/object:Gem::Dependency
42
- name: yard
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: 0.8.6
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: 0.8.6
55
- - !ruby/object:Gem::Dependency
56
- name: coveralls
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: 0.6.7
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: 0.6.7
69
- - !ruby/object:Gem::Dependency
70
- name: json
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: 1.7.7
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: 1.7.7
83
- - !ruby/object:Gem::Dependency
84
- name: rake-compiler
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: 0.8.3
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: 0.8.3
97
- - !ruby/object:Gem::Dependency
98
- name: redcarpet
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: 2.3.0
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: 2.3.0
111
12
  - !ruby/object:Gem::Dependency
112
13
  name: parslet
113
14
  requirement: !ruby/object:Gem::Requirement
114
15
  requirements:
115
- - - ">="
16
+ - - "~>"
116
17
  - !ruby/object:Gem::Version
117
- version: 1.5.0
18
+ version: '2.0'
118
19
  type: :runtime
119
20
  prerelease: false
120
21
  version_requirements: !ruby/object:Gem::Requirement
121
22
  requirements:
122
- - - ">="
23
+ - - "~>"
123
24
  - !ruby/object:Gem::Version
124
- version: 1.5.0
125
- description: A library for simulating dice. Use it to construct dice-rolling systems
25
+ version: '2.0'
26
+ description: 'A library for simulating dice. Use it to construct dice-rolling systems
126
27
  used in role-playing and board games.
28
+
29
+ '
127
30
  email:
128
31
  - slobo777@gmail.com
129
32
  executables: []
@@ -131,8 +34,9 @@ extensions:
131
34
  - ext/games_dice/extconf.rb
132
35
  extra_rdoc_files: []
133
36
  files:
37
+ - ".github/workflows/ci.yml"
134
38
  - ".gitignore"
135
- - ".travis.yml"
39
+ - ".rubocop.yml"
136
40
  - ".yardopts"
137
41
  - CHANGELOG.md
138
42
  - Gemfile
@@ -146,6 +50,7 @@ files:
146
50
  - games_dice.gemspec
147
51
  - lib/games_dice.rb
148
52
  - lib/games_dice/bunch.rb
53
+ - lib/games_dice/bunch_helpers.rb
149
54
  - lib/games_dice/complex_die.rb
150
55
  - lib/games_dice/complex_die_helpers.rb
151
56
  - lib/games_dice/constants.rb
@@ -155,8 +60,6 @@ files:
155
60
  - lib/games_dice/map_rule.rb
156
61
  - lib/games_dice/marshal.rb
157
62
  - lib/games_dice/parser.rb
158
- - lib/games_dice/prob_helpers.rb
159
- - lib/games_dice/probabilities.rb
160
63
  - lib/games_dice/reroll_rule.rb
161
64
  - lib/games_dice/version.rb
162
65
  - spec/bunch_spec.rb
@@ -174,8 +77,8 @@ files:
174
77
  homepage: https://github.com/neilslater/games_dice
175
78
  licenses:
176
79
  - MIT
177
- metadata: {}
178
- post_install_message:
80
+ metadata:
81
+ rubygems_mfa_required: 'true'
179
82
  rdoc_options: []
180
83
  require_paths:
181
84
  - lib
@@ -183,30 +86,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
183
86
  requirements:
184
87
  - - ">="
185
88
  - !ruby/object:Gem::Version
186
- version: '0'
89
+ version: 3.3.0
187
90
  required_rubygems_version: !ruby/object:Gem::Requirement
188
91
  requirements:
189
92
  - - ">="
190
93
  - !ruby/object:Gem::Version
191
94
  version: '0'
192
95
  requirements: []
193
- rubyforge_project:
194
- rubygems_version: 2.6.12
195
- signing_key:
96
+ rubygems_version: 4.0.1
196
97
  specification_version: 4
197
98
  summary: Simulates and explains dice rolls from simple "1d6" to complex "roll 7 ten-sided
198
99
  dice, take best 3, results of 10 roll again and add on".
199
- test_files:
200
- - spec/bunch_spec.rb
201
- - spec/complex_die_spec.rb
202
- - spec/dice_spec.rb
203
- - spec/die_result_spec.rb
204
- - spec/die_spec.rb
205
- - spec/fixtures/probs_fair_die_6.dat
206
- - spec/helpers.rb
207
- - spec/map_rule_spec.rb
208
- - spec/parser_spec.rb
209
- - spec/probability_spec.rb
210
- - spec/readme_spec.rb
211
- - spec/reroll_rule_spec.rb
212
- has_rdoc:
100
+ test_files: []
data/.travis.yml DELETED
@@ -1,14 +0,0 @@
1
- language: ruby
2
- before_install:
3
- - gem install bundler
4
- rvm:
5
- - "2.0.0"
6
- - "2.1.0"
7
- - "2.1.1"
8
- - "2.1.4"
9
- - "2.2.0"
10
- - "2.3.1"
11
- - "2.4.1"
12
- - "2.4.2"
13
- - jruby-9.0.5.0
14
-
@@ -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