games_dice 0.4.0 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +38 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +41 -3
- data/.yardopts +1 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile +10 -0
- data/README.md +11 -33
- data/Rakefile +2 -18
- data/ext/games_dice/games_dice.c +1 -1
- data/ext/games_dice/probabilities.c +127 -8
- data/ext/games_dice/probabilities.h +1 -1
- data/games_dice.gemspec +11 -19
- data/lib/games_dice/bunch.rb +27 -65
- data/lib/games_dice/bunch_helpers.rb +68 -0
- data/lib/games_dice/complex_die.rb +10 -146
- data/lib/games_dice/complex_die_helpers.rb +238 -46
- data/lib/games_dice/dice.rb +17 -10
- data/lib/games_dice/die.rb +2 -2
- data/lib/games_dice/die_result.rb +84 -71
- data/lib/games_dice/marshal.rb +26 -3
- data/lib/games_dice/parser.rb +128 -102
- data/lib/games_dice/version.rb +2 -1
- data/lib/games_dice.rb +7 -9
- data/spec/bunch_spec.rb +72 -72
- data/spec/complex_die_spec.rb +158 -158
- data/spec/dice_spec.rb +5 -5
- data/spec/die_result_spec.rb +98 -98
- data/spec/die_spec.rb +19 -19
- data/spec/helpers.rb +2 -4
- data/spec/map_rule_spec.rb +17 -17
- data/spec/parser_spec.rb +7 -7
- data/spec/probability_spec.rb +188 -196
- data/spec/readme_spec.rb +28 -22
- data/spec/reroll_rule_spec.rb +15 -15
- metadata +16 -139
- data/.travis.yml +0 -11
data/spec/probability_spec.rb
CHANGED
|
@@ -5,35 +5,35 @@ require 'helpers'
|
|
|
5
5
|
describe GamesDice::Probabilities do
|
|
6
6
|
describe 'class methods' do
|
|
7
7
|
describe '#new' do
|
|
8
|
-
it '
|
|
9
|
-
pr =
|
|
10
|
-
expect(pr).to be_a
|
|
8
|
+
it 'create a new distribution from an array and offset' do
|
|
9
|
+
pr = described_class.new([1.0], 1)
|
|
10
|
+
expect(pr).to be_a described_class
|
|
11
11
|
expect(pr.to_h).to be_valid_distribution
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
it '
|
|
15
|
-
expect
|
|
16
|
-
expect
|
|
17
|
-
expect
|
|
18
|
-
expect
|
|
14
|
+
it 'raise an error if passed incorrect parameter types' do
|
|
15
|
+
expect { described_class.new([nil], 20) }.to raise_error TypeError
|
|
16
|
+
expect { described_class.new([0.3, nil, 0.5], 7) }.to raise_error TypeError
|
|
17
|
+
expect { described_class.new([0.3, 0.2, 0.5], {}) }.to raise_error TypeError
|
|
18
|
+
expect { described_class.new({ x: :y }, 17) }.to raise_error TypeError
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
it '
|
|
22
|
-
expect
|
|
23
|
-
expect
|
|
24
|
-
expect
|
|
25
|
-
expect
|
|
21
|
+
it 'raise an error if distribution is incomplete or inaccurate' do
|
|
22
|
+
expect { described_class.new([0.3, 0.2, 0.6], 3) }.to raise_error ArgumentError
|
|
23
|
+
expect { described_class.new([], 1) }.to raise_error ArgumentError
|
|
24
|
+
expect { described_class.new([0.9], 1) }.to raise_error ArgumentError
|
|
25
|
+
expect { described_class.new([-0.9, 0.2, 0.9], 1) }.to raise_error ArgumentError
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
describe '#for_fair_die' do
|
|
30
|
-
it '
|
|
31
|
-
pr2 =
|
|
32
|
-
expect(pr2).to be_a
|
|
30
|
+
it 'create a new distribution based on number of sides' do
|
|
31
|
+
pr2 = described_class.for_fair_die(2)
|
|
32
|
+
expect(pr2).to be_a described_class
|
|
33
33
|
expect(pr2.to_h).to eql({ 1 => 0.5, 2 => 0.5 })
|
|
34
34
|
(1..20).each do |sides|
|
|
35
|
-
pr =
|
|
36
|
-
expect(pr).to be_a
|
|
35
|
+
pr = described_class.for_fair_die(sides)
|
|
36
|
+
expect(pr).to be_a described_class
|
|
37
37
|
h = pr.to_h
|
|
38
38
|
expect(h).to be_valid_distribution
|
|
39
39
|
expect(h.keys.count).to eql sides
|
|
@@ -41,27 +41,27 @@ describe GamesDice::Probabilities do
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
it '
|
|
45
|
-
expect
|
|
44
|
+
it 'raise an error if number of sides is not an integer' do
|
|
45
|
+
expect { described_class.for_fair_die({}) }.to raise_error TypeError
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
it '
|
|
49
|
-
expect
|
|
50
|
-
expect
|
|
48
|
+
it 'raise an error if number of sides is too low or too high' do
|
|
49
|
+
expect { described_class.for_fair_die(0) }.to raise_error ArgumentError
|
|
50
|
+
expect { described_class.for_fair_die(1_000_001) }.to raise_error ArgumentError
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
describe '#add_distributions' do
|
|
55
|
-
it '
|
|
56
|
-
d4a =
|
|
57
|
-
d4b =
|
|
58
|
-
pr =
|
|
55
|
+
it 'combine two distributions to create a third one' do
|
|
56
|
+
d4a = described_class.new([1.0 / 4, 1.0 / 4, 1.0 / 4, 1.0 / 4], 1)
|
|
57
|
+
d4b = described_class.new([1.0 / 10, 2.0 / 10, 3.0 / 10, 4.0 / 10], 1)
|
|
58
|
+
pr = described_class.add_distributions(d4a, d4b)
|
|
59
59
|
expect(pr.to_h).to be_valid_distribution
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
it '
|
|
63
|
-
d6 =
|
|
64
|
-
pr =
|
|
62
|
+
it 'calculate a classic 2d6 distribution accurately' do
|
|
63
|
+
d6 = described_class.for_fair_die(6)
|
|
64
|
+
pr = described_class.add_distributions(d6, d6)
|
|
65
65
|
h = pr.to_h
|
|
66
66
|
expect(h).to be_valid_distribution
|
|
67
67
|
expect(h[2]).to be_within(1e-9).of 1.0 / 36
|
|
@@ -77,26 +77,26 @@ describe GamesDice::Probabilities do
|
|
|
77
77
|
expect(h[12]).to be_within(1e-9).of 1.0 / 36
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
it '
|
|
81
|
-
d10 =
|
|
82
|
-
expect
|
|
83
|
-
expect
|
|
84
|
-
expect
|
|
80
|
+
it 'raise an error if either parameter is not a GamesDice::Probabilities object' do
|
|
81
|
+
d10 = described_class.for_fair_die(10)
|
|
82
|
+
expect { described_class.add_distributions('', 6) }.to raise_error TypeError
|
|
83
|
+
expect { described_class.add_distributions(d10, 6) }.to raise_error TypeError
|
|
84
|
+
expect { described_class.add_distributions('', d10) }.to raise_error TypeError
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
describe '#add_distributions_mult' do
|
|
89
|
-
it '
|
|
90
|
-
d4a =
|
|
91
|
-
d4b =
|
|
92
|
-
pr =
|
|
89
|
+
it 'combine two multiplied distributions to create a third one' do
|
|
90
|
+
d4a = described_class.new([1.0 / 4, 1.0 / 4, 1.0 / 4, 1.0 / 4], 1)
|
|
91
|
+
d4b = described_class.new([1.0 / 10, 2.0 / 10, 3.0 / 10, 4.0 / 10], 1)
|
|
92
|
+
pr = described_class.add_distributions_mult(2, d4a, -1, d4b)
|
|
93
93
|
expect(pr.to_h).to be_valid_distribution
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
it "
|
|
97
|
-
d6 =
|
|
98
|
-
d4 =
|
|
99
|
-
pr =
|
|
96
|
+
it "calculate a distribution for '1d6 - 1d4' accurately" do
|
|
97
|
+
d6 = described_class.for_fair_die(6)
|
|
98
|
+
d4 = described_class.for_fair_die(4)
|
|
99
|
+
pr = described_class.add_distributions_mult(1, d6, -1, d4)
|
|
100
100
|
h = pr.to_h
|
|
101
101
|
expect(h).to be_valid_distribution
|
|
102
102
|
expect(h[-3]).to be_within(1e-9).of 1.0 / 24
|
|
@@ -110,10 +110,10 @@ describe GamesDice::Probabilities do
|
|
|
110
110
|
expect(h[5]).to be_within(1e-9).of 1.0 / 24
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
it '
|
|
114
|
-
da =
|
|
115
|
-
db =
|
|
116
|
-
pr =
|
|
113
|
+
it 'add asymmetric distributions accurately' do
|
|
114
|
+
da = described_class.new([0.7, 0.0, 0.3], 2)
|
|
115
|
+
db = described_class.new([0.5, 0.3, 0.2], 2)
|
|
116
|
+
pr = described_class.add_distributions_mult(1, da, 2, db)
|
|
117
117
|
h = pr.to_h
|
|
118
118
|
expect(h).to be_valid_distribution
|
|
119
119
|
expect(h[6]).to be_within(1e-9).of 0.7 * 0.5
|
|
@@ -122,70 +122,62 @@ describe GamesDice::Probabilities do
|
|
|
122
122
|
expect(h[12]).to be_within(1e-9).of 0.3 * 0.2
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
-
it '
|
|
126
|
-
d10 =
|
|
127
|
-
expect
|
|
128
|
-
expect
|
|
129
|
-
expect
|
|
125
|
+
it 'raise an error if passed incorrect objects for distributions' do
|
|
126
|
+
d10 = described_class.for_fair_die(10)
|
|
127
|
+
expect { described_class.add_distributions_mult(1, '', -1, 6) }.to raise_error TypeError
|
|
128
|
+
expect { described_class.add_distributions_mult(2, d10, 3, 6) }.to raise_error TypeError
|
|
129
|
+
expect { described_class.add_distributions_mult(1, '', -1, d10) }.to raise_error TypeError
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
-
it '
|
|
133
|
-
d10 =
|
|
134
|
-
expect
|
|
135
|
-
expect
|
|
136
|
-
expect
|
|
132
|
+
it 'raise an error if passed incorrect objects for multipliers' do
|
|
133
|
+
d10 = described_class.for_fair_die(10)
|
|
134
|
+
expect { described_class.add_distributions_mult({}, d10, [], d10) }.to raise_error TypeError
|
|
135
|
+
expect { described_class.add_distributions_mult([7], d10, 3, d10) }.to raise_error TypeError
|
|
136
|
+
expect { described_class.add_distributions_mult(1, d10, {}, d10) }.to raise_error TypeError
|
|
137
137
|
end
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
describe '#from_h' do
|
|
141
|
-
it '
|
|
142
|
-
pr =
|
|
143
|
-
expect(pr).to be_a
|
|
141
|
+
it 'create a Probabilities object from a valid hash' do
|
|
142
|
+
pr = described_class.from_h({ 7 => 0.5, 9 => 0.5 })
|
|
143
|
+
expect(pr).to be_a described_class
|
|
144
144
|
end
|
|
145
145
|
|
|
146
|
-
it '
|
|
147
|
-
expect
|
|
146
|
+
it 'raise an ArgumentError when called with a non-valid hash' do
|
|
147
|
+
expect { described_class.from_h({ 7 => 0.5, 9 => 0.6 }) }.to raise_error ArgumentError
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
-
it '
|
|
151
|
-
expect
|
|
150
|
+
it 'raise an TypeError when called with data that is not a hash' do
|
|
151
|
+
expect { described_class.from_h(:foo) }.to raise_error TypeError
|
|
152
152
|
end
|
|
153
153
|
|
|
154
|
-
it '
|
|
155
|
-
expect
|
|
156
|
-
expect
|
|
154
|
+
it 'raise a TypeError when called when keys and values are not all integers and floats' do
|
|
155
|
+
expect { described_class.from_h({ 'x' => 0.5, 9 => 0.5 }) }.to raise_error TypeError
|
|
156
|
+
expect { described_class.from_h({ 7 => [], 9 => 0.5 }) }.to raise_error TypeError
|
|
157
157
|
end
|
|
158
158
|
|
|
159
|
-
it '
|
|
160
|
-
expect
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
describe '#implemented_in' do
|
|
165
|
-
it 'should be either :c or :ruby' do
|
|
166
|
-
lang = GamesDice::Probabilities.implemented_in
|
|
167
|
-
expect(lang).to be_a Symbol
|
|
168
|
-
expect(%i[c ruby].member?(lang)).to eql true
|
|
159
|
+
it 'raise an ArgumentError when results are spread very far apart' do
|
|
160
|
+
expect { described_class.from_h({ 0 => 0.5, 2_000_000 => 0.5 }) }.to raise_error ArgumentError
|
|
169
161
|
end
|
|
170
162
|
end
|
|
171
163
|
end
|
|
172
164
|
|
|
173
165
|
describe 'instance methods' do
|
|
174
|
-
let(:pr2) {
|
|
175
|
-
let(:pr4) {
|
|
176
|
-
let(:pr6) {
|
|
177
|
-
let(:pr10) {
|
|
178
|
-
let(:pra) {
|
|
166
|
+
let(:pr2) { described_class.for_fair_die(2) }
|
|
167
|
+
let(:pr4) { described_class.for_fair_die(4) }
|
|
168
|
+
let(:pr6) { described_class.for_fair_die(6) }
|
|
169
|
+
let(:pr10) { described_class.for_fair_die(10) }
|
|
170
|
+
let(:pra) { described_class.new([0.4, 0.2, 0.4], -1) }
|
|
179
171
|
|
|
180
172
|
describe '#each' do
|
|
181
|
-
it '
|
|
173
|
+
it 'iterate through all result/probability pairs' do
|
|
182
174
|
yielded = []
|
|
183
175
|
pr4.each { |r, p| yielded << [r, p] }
|
|
184
176
|
expect(yielded).to eql [[1, 0.25], [2, 0.25], [3, 0.25], [4, 0.25]]
|
|
185
177
|
end
|
|
186
178
|
|
|
187
|
-
it '
|
|
188
|
-
pr_plus_minus =
|
|
179
|
+
it 'skip zero probabilities' do
|
|
180
|
+
pr_plus_minus = described_class.new([0.5, 0.0, 0.5], -1)
|
|
189
181
|
yielded = []
|
|
190
182
|
pr_plus_minus.each { |r, p| yielded << [r, p] }
|
|
191
183
|
expect(yielded).to eql [[-1, 0.5], [1, 0.5]]
|
|
@@ -193,7 +185,7 @@ describe GamesDice::Probabilities do
|
|
|
193
185
|
end
|
|
194
186
|
|
|
195
187
|
describe '#p_eql' do
|
|
196
|
-
it '
|
|
188
|
+
it 'return probability of getting a number inside the range' do
|
|
197
189
|
expect(pr2.p_eql(2)).to be_within(1.0e-9).of 0.5
|
|
198
190
|
expect(pr4.p_eql(1)).to be_within(1.0e-9).of 0.25
|
|
199
191
|
expect(pr6.p_eql(6)).to be_within(1.0e-9).of 1.0 / 6
|
|
@@ -201,21 +193,21 @@ describe GamesDice::Probabilities do
|
|
|
201
193
|
expect(pra.p_eql(-1)).to be_within(1.0e-9).of 0.4
|
|
202
194
|
end
|
|
203
195
|
|
|
204
|
-
it '
|
|
205
|
-
expect(pr2.p_eql(3)).to
|
|
206
|
-
expect(pr4.p_eql(-1)).to
|
|
207
|
-
expect(pr6.p_eql(8)).to
|
|
208
|
-
expect(pr10.p_eql(11)).to
|
|
209
|
-
expect(pra.p_eql(2)).to
|
|
196
|
+
it 'return 0.0 for values not covered by distribution' do
|
|
197
|
+
expect(pr2.p_eql(3)).to be 0.0
|
|
198
|
+
expect(pr4.p_eql(-1)).to be 0.0
|
|
199
|
+
expect(pr6.p_eql(8)).to be 0.0
|
|
200
|
+
expect(pr10.p_eql(11)).to be 0.0
|
|
201
|
+
expect(pra.p_eql(2)).to be 0.0
|
|
210
202
|
end
|
|
211
203
|
|
|
212
|
-
it '
|
|
213
|
-
expect
|
|
204
|
+
it 'raise a TypeError if asked for probability of non-Integer' do
|
|
205
|
+
expect { pr2.p_eql([]) }.to raise_error TypeError
|
|
214
206
|
end
|
|
215
207
|
end
|
|
216
208
|
|
|
217
209
|
describe '#p_gt' do
|
|
218
|
-
it '
|
|
210
|
+
it 'return probability of getting a number greater than target' do
|
|
219
211
|
expect(pr2.p_gt(1)).to be_within(1.0e-9).of 0.5
|
|
220
212
|
expect(pr4.p_gt(3)).to be_within(1.0e-9).of 0.25
|
|
221
213
|
expect(pr6.p_gt(2)).to be_within(1.0e-9).of 4.0 / 6
|
|
@@ -228,111 +220,111 @@ describe GamesDice::Probabilities do
|
|
|
228
220
|
expect(pra.p_gt(1)).to be_within(1.0e-9).of 0.0
|
|
229
221
|
end
|
|
230
222
|
|
|
231
|
-
it '
|
|
232
|
-
expect(pr2.p_gt(2)).to
|
|
233
|
-
expect(pr4.p_gt(5)).to
|
|
234
|
-
expect(pr6.p_gt(6)).to
|
|
235
|
-
expect(pr10.p_gt(20)).to
|
|
236
|
-
expect(pra.p_gt(3)).to
|
|
223
|
+
it 'return 0.0 when the target number is equal or higher than maximum possible' do
|
|
224
|
+
expect(pr2.p_gt(2)).to be 0.0
|
|
225
|
+
expect(pr4.p_gt(5)).to be 0.0
|
|
226
|
+
expect(pr6.p_gt(6)).to be 0.0
|
|
227
|
+
expect(pr10.p_gt(20)).to be 0.0
|
|
228
|
+
expect(pra.p_gt(3)).to be 0.0
|
|
237
229
|
end
|
|
238
230
|
|
|
239
|
-
it '
|
|
240
|
-
expect(pr2.p_gt(0)).to
|
|
241
|
-
expect(pr4.p_gt(-5)).to
|
|
242
|
-
expect(pr6.p_gt(0)).to
|
|
243
|
-
expect(pr10.p_gt(-200)).to
|
|
244
|
-
expect(pra.p_gt(-2)).to
|
|
231
|
+
it 'return 1.0 when the target number is lower than minimum' do
|
|
232
|
+
expect(pr2.p_gt(0)).to be 1.0
|
|
233
|
+
expect(pr4.p_gt(-5)).to be 1.0
|
|
234
|
+
expect(pr6.p_gt(0)).to be 1.0
|
|
235
|
+
expect(pr10.p_gt(-200)).to be 1.0
|
|
236
|
+
expect(pra.p_gt(-2)).to be 1.0
|
|
245
237
|
end
|
|
246
238
|
|
|
247
|
-
it '
|
|
248
|
-
expect
|
|
239
|
+
it 'raise a TypeError if asked for probability of non-Integer' do
|
|
240
|
+
expect { pr2.p_gt({}) }.to raise_error TypeError
|
|
249
241
|
end
|
|
250
242
|
end
|
|
251
243
|
|
|
252
244
|
describe '#p_ge' do
|
|
253
|
-
it '
|
|
245
|
+
it 'return probability of getting a number greater than or equal to target' do
|
|
254
246
|
expect(pr2.p_ge(2)).to be_within(1.0e-9).of 0.5
|
|
255
247
|
expect(pr4.p_ge(3)).to be_within(1.0e-9).of 0.5
|
|
256
248
|
expect(pr6.p_ge(2)).to be_within(1.0e-9).of 5.0 / 6
|
|
257
249
|
expect(pr10.p_ge(6)).to be_within(1.0e-9).of 0.5
|
|
258
250
|
end
|
|
259
251
|
|
|
260
|
-
it '
|
|
261
|
-
expect(pr2.p_ge(6)).to
|
|
262
|
-
expect(pr4.p_ge(5)).to
|
|
263
|
-
expect(pr6.p_ge(7)).to
|
|
264
|
-
expect(pr10.p_ge(20)).to
|
|
252
|
+
it 'return 0.0 when the target number is higher than maximum possible' do
|
|
253
|
+
expect(pr2.p_ge(6)).to be 0.0
|
|
254
|
+
expect(pr4.p_ge(5)).to be 0.0
|
|
255
|
+
expect(pr6.p_ge(7)).to be 0.0
|
|
256
|
+
expect(pr10.p_ge(20)).to be 0.0
|
|
265
257
|
end
|
|
266
258
|
|
|
267
|
-
it '
|
|
268
|
-
expect(pr2.p_ge(1)).to
|
|
269
|
-
expect(pr4.p_ge(-5)).to
|
|
270
|
-
expect(pr6.p_ge(1)).to
|
|
271
|
-
expect(pr10.p_ge(-200)).to
|
|
259
|
+
it 'return 1.0 when the target number is lower than or equal to minimum possible' do
|
|
260
|
+
expect(pr2.p_ge(1)).to be 1.0
|
|
261
|
+
expect(pr4.p_ge(-5)).to be 1.0
|
|
262
|
+
expect(pr6.p_ge(1)).to be 1.0
|
|
263
|
+
expect(pr10.p_ge(-200)).to be 1.0
|
|
272
264
|
end
|
|
273
265
|
|
|
274
|
-
it '
|
|
275
|
-
expect
|
|
266
|
+
it 'raise a TypeError if asked for probability of non-Integer' do
|
|
267
|
+
expect { pr4.p_ge({}) }.to raise_error TypeError
|
|
276
268
|
end
|
|
277
269
|
end
|
|
278
270
|
|
|
279
271
|
describe '#p_le' do
|
|
280
|
-
it '
|
|
272
|
+
it 'return probability of getting a number less than or equal to target' do
|
|
281
273
|
expect(pr2.p_le(1)).to be_within(1.0e-9).of 0.5
|
|
282
274
|
expect(pr4.p_le(2)).to be_within(1.0e-9).of 0.5
|
|
283
275
|
expect(pr6.p_le(2)).to be_within(1.0e-9).of 2.0 / 6
|
|
284
276
|
expect(pr10.p_le(6)).to be_within(1.0e-9).of 0.6
|
|
285
277
|
end
|
|
286
278
|
|
|
287
|
-
it '
|
|
288
|
-
expect(pr2.p_le(6)).to
|
|
289
|
-
expect(pr4.p_le(4)).to
|
|
290
|
-
expect(pr6.p_le(7)).to
|
|
291
|
-
expect(pr10.p_le(10)).to
|
|
279
|
+
it 'return 1.0 when the target number is higher than or equal to maximum possible' do
|
|
280
|
+
expect(pr2.p_le(6)).to be 1.0
|
|
281
|
+
expect(pr4.p_le(4)).to be 1.0
|
|
282
|
+
expect(pr6.p_le(7)).to be 1.0
|
|
283
|
+
expect(pr10.p_le(10)).to be 1.0
|
|
292
284
|
end
|
|
293
285
|
|
|
294
|
-
it '
|
|
295
|
-
expect(pr2.p_le(0)).to
|
|
296
|
-
expect(pr4.p_le(-5)).to
|
|
297
|
-
expect(pr6.p_le(0)).to
|
|
298
|
-
expect(pr10.p_le(-200)).to
|
|
286
|
+
it 'return 0.0 when the target number is lower than minimum possible' do
|
|
287
|
+
expect(pr2.p_le(0)).to be 0.0
|
|
288
|
+
expect(pr4.p_le(-5)).to be 0.0
|
|
289
|
+
expect(pr6.p_le(0)).to be 0.0
|
|
290
|
+
expect(pr10.p_le(-200)).to be 0.0
|
|
299
291
|
end
|
|
300
292
|
|
|
301
|
-
it '
|
|
302
|
-
expect
|
|
293
|
+
it 'raise a TypeError if asked for probability of non-Integer' do
|
|
294
|
+
expect { pr4.p_le([]) }.to raise_error TypeError
|
|
303
295
|
end
|
|
304
296
|
end
|
|
305
297
|
|
|
306
298
|
describe '#p_lt' do
|
|
307
|
-
it '
|
|
299
|
+
it 'return probability of getting a number less than target' do
|
|
308
300
|
expect(pr2.p_lt(2)).to be_within(1.0e-9).of 0.5
|
|
309
301
|
expect(pr4.p_lt(3)).to be_within(1.0e-9).of 0.5
|
|
310
302
|
expect(pr6.p_lt(2)).to be_within(1.0e-9).of 1 / 6.0
|
|
311
303
|
expect(pr10.p_lt(6)).to be_within(1.0e-9).of 0.5
|
|
312
304
|
end
|
|
313
305
|
|
|
314
|
-
it '
|
|
315
|
-
expect(pr2.p_lt(6)).to
|
|
316
|
-
expect(pr4.p_lt(5)).to
|
|
317
|
-
expect(pr6.p_lt(7)).to
|
|
318
|
-
expect(pr10.p_lt(20)).to
|
|
306
|
+
it 'return 1.0 when the target number is higher than maximum possible' do
|
|
307
|
+
expect(pr2.p_lt(6)).to be 1.0
|
|
308
|
+
expect(pr4.p_lt(5)).to be 1.0
|
|
309
|
+
expect(pr6.p_lt(7)).to be 1.0
|
|
310
|
+
expect(pr10.p_lt(20)).to be 1.0
|
|
319
311
|
end
|
|
320
312
|
|
|
321
|
-
it '
|
|
322
|
-
expect(pr2.p_lt(1)).to
|
|
323
|
-
expect(pr4.p_lt(-5)).to
|
|
324
|
-
expect(pr6.p_lt(1)).to
|
|
325
|
-
expect(pr10.p_lt(-200)).to
|
|
313
|
+
it 'return 0.0 when the target number is lower than or equal to minimum possible' do
|
|
314
|
+
expect(pr2.p_lt(1)).to be 0.0
|
|
315
|
+
expect(pr4.p_lt(-5)).to be 0.0
|
|
316
|
+
expect(pr6.p_lt(1)).to be 0.0
|
|
317
|
+
expect(pr10.p_lt(-200)).to be 0.0
|
|
326
318
|
end
|
|
327
319
|
|
|
328
|
-
it '
|
|
329
|
-
expect
|
|
320
|
+
it 'raise a TypeError if asked for probability of non-Integer' do
|
|
321
|
+
expect { pr6.p_lt({}) }.to raise_error TypeError
|
|
330
322
|
end
|
|
331
323
|
end
|
|
332
324
|
|
|
333
325
|
describe '#to_h' do
|
|
334
326
|
# This is used loads in other tests
|
|
335
|
-
it '
|
|
327
|
+
it 'represent a valid distribution with each integer result associated with its probability' do
|
|
336
328
|
expect(pr2.to_h).to be_valid_distribution
|
|
337
329
|
expect(pr4.to_h).to be_valid_distribution
|
|
338
330
|
expect(pr6.to_h).to be_valid_distribution
|
|
@@ -341,87 +333,87 @@ describe GamesDice::Probabilities do
|
|
|
341
333
|
end
|
|
342
334
|
|
|
343
335
|
describe '#min' do
|
|
344
|
-
it '
|
|
345
|
-
expect(pr2.min).to
|
|
346
|
-
expect(pr4.min).to
|
|
347
|
-
expect(pr6.min).to
|
|
348
|
-
expect(pr10.min).to
|
|
349
|
-
expect(
|
|
336
|
+
it 'return lowest possible result allowed by distribution' do
|
|
337
|
+
expect(pr2.min).to be 1
|
|
338
|
+
expect(pr4.min).to be 1
|
|
339
|
+
expect(pr6.min).to be 1
|
|
340
|
+
expect(pr10.min).to be 1
|
|
341
|
+
expect(described_class.add_distributions(pr6, pr10).min).to be 2
|
|
350
342
|
end
|
|
351
343
|
end
|
|
352
344
|
|
|
353
345
|
describe '#max' do
|
|
354
|
-
it '
|
|
355
|
-
expect(pr2.max).to
|
|
356
|
-
expect(pr4.max).to
|
|
357
|
-
expect(pr6.max).to
|
|
358
|
-
expect(pr10.max).to
|
|
359
|
-
expect(
|
|
346
|
+
it 'return highest possible result allowed by distribution' do
|
|
347
|
+
expect(pr2.max).to be 2
|
|
348
|
+
expect(pr4.max).to be 4
|
|
349
|
+
expect(pr6.max).to be 6
|
|
350
|
+
expect(pr10.max).to be 10
|
|
351
|
+
expect(described_class.add_distributions(pr6, pr10).max).to be 16
|
|
360
352
|
end
|
|
361
353
|
end
|
|
362
354
|
|
|
363
355
|
describe '#expected' do
|
|
364
|
-
it '
|
|
356
|
+
it 'return the weighted mean value' do
|
|
365
357
|
expect(pr2.expected).to be_within(1.0e-9).of 1.5
|
|
366
358
|
expect(pr4.expected).to be_within(1.0e-9).of 2.5
|
|
367
359
|
expect(pr6.expected).to be_within(1.0e-9).of 3.5
|
|
368
360
|
expect(pr10.expected).to be_within(1.0e-9).of 5.5
|
|
369
|
-
expect(
|
|
361
|
+
expect(described_class.add_distributions(pr6, pr10).expected).to be_within(1.0e-9).of 9.0
|
|
370
362
|
end
|
|
371
363
|
end
|
|
372
364
|
|
|
373
365
|
describe '#given_ge' do
|
|
374
|
-
it '
|
|
366
|
+
it 'return a new distribution with probabilities calculated assuming value is >= target' do
|
|
375
367
|
pd = pr2.given_ge(2)
|
|
376
368
|
expect(pd.to_h).to eql({ 2 => 1.0 })
|
|
377
369
|
pd = pr10.given_ge(4)
|
|
378
370
|
expect(pd.to_h).to be_valid_distribution
|
|
379
|
-
expect(pd.p_eql(3)).to
|
|
371
|
+
expect(pd.p_eql(3)).to be 0.0
|
|
380
372
|
expect(pd.p_eql(10)).to be_within(1.0e-9).of 0.1 / 0.7
|
|
381
373
|
end
|
|
382
374
|
|
|
383
|
-
it '
|
|
384
|
-
expect
|
|
375
|
+
it 'raise a TypeError if asked for probability of non-Integer' do
|
|
376
|
+
expect { pr10.given_ge([]) }.to raise_error TypeError
|
|
385
377
|
end
|
|
386
378
|
end
|
|
387
379
|
|
|
388
380
|
describe '#given_le' do
|
|
389
|
-
it '
|
|
381
|
+
it 'return a new distribution with probabilities calculated assuming value is <= target' do
|
|
390
382
|
pd = pr2.given_le(2)
|
|
391
383
|
expect(pd.to_h).to eql({ 1 => 0.5, 2 => 0.5 })
|
|
392
384
|
pd = pr10.given_le(4)
|
|
393
385
|
expect(pd.to_h).to be_valid_distribution
|
|
394
386
|
expect(pd.p_eql(3)).to be_within(1.0e-9).of 0.1 / 0.4
|
|
395
|
-
expect(pd.p_eql(10)).to
|
|
387
|
+
expect(pd.p_eql(10)).to be 0.0
|
|
396
388
|
end
|
|
397
389
|
|
|
398
|
-
it '
|
|
399
|
-
expect
|
|
390
|
+
it 'raise a TypeError if asked for probability of non-Integer' do
|
|
391
|
+
expect { pr10.given_le({}) }.to raise_error TypeError
|
|
400
392
|
end
|
|
401
393
|
end
|
|
402
394
|
|
|
403
395
|
describe '#repeat_sum' do
|
|
404
|
-
it '
|
|
405
|
-
d4a =
|
|
406
|
-
d4b =
|
|
396
|
+
it 'output a valid distribution if params are valid' do
|
|
397
|
+
d4a = described_class.new([1.0 / 4, 1.0 / 4, 1.0 / 4, 1.0 / 4], 1)
|
|
398
|
+
d4b = described_class.new([1.0 / 10, 2.0 / 10, 3.0 / 10, 4.0 / 10], 1)
|
|
407
399
|
pr = d4a.repeat_sum(7)
|
|
408
400
|
expect(pr.to_h).to be_valid_distribution
|
|
409
401
|
pr = d4b.repeat_sum(12)
|
|
410
402
|
expect(pr.to_h).to be_valid_distribution
|
|
411
403
|
end
|
|
412
404
|
|
|
413
|
-
it '
|
|
414
|
-
d6 =
|
|
415
|
-
expect
|
|
405
|
+
it 'raise an error if any param is unexpected type' do
|
|
406
|
+
d6 = described_class.for_fair_die(6)
|
|
407
|
+
expect { d6.repeat_sum({}) }.to raise_error TypeError
|
|
416
408
|
end
|
|
417
409
|
|
|
418
|
-
it '
|
|
419
|
-
d1000 =
|
|
420
|
-
expect
|
|
410
|
+
it 'raise an error if distribution would have more than a million results' do
|
|
411
|
+
d1000 = described_class.for_fair_die(1000)
|
|
412
|
+
expect { d1000.repeat_sum(11_000) }.to raise_error(RuntimeError, /Too many probability slots/)
|
|
421
413
|
end
|
|
422
414
|
|
|
423
|
-
it "
|
|
424
|
-
d6 =
|
|
415
|
+
it "calculate a '3d6' distribution accurately" do
|
|
416
|
+
d6 = described_class.for_fair_die(6)
|
|
425
417
|
pr = d6.repeat_sum(3)
|
|
426
418
|
h = pr.to_h
|
|
427
419
|
expect(h).to be_valid_distribution
|
|
@@ -445,28 +437,28 @@ describe GamesDice::Probabilities do
|
|
|
445
437
|
end
|
|
446
438
|
|
|
447
439
|
describe '#repeat_n_sum_k' do
|
|
448
|
-
it '
|
|
449
|
-
d4a =
|
|
450
|
-
d4b =
|
|
440
|
+
it 'output a valid distribution if params are valid' do
|
|
441
|
+
d4a = described_class.new([1.0 / 4, 1.0 / 4, 1.0 / 4, 1.0 / 4], 1)
|
|
442
|
+
d4b = described_class.new([1.0 / 10, 2.0 / 10, 3.0 / 10, 4.0 / 10], 1)
|
|
451
443
|
pr = d4a.repeat_n_sum_k(3, 2)
|
|
452
444
|
expect(pr.to_h).to be_valid_distribution
|
|
453
445
|
pr = d4b.repeat_n_sum_k(12, 4)
|
|
454
446
|
expect(pr.to_h).to be_valid_distribution
|
|
455
447
|
end
|
|
456
448
|
|
|
457
|
-
it '
|
|
458
|
-
d6 =
|
|
459
|
-
expect
|
|
460
|
-
expect
|
|
449
|
+
it 'raise an error if any param is unexpected type' do
|
|
450
|
+
d6 = described_class.for_fair_die(6)
|
|
451
|
+
expect { d6.repeat_n_sum_k({}, 10) }.to raise_error TypeError
|
|
452
|
+
expect { d6.repeat_n_sum_k(10, {}) }.to raise_error TypeError
|
|
461
453
|
end
|
|
462
454
|
|
|
463
|
-
it '
|
|
464
|
-
d6 =
|
|
465
|
-
expect
|
|
455
|
+
it 'raise an error if n is greater than 170' do
|
|
456
|
+
d6 = described_class.for_fair_die(6)
|
|
457
|
+
expect { d6.repeat_n_sum_k(171, 10) }.to raise_error(RuntimeError, /Too many dice/)
|
|
466
458
|
end
|
|
467
459
|
|
|
468
|
-
it "
|
|
469
|
-
d6 =
|
|
460
|
+
it "calculate a '4d6 keep best 3' distribution accurately" do
|
|
461
|
+
d6 = described_class.for_fair_die(6)
|
|
470
462
|
pr = d6.repeat_n_sum_k(4, 3)
|
|
471
463
|
h = pr.to_h
|
|
472
464
|
expect(h).to be_valid_distribution
|
|
@@ -488,8 +480,8 @@ describe GamesDice::Probabilities do
|
|
|
488
480
|
expect(h[18]).to be_within(1e-10).of 21 / 1296.0
|
|
489
481
|
end
|
|
490
482
|
|
|
491
|
-
it "
|
|
492
|
-
d20 =
|
|
483
|
+
it "calculate a '2d20 keep worst result' distribution accurately" do
|
|
484
|
+
d20 = described_class.for_fair_die(20)
|
|
493
485
|
pr = d20.repeat_n_sum_k(2, 1, :keep_worst)
|
|
494
486
|
h = pr.to_h
|
|
495
487
|
expect(h).to be_valid_distribution
|