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
data/spec/readme_spec.rb CHANGED
@@ -1,390 +1,410 @@
1
- require 'helpers'
2
- # This spec demonstrates that documentation from the README.md works as intended
3
-
4
- describe GamesDice do
5
-
6
- describe '#create' do
7
- it "converts a string such as '3d6+6' into a GamesDice::Dice object" do
8
- d = GamesDice.create '3d6+6'
9
- d.should be_a GamesDice::Dice
10
- end
11
-
12
- it "takes a parameter 'dice_description', which is a string such as '3d6' or '2d4-1'" do
13
- d = GamesDice.create '3d6'
14
- d.should be_a GamesDice::Dice
15
- d = GamesDice.create '2d4-1'
16
- d.should be_a GamesDice::Dice
17
- end
18
-
19
- it "takes an optional parameter 'prng', which if provided it should be an object that has a method 'rand( integer )'" do
20
- prng = TestPRNG.new
21
-
22
- d = GamesDice.create '3d6', prng
23
- d.should be_a GamesDice::Dice
24
-
25
- (0..5).each do |dresult|
26
- prng.stub( :rand ) { dresult }
27
- expect( prng ).to receive(:rand).with(6)
28
- d.roll.should == (dresult + 1) * 3
29
- end
30
- end
31
- end # describe '#create'
32
-
33
- end # describe GamesDice
34
-
35
- describe GamesDice::Dice do
36
-
37
- before :each do
38
- srand(67809)
39
- end
40
-
41
- let(:dice) { GamesDice.create '3d6'}
42
-
43
- describe "#roll" do
44
- it "simulates rolling the dice as they were described in the constructor" do
45
- expected_results = [11,15,11,12,12,9]
46
- expected_results.each do |expected|
47
- dice.roll.should == expected
48
- end
49
- end
50
- end
51
-
52
- describe "#result" do
53
- it "returns the value from the last call to roll" do
54
- expected_results = [11,15,11,12,12,9]
55
- expected_results.each do |expected|
56
- dice.roll
57
- dice.result.should == expected
58
- end
59
- end
60
-
61
- it "will be nil if no roll has been made yet" do
62
- dice.result.should be_nil
63
- end
64
- end
65
-
66
- describe "#explain_result" do
67
- it "attempts to show how the result from the last call to roll was composed" do
68
- expected_results = [
69
- "3d6: 3 + 6 + 2 = 11",
70
- "3d6: 4 + 5 + 6 = 15",
71
- "3d6: 3 + 6 + 2 = 11",
72
- "3d6: 5 + 6 + 1 = 12"
73
- ]
74
- expected_results.each do |expected|
75
- dice.roll
76
- dice.explain_result.should == expected
77
- end
78
- end
79
-
80
- it "will be nil if no roll has been made yet" do
81
- dice.explain_result.should be_nil
82
- end
83
- end
84
-
85
- describe "#max" do
86
- it "returns the maximum possible value from a roll of the dice" do
87
- dice.max.should == 18
88
- end
89
- end
90
-
91
- describe "#min" do
92
- it "returns the minimum possible value from a roll of the dice" do
93
- dice.min.should == 3
94
- end
95
- end
96
-
97
- describe "#minmax" do
98
- it "returns an array [ dice.min, dice.max ]" do
99
- dice.minmax.should == [3,18]
100
- end
101
- end
102
-
103
- describe "#probabilities" do
104
- it "calculates probability distribution for the dice" do
105
- pd = dice.probabilities
106
- pd.should be_a GamesDice::Probabilities
107
- pd.p_eql( 3).should be_within(1e-10).of 1.0/216
108
- pd.p_eql( 11 ).should be_within(1e-10).of 27.0/216
109
- end
110
- end
111
-
112
- end # describe GamesDice::Dice
113
-
114
- describe GamesDice::Probabilities do
115
- let(:probs) { GamesDice.create('3d6').probabilities }
116
-
117
- describe "#to_h" do
118
- it "returns a hash representation of the probability distribution" do
119
- h = probs.to_h
120
- h.should be_valid_distribution
121
- h[3].should be_within(1e-10).of 1.0/216
122
- h[11].should be_within(1e-10).of 27.0/216
123
- end
124
- end
125
-
126
- describe "#max" do
127
- it "returns maximum result in the probability distribution" do
128
- probs.max.should == 18
129
- end
130
- end
131
-
132
- describe "#min" do
133
- it "returns minimum result in the probability distribution" do
134
- probs.min.should == 3
135
- end
136
- end
137
-
138
- describe "#p_eql( n )" do
139
- it "returns the probability of a result equal to the integer n" do
140
- probs.p_eql( 3 ).should be_within(1e-10).of 1.0/216
141
- probs.p_eql( 2 ).should == 0.0
142
- end
143
- end
144
-
145
- describe "#p_gt( n )" do
146
- it "returns the probability of a result greater than the integer n" do
147
- probs.p_gt( 17 ).should be_within(1e-10).of 1.0/216
148
- probs.p_gt( 2 ).should == 1.0
149
- end
150
- end
151
-
152
- describe "#p_ge( n )" do
153
- it "returns the probability of a result greater than the integer n" do
154
- probs.p_ge( 17 ).should be_within(1e-10).of 4.0/216
155
- probs.p_ge( 3 ).should == 1.0
156
- end
157
- end
158
-
159
- describe "#p_le( n )" do
160
- it "returns the probability of a result less than or equal to the integer n" do
161
- probs.p_le( 17 ).should be_within(1e-10).of 215.0/216
162
- probs.p_le( 3 ).should be_within(1e-10).of 1.0/216
163
- end
164
- end
165
-
166
- describe "#p_lt( n )" do
167
- it "returns the probability of a result less than the integer n" do
168
- probs.p_lt( 17 ).should be_within(1e-10).of 212.0/216
169
- probs.p_lt( 3 ).should == 0.0
170
- end
171
- end
172
-
173
- end # describe GamesDice::Probabilities
174
-
175
- describe 'String Dice Description' do
176
-
177
- before :each do
178
- srand(35241)
179
- end
180
-
181
- describe "'1d6'" do
182
- it "returns expected results from rolling" do
183
- d = GamesDice.create '1d6'
184
- (1..20).map { |n| d.roll }.should == [6, 3, 2, 3, 4, 6, 4, 2, 6, 3, 3, 5, 6, 6, 3, 6, 5, 2, 1, 4]
185
- end
186
- end
187
-
188
- describe "'2d6 + 1d4'" do
189
- it "returns expected results from rolling" do
190
- d = GamesDice.create '2d6 + 1d4'
191
- (1..5).map { |n| d.roll }.should == [11, 10, 12, 12, 14]
192
- end
193
- end
194
-
195
- describe "'1d100 + 1d20 - 5'" do
196
- it "returns expected results from rolling" do
197
- d = GamesDice.create '1d100 + 1d20 - 5'
198
- (1..5).map { |n| d.roll }.should == [75, 78, 24, 102, 32]
199
- end
200
- end
201
-
202
- describe "'1d10x'" do
203
- it "returns expected results from rolling" do
204
- d = GamesDice.create '1d10x'
205
- (1..20).map { |n| d.roll }.should == [2, 3, 4, 7, 6, 7, 4, 2, 6, 3, 7, 5, 6, 7, 6, 6, 5, 19, 4, 19]
206
- end
207
- end
208
-
209
- describe "'1d6r1'" do
210
- it "returns expected results from rolling" do
211
- d = GamesDice.create '1d6r1'
212
- (1..20).map { |n| d.roll }.should == [6, 3, 2, 3, 4, 6, 4, 2, 6, 3, 3, 5, 6, 6, 3, 6, 5, 2, 4, 2]
213
- end
214
- end
215
-
216
- describe "'5d10r:10,add.k2'" do
217
- it "returns expected results from rolling" do
218
- d = GamesDice.create '5d10r:10,add.k2'
219
- (1..5).map { |n| d.roll }.should == [13, 13, 14, 38, 15]
220
- end
221
- end
222
-
223
- describe "'3d10m6'" do
224
- it "returns expected results from rolling" do
225
- d = GamesDice.create '3d10m6'
226
- (1..6).map { |n| d.roll }.should == [0, 3, 1, 1, 3, 2]
227
- end
228
- end
229
-
230
- describe "'5d10k2'" do
231
- it "returns expected results from rolling" do
232
- d = GamesDice.create '5d10k2'
233
- (1..5).map { |n| d.roll }.should == [13, 13, 14, 19, 19]
234
- end
235
- end
236
-
237
- describe "'5d10x'" do
238
- it "is the same as '5d10r:10,add.'" do
239
- srand(235241)
240
- d = GamesDice.create '5d10x'
241
- results1 = (1..50).map { d.roll }
242
-
243
- srand(235241)
244
- d = GamesDice.create '5d10r:10,add.'
245
- results2 = (1..50).map { d.roll }
246
-
247
- results1.should == results2
248
- end
249
- end
250
-
251
- describe "'1d6r:1.'" do
252
- it "should return same as '1d6r1'" do
253
- srand(235241)
254
- d = GamesDice.create '1d6r:1.'
255
- results1 = (1..50).map { d.roll }
256
-
257
- srand(235241)
258
- d = GamesDice.create '1d6r1'
259
- results2 = (1..50).map { d.roll }
260
-
261
- results1.should == results2
262
- end
263
- end
264
-
265
- describe "'1d10r:10,replace,1.'" do
266
- it "should roll a 10-sided die, re-roll a result of 10 and take the value of the second roll" do
267
- d = GamesDice.create '1d10r:10,replace,1.'
268
- (1..27).map { d.roll }.should == [2, 3, 4, 7, 6, 7, 4, 2, 6, 3, 7, 5, 6, 7, 6, 6, 5, 9, 4, 9, 8, 3, 1, 6, 7, 1, 1]
269
- end
270
- end
271
-
272
- describe "'1d20r:<=10,use_best,1.'" do
273
- it "should roll a 20-sided die, re-roll a result if 10 or lower, and use best result" do
274
- d = GamesDice.create '1d20r:<=10,use_best,1.'
275
- (1..20).map { d.roll }.should == [ 18, 19, 20, 20, 3, 11, 7, 20, 15, 19, 6, 16, 17, 16, 15, 11, 9, 15, 20, 16 ]
276
- end
277
- end
278
-
279
- describe "'5d10r:10,add.k2', '5d10xk2' and '5d10x.k2'" do
280
- it "should all be equivalent" do
281
- srand(135241)
282
- d = GamesDice.create '5d10r:10,add.k2'
283
- results1 = (1..50).map { d.roll }
284
-
285
- srand(135241)
286
- d = GamesDice.create '5d10xk2'
287
- results2 = (1..50).map { d.roll }
288
-
289
- srand(135241)
290
- d = GamesDice.create '5d10x.k2'
291
- results3 = (1..50).map { d.roll }
292
-
293
- results1.should == results2
294
- results2.should == results3
295
- end
296
- end
297
-
298
- describe "'5d10r:>8,add.'" do
299
- it "returns expected results from rolling" do
300
- d = GamesDice.create '5d10r:>8,add.'
301
- (1..5).map { |n| d.roll }.should == [22, 22, 31, 64, 26]
302
- end
303
- end
304
-
305
- describe "'9d6x.m:10.'" do
306
- it "returns expected results from rolling" do
307
- d = GamesDice.create '9d6x.m:10.'
308
- (1..5).map { |n| d.roll }.should == [1, 2, 1, 1, 1]
309
- end
310
- it "can be explained as number of exploding dice scoring 10+" do
311
- d = GamesDice.create '9d6x.m:10.'
312
- (1..5).map { |n| d.roll; d.explain_result }.should == [
313
- "9d6: [6+3] 9, 2, 3, 4, [6+4] 10, 2, [6+3] 9, 3, 5. Successes: 1",
314
- "9d6: [6+6+3] 15, [6+5] 11, 2, 1, 4, 2, 1, 3, 5. Successes: 2",
315
- "9d6: 1, [6+6+1] 13, 2, 1, 1, 3, [6+1] 7, 5, 4. Successes: 1",
316
- "9d6: [6+4] 10, 3, 4, 5, 5, 1, [6+3] 9, 3, 5. Successes: 1",
317
- "9d6: [6+3] 9, 3, [6+5] 11, 4, 2, 2, 1, 4, 5. Successes: 1"
318
- ]
319
- end
320
- end
321
-
322
- describe "'9d6x.m:10,1,S.'" do
323
- it "returns expected results from rolling" do
324
- d = GamesDice.create '9d6x.m:10,1,S.'
325
- (1..5).map { |n| d.roll }.should == [1, 2, 1, 1, 1]
326
- end
327
- it "includes the string 'S' next to each success" do
328
- d = GamesDice.create '9d6x.m:10,1,S.'
329
- (1..5).map { |n| d.roll; d.explain_result }.should == [
330
- "9d6: [6+3] 9, 2, 3, 4, [6+4] 10 S, 2, [6+3] 9, 3, 5. Successes: 1",
331
- "9d6: [6+6+3] 15 S, [6+5] 11 S, 2, 1, 4, 2, 1, 3, 5. Successes: 2",
332
- "9d6: 1, [6+6+1] 13 S, 2, 1, 1, 3, [6+1] 7, 5, 4. Successes: 1",
333
- "9d6: [6+4] 10 S, 3, 4, 5, 5, 1, [6+3] 9, 3, 5. Successes: 1",
334
- "9d6: [6+3] 9, 3, [6+5] 11 S, 4, 2, 2, 1, 4, 5. Successes: 1"
335
- ]
336
- end
337
- end
338
-
339
- describe "'5d10m:>=6,1,S.m:==1,-1,F.'" do
340
- it "returns expected results from rolling" do
341
- d = GamesDice.create '5d10m:>=6,1,S.m:==1,-1,F.'
342
- (1..10).map { |n| d.roll }.should == [2, 2, 4, 3, 2, 1, 1, 3, 3, 0]
343
- end
344
- it "includes the string 'S' next to each success, and 'F' next to each 'fumble'" do
345
- d = GamesDice.create '5d10m:>=6,1,S.m:==1,-1,F.'
346
- (1..5).map { |n| d.roll; d.explain_result }.should == [
347
- "5d10: 2, 3, 4, 7 S, 6 S. Successes: 2",
348
- "5d10: 7 S, 4, 2, 6 S, 3. Successes: 2",
349
- "5d10: 7 S, 5, 6 S, 7 S, 6 S. Successes: 4",
350
- "5d10: 6 S, 5, 10 S, 9 S, 4. Successes: 3",
351
- "5d10: 10 S, 9 S, 8 S, 3, 1 F. Successes: 2"
352
- ]
353
- end
354
- end
355
-
356
- describe "'4d6k:3.r:1,replace,1.'" do
357
- it "represents roll 4 six-sided dice, re-roll any 1s, and keep best 3." do
358
- d = GamesDice.create '4d6k:3.r:1,replace,1.'
359
- (1..10).map { |n| d.roll }.should == [12, 14, 14, 18, 11, 17, 11, 15, 14, 14]
360
- end
361
- it "includes re-rolls and keeper choice in explanations" do
362
- d = GamesDice.create '4d6k:3.r:1,replace,1.'
363
- (1..5).map { |n| d.roll; d.explain_result }.should == [
364
- "4d6: 6, 3, 2, 3. Keep: 3 + 3 + 6 = 12",
365
- "4d6: 4, 6, 4, 2. Keep: 4 + 4 + 6 = 14",
366
- "4d6: 6, 3, 3, 5. Keep: 3 + 5 + 6 = 14",
367
- "4d6: 6, 6, 3, 6. Keep: 6 + 6 + 6 = 18",
368
- "4d6: 5, 2, [1|4] 4, 2. Keep: 2 + 4 + 5 = 11"
369
- ]
370
- end
371
- end
372
-
373
- describe "'2d20k:1,worst.'" do
374
- it "represents roll 2 twenty-sided dice, return lowest of the two results" do
375
- d = GamesDice.create '2d20k:1,worst.'
376
- (1..10).map { |n| d.roll }.should == [18, 6, 2, 3, 5, 10, 15, 1, 7, 10]
377
- end
378
- it "includes keeper choice in explanations" do
379
- d = GamesDice.create '2d20k:1,worst.'
380
- (1..5).map { |n| d.roll; d.explain_result }.should == [
381
- "2d20: 18, 19. Keep: 18",
382
- "2d20: 20, 6. Keep: 6",
383
- "2d20: 20, 2. Keep: 2",
384
- "2d20: 3, 11. Keep: 3",
385
- "2d20: 5, 7. Keep: 5"
386
- ]
387
- end
388
- end
389
-
390
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'helpers'
4
+ # This spec demonstrates that documentation from the README.md works as intended
5
+
6
+ describe GamesDice do
7
+ describe '#create' do
8
+ it "converts a string such as '3d6+6' into a GamesDice::Dice object" do
9
+ d = described_class.create '3d6+6'
10
+ expect(d).to be_a GamesDice::Dice
11
+ end
12
+
13
+ it "takes a parameter 'dice_description', which is a string such as '3d6' or '2d4-1'" do
14
+ d = described_class.create '3d6'
15
+ expect(d).to be_a GamesDice::Dice
16
+ d = described_class.create '2d4-1'
17
+ expect(d).to be_a GamesDice::Dice
18
+ end
19
+
20
+ it "takes an optional parameter 'prng', which should be an object that has a method 'rand( integer )'" do
21
+ prng = TestPRNG.new
22
+
23
+ d = described_class.create '3d6', prng
24
+ expect(d).to be_a GamesDice::Dice
25
+
26
+ (0..5).each do |dresult|
27
+ allow(prng).to receive(:rand).and_return(dresult)
28
+ result = d.roll
29
+ expect(prng).to have_received(:rand).with(6).at_least(:once)
30
+ expect(result).to eql (dresult + 1) * 3
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ describe GamesDice::Dice do
37
+ before do
38
+ srand(67_809)
39
+ end
40
+
41
+ let(:dice) { GamesDice.create '3d6' }
42
+
43
+ describe '#roll' do
44
+ it 'simulates rolling the dice as they were described in the constructor' do
45
+ expected_results = [11, 15, 11, 12, 12, 9]
46
+ expected_results.each do |expected|
47
+ expect(dice.roll).to eql expected
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '#result' do
53
+ it 'returns the value from the last call to roll' do
54
+ expected_results = [11, 15, 11, 12, 12, 9]
55
+ expected_results.each do |expected|
56
+ dice.roll
57
+ expect(dice.result).to eql expected
58
+ end
59
+ end
60
+
61
+ it 'is nil if no roll has been made yet' do
62
+ expect(dice.result).to be_nil
63
+ end
64
+ end
65
+
66
+ describe '#explain_result' do
67
+ it 'attempts to show how the result from the last call to roll was composed' do
68
+ expected_results = [
69
+ '3d6: 3 + 6 + 2 = 11',
70
+ '3d6: 4 + 5 + 6 = 15',
71
+ '3d6: 3 + 6 + 2 = 11',
72
+ '3d6: 5 + 6 + 1 = 12'
73
+ ]
74
+ expected_results.each do |expected|
75
+ dice.roll
76
+ expect(dice.explain_result).to eql expected
77
+ end
78
+ end
79
+
80
+ it 'is nil if no roll has been made yet' do
81
+ expect(dice.explain_result).to be_nil
82
+ end
83
+ end
84
+
85
+ describe '#max' do
86
+ it 'returns the maximum possible value from a roll of the dice' do
87
+ expect(dice.max).to be 18
88
+ end
89
+ end
90
+
91
+ describe '#min' do
92
+ it 'returns the minimum possible value from a roll of the dice' do
93
+ expect(dice.min).to be 3
94
+ end
95
+ end
96
+
97
+ describe '#minmax' do
98
+ it 'returns an array [ dice.min, dice.max ]' do
99
+ expect(dice.minmax).to eql [3, 18]
100
+ end
101
+ end
102
+
103
+ describe '#probabilities' do
104
+ it 'calculates probability distribution for the dice' do
105
+ pd = dice.probabilities
106
+ expect(pd).to be_a GamesDice::Probabilities
107
+ expect(pd.p_eql(3)).to be_within(1e-10).of 1.0 / 216
108
+ expect(pd.p_eql(11)).to be_within(1e-10).of 27.0 / 216
109
+ end
110
+ end
111
+ end
112
+
113
+ describe GamesDice::Probabilities do
114
+ let(:probs) { GamesDice.create('3d6').probabilities }
115
+
116
+ describe '#to_h' do
117
+ it 'returns a hash representation of the probability distribution' do
118
+ h = probs.to_h
119
+ expect(h).to be_valid_distribution
120
+ expect(h[3]).to be_within(1e-10).of 1.0 / 216
121
+ expect(h[11]).to be_within(1e-10).of 27.0 / 216
122
+ end
123
+ end
124
+
125
+ describe '#max' do
126
+ it 'returns maximum result in the probability distribution' do
127
+ expect(probs.max).to be 18
128
+ end
129
+ end
130
+
131
+ describe '#min' do
132
+ it 'returns minimum result in the probability distribution' do
133
+ expect(probs.min).to be 3
134
+ end
135
+ end
136
+
137
+ describe '#p_eql( n )' do
138
+ it 'returns the probability of a result equal to the integer n' do
139
+ expect(probs.p_eql(3)).to be_within(1e-10).of 1.0 / 216
140
+ expect(probs.p_eql(2)).to be 0.0
141
+ end
142
+ end
143
+
144
+ describe '#p_gt( n )' do
145
+ it 'returns the probability of a result greater than the integer n' do
146
+ expect(probs.p_gt(17)).to be_within(1e-10).of 1.0 / 216
147
+ expect(probs.p_gt(2)).to be 1.0
148
+ end
149
+ end
150
+
151
+ describe '#p_ge( n )' do
152
+ it 'returns the probability of a result greater than the integer n' do
153
+ expect(probs.p_ge(17)).to be_within(1e-10).of 4.0 / 216
154
+ expect(probs.p_ge(3)).to be 1.0
155
+ end
156
+ end
157
+
158
+ describe '#p_le( n )' do
159
+ it 'returns the probability of a result less than or equal to the integer n' do
160
+ expect(probs.p_le(17)).to be_within(1e-10).of 215.0 / 216
161
+ expect(probs.p_le(3)).to be_within(1e-10).of 1.0 / 216
162
+ end
163
+ end
164
+
165
+ describe '#p_lt( n )' do
166
+ it 'returns the probability of a result less than the integer n' do
167
+ expect(probs.p_lt(17)).to be_within(1e-10).of 212.0 / 216
168
+ expect(probs.p_lt(3)).to be 0.0
169
+ end
170
+ end
171
+ end
172
+
173
+ describe 'String Dice Description' do
174
+ before do
175
+ srand(35_241)
176
+ end
177
+
178
+ describe "'1d6'" do
179
+ it 'returns expected results from rolling' do
180
+ d = GamesDice.create '1d6'
181
+ expect((1..20).map { |_n| d.roll }).to eql [6, 3, 2, 3, 4, 6, 4, 2, 6, 3, 3, 5, 6, 6, 3, 6, 5, 2, 1, 4]
182
+ end
183
+ end
184
+
185
+ describe "'2d6 + 1d4'" do
186
+ it 'returns expected results from rolling' do
187
+ d = GamesDice.create '2d6 + 1d4'
188
+ expect((1..5).map { |_n| d.roll }).to eql [11, 10, 12, 12, 14]
189
+ end
190
+ end
191
+
192
+ describe "'1d100 + 1d20 - 5'" do
193
+ it 'returns expected results from rolling' do
194
+ d = GamesDice.create '1d100 + 1d20 - 5'
195
+ expect((1..5).map { |_n| d.roll }).to eql [75, 78, 24, 102, 32]
196
+ end
197
+ end
198
+
199
+ describe "'1d10x'" do
200
+ it 'returns expected results from rolling' do
201
+ d = GamesDice.create '1d10x'
202
+ expect((1..20).map { |_n| d.roll }).to eql [2, 3, 4, 7, 6, 7, 4, 2, 6, 3, 7, 5, 6, 7, 6, 6, 5, 19, 4, 19]
203
+ end
204
+ end
205
+
206
+ describe "'1d6r1'" do
207
+ it 'returns expected results from rolling' do
208
+ d = GamesDice.create '1d6r1'
209
+ expect((1..20).map { |_n| d.roll }).to eql [6, 3, 2, 3, 4, 6, 4, 2, 6, 3, 3, 5, 6, 6, 3, 6, 5, 2, 4, 2]
210
+ end
211
+ end
212
+
213
+ describe "'5d10r:10,add.k2'" do
214
+ it 'returns expected results from rolling' do
215
+ d = GamesDice.create '5d10r:10,add.k2'
216
+ expect((1..5).map { |_n| d.roll }).to eql [13, 13, 14, 38, 15]
217
+ end
218
+ end
219
+
220
+ describe "'3d10m6'" do
221
+ it 'returns expected results from rolling' do
222
+ d = GamesDice.create '3d10m6'
223
+ expect((1..6).map { |_n| d.roll }).to eql [0, 3, 1, 1, 3, 2]
224
+ end
225
+ end
226
+
227
+ describe "'5d10k2'" do
228
+ it 'returns expected results from rolling' do
229
+ d = GamesDice.create '5d10k2'
230
+ expect((1..5).map { |_n| d.roll }).to eql [13, 13, 14, 19, 19]
231
+ end
232
+ end
233
+
234
+ describe "'5d10x'" do
235
+ it "is the same as '5d10r:10,add.'" do
236
+ srand(235_241)
237
+ d = GamesDice.create '5d10x'
238
+ results1 = (1..50).map { d.roll }
239
+
240
+ srand(235_241)
241
+ d = GamesDice.create '5d10r:10,add.'
242
+ results2 = (1..50).map { d.roll }
243
+
244
+ expect(results1).to eql results2
245
+ end
246
+ end
247
+
248
+ describe "'1d6r:1.'" do
249
+ it "return same as '1d6r1'" do
250
+ srand(235_241)
251
+ d = GamesDice.create '1d6r:1.'
252
+ results1 = (1..50).map { d.roll }
253
+
254
+ srand(235_241)
255
+ d = GamesDice.create '1d6r1'
256
+ results2 = (1..50).map { d.roll }
257
+
258
+ expect(results1).to eql results2
259
+ end
260
+ end
261
+
262
+ describe "'1d10r:10,replace,1.'" do
263
+ it 'roll a 10-sided die, re-roll a result of 10 and take the value of the second roll' do
264
+ d = GamesDice.create '1d10r:10,replace,1.'
265
+ expect((1..27).map do
266
+ d.roll
267
+ end).to eql [2, 3, 4, 7, 6, 7, 4, 2, 6, 3, 7, 5, 6, 7, 6, 6, 5, 9, 4, 9, 8, 3, 1, 6, 7, 1, 1]
268
+ end
269
+ end
270
+
271
+ describe "'1d20r:<=10,use_best,1.'" do
272
+ it 'roll a 20-sided die, re-roll a result if 10 or lower, and use best result' do
273
+ d = GamesDice.create '1d20r:<=10,use_best,1.'
274
+ expect((1..20).map do
275
+ d.roll
276
+ end).to eql [18, 19, 20, 20, 3, 11, 7, 20, 15, 19, 6, 16, 17, 16, 15, 11, 9, 15, 20, 16]
277
+ end
278
+ end
279
+
280
+ describe "'5d10r:10,add.k2', '5d10xk2' and '5d10x.k2'" do
281
+ it 'all be equivalent' do
282
+ srand(135_241)
283
+ d = GamesDice.create '5d10r:10,add.k2'
284
+ results1 = (1..50).map { d.roll }
285
+
286
+ srand(135_241)
287
+ d = GamesDice.create '5d10xk2'
288
+ results2 = (1..50).map { d.roll }
289
+
290
+ srand(135_241)
291
+ d = GamesDice.create '5d10x.k2'
292
+ results3 = (1..50).map { d.roll }
293
+
294
+ expect(results1).to eql results2
295
+ expect(results1).to eql results3
296
+ end
297
+ end
298
+
299
+ describe "'5d10r:>8,add.'" do
300
+ it 'returns expected results from rolling' do
301
+ d = GamesDice.create '5d10r:>8,add.'
302
+ expect((1..5).map { |_n| d.roll }).to eql [22, 22, 31, 64, 26]
303
+ end
304
+ end
305
+
306
+ describe "'9d6x.m:10.'" do
307
+ it 'returns expected results from rolling' do
308
+ d = GamesDice.create '9d6x.m:10.'
309
+ expect((1..5).map { |_n| d.roll }).to eql [1, 2, 1, 1, 1]
310
+ end
311
+
312
+ it 'can be explained as number of exploding dice scoring 10+' do
313
+ d = GamesDice.create '9d6x.m:10.'
314
+ expect((1..5).map do |_n|
315
+ d.roll
316
+ d.explain_result
317
+ end).to eql [
318
+ '9d6: [6+3] 9, 2, 3, 4, [6+4] 10, 2, [6+3] 9, 3, 5. Successes: 1',
319
+ '9d6: [6+6+3] 15, [6+5] 11, 2, 1, 4, 2, 1, 3, 5. Successes: 2',
320
+ '9d6: 1, [6+6+1] 13, 2, 1, 1, 3, [6+1] 7, 5, 4. Successes: 1',
321
+ '9d6: [6+4] 10, 3, 4, 5, 5, 1, [6+3] 9, 3, 5. Successes: 1',
322
+ '9d6: [6+3] 9, 3, [6+5] 11, 4, 2, 2, 1, 4, 5. Successes: 1'
323
+ ]
324
+ end
325
+ end
326
+
327
+ describe "'9d6x.m:10,1,S.'" do
328
+ it 'returns expected results from rolling' do
329
+ d = GamesDice.create '9d6x.m:10,1,S.'
330
+ expect((1..5).map { |_n| d.roll }).to eql [1, 2, 1, 1, 1]
331
+ end
332
+
333
+ it "includes the string 'S' next to each success" do
334
+ d = GamesDice.create '9d6x.m:10,1,S.'
335
+ expect((1..5).map do |_n|
336
+ d.roll
337
+ d.explain_result
338
+ end).to eql [
339
+ '9d6: [6+3] 9, 2, 3, 4, [6+4] 10 S, 2, [6+3] 9, 3, 5. Successes: 1',
340
+ '9d6: [6+6+3] 15 S, [6+5] 11 S, 2, 1, 4, 2, 1, 3, 5. Successes: 2',
341
+ '9d6: 1, [6+6+1] 13 S, 2, 1, 1, 3, [6+1] 7, 5, 4. Successes: 1',
342
+ '9d6: [6+4] 10 S, 3, 4, 5, 5, 1, [6+3] 9, 3, 5. Successes: 1',
343
+ '9d6: [6+3] 9, 3, [6+5] 11 S, 4, 2, 2, 1, 4, 5. Successes: 1'
344
+ ]
345
+ end
346
+ end
347
+
348
+ describe "'5d10m:>=6,1,S.m:==1,-1,F.'" do
349
+ it 'returns expected results from rolling' do
350
+ d = GamesDice.create '5d10m:>=6,1,S.m:==1,-1,F.'
351
+ expect((1..10).map { |_n| d.roll }).to eql [2, 2, 4, 3, 2, 1, 1, 3, 3, 0]
352
+ end
353
+
354
+ it "includes the string 'S' next to each success, and 'F' next to each 'fumble'" do
355
+ d = GamesDice.create '5d10m:>=6,1,S.m:==1,-1,F.'
356
+ expect((1..5).map do |_n|
357
+ d.roll
358
+ d.explain_result
359
+ end).to eql [
360
+ '5d10: 2, 3, 4, 7 S, 6 S. Successes: 2',
361
+ '5d10: 7 S, 4, 2, 6 S, 3. Successes: 2',
362
+ '5d10: 7 S, 5, 6 S, 7 S, 6 S. Successes: 4',
363
+ '5d10: 6 S, 5, 10 S, 9 S, 4. Successes: 3',
364
+ '5d10: 10 S, 9 S, 8 S, 3, 1 F. Successes: 2'
365
+ ]
366
+ end
367
+ end
368
+
369
+ describe "'4d6k:3.r:1,replace,1.'" do
370
+ it 'represents roll 4 six-sided dice, re-roll any 1s, and keep best 3.' do
371
+ d = GamesDice.create '4d6k:3.r:1,replace,1.'
372
+ expect((1..10).map { |_n| d.roll }).to eql [12, 14, 14, 18, 11, 17, 11, 15, 14, 14]
373
+ end
374
+
375
+ it 'includes re-rolls and keeper choice in explanations' do
376
+ d = GamesDice.create '4d6k:3.r:1,replace,1.'
377
+ expect((1..5).map do |_n|
378
+ d.roll
379
+ d.explain_result
380
+ end).to eql [
381
+ '4d6: 6, 3, 2, 3. Keep: 3 + 3 + 6 = 12',
382
+ '4d6: 4, 6, 4, 2. Keep: 4 + 4 + 6 = 14',
383
+ '4d6: 6, 3, 3, 5. Keep: 3 + 5 + 6 = 14',
384
+ '4d6: 6, 6, 3, 6. Keep: 6 + 6 + 6 = 18',
385
+ '4d6: 5, 2, [1|4] 4, 2. Keep: 2 + 4 + 5 = 11'
386
+ ]
387
+ end
388
+ end
389
+
390
+ describe "'2d20k:1,worst.'" do
391
+ it 'represents roll 2 twenty-sided dice, return lowest of the two results' do
392
+ d = GamesDice.create '2d20k:1,worst.'
393
+ expect((1..10).map { |_n| d.roll }).to eql [18, 6, 2, 3, 5, 10, 15, 1, 7, 10]
394
+ end
395
+
396
+ it 'includes keeper choice in explanations' do
397
+ d = GamesDice.create '2d20k:1,worst.'
398
+ expect((1..5).map do |_n|
399
+ d.roll
400
+ d.explain_result
401
+ end).to eql [
402
+ '2d20: 18, 19. Keep: 18',
403
+ '2d20: 20, 6. Keep: 6',
404
+ '2d20: 20, 2. Keep: 2',
405
+ '2d20: 3, 11. Keep: 3',
406
+ '2d20: 5, 7. Keep: 5'
407
+ ]
408
+ end
409
+ end
410
+ end