games_dice 0.5.0 → 0.6.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +11 -1
  3. data/.gitignore +0 -1
  4. data/.rubocop.yml +13 -48
  5. data/CHANGELOG.md +19 -0
  6. data/Gemfile +5 -3
  7. data/README.md +46 -5
  8. data/Rakefile +12 -5
  9. data/ext/games_dice/extconf.rb +0 -3
  10. data/ext/games_dice/probabilities.c +293 -193
  11. data/ext/games_dice/probabilities.h +2 -12
  12. data/games_dice.gemspec +1 -1
  13. data/lib/games_dice/bunch.rb +50 -36
  14. data/lib/games_dice/bunch_helpers.rb +35 -14
  15. data/lib/games_dice/complex_die.rb +64 -22
  16. data/lib/games_dice/complex_die_helpers.rb +122 -144
  17. data/lib/games_dice/dice.rb +61 -17
  18. data/lib/games_dice/dice_helpers.rb +37 -0
  19. data/lib/games_dice/die_result.rb +6 -8
  20. data/lib/games_dice/marshal.rb +2 -7
  21. data/lib/games_dice/parser.rb +30 -0
  22. data/lib/games_dice/probability_budget.rb +90 -0
  23. data/lib/games_dice/probability_calculation.rb +95 -0
  24. data/lib/games_dice/probability_shortcuts.rb +65 -0
  25. data/lib/games_dice/recipe_cache.rb +155 -0
  26. data/lib/games_dice/version.rb +1 -1
  27. data/lib/games_dice.rb +4 -0
  28. data/script/check_documentation.rb +10 -0
  29. data/spec/games_dice/bunch_regressions_spec.rb +46 -0
  30. data/spec/{bunch_spec.rb → games_dice/bunch_spec.rb} +65 -112
  31. data/spec/games_dice/complex_die_budget_spec.rb +60 -0
  32. data/spec/games_dice/complex_die_limits_spec.rb +119 -0
  33. data/spec/games_dice/complex_die_spec.rb +228 -0
  34. data/spec/games_dice/dice_mutation_spec.rb +165 -0
  35. data/spec/games_dice/dice_regressions_spec.rb +50 -0
  36. data/spec/games_dice/dice_spec.rb +61 -0
  37. data/spec/games_dice/die_result_spec.rb +221 -0
  38. data/spec/{die_spec.rb → games_dice/die_spec.rb} +15 -14
  39. data/spec/{map_rule_spec.rb → games_dice/map_rule_spec.rb} +8 -1
  40. data/spec/{parser_spec.rb → games_dice/parser_spec.rb} +12 -2
  41. data/spec/games_dice/probabilities_boundaries_spec.rb +161 -0
  42. data/spec/games_dice/probabilities_marshal_spec.rb +62 -0
  43. data/spec/games_dice/probabilities_spec.rb +484 -0
  44. data/spec/games_dice/probability_budget_spec.rb +17 -0
  45. data/spec/{reroll_rule_spec.rb → games_dice/reroll_rule_spec.rb} +5 -1
  46. data/spec/games_dice_spec.rb +382 -0
  47. data/spec/helpers.rb +64 -2
  48. data/spec/simple_cov_spec.rb +42 -0
  49. data/spec/support/coverage.rb +12 -0
  50. data/spec/support/validation.rb +14 -0
  51. data/spec/yard/cli/stats_spec.rb +43 -0
  52. metadata +29 -12
  53. data/spec/complex_die_spec.rb +0 -314
  54. data/spec/dice_spec.rb +0 -33
  55. data/spec/die_result_spec.rb +0 -254
  56. data/spec/fixtures/probs_fair_die_6.dat +0 -2
  57. data/spec/probability_spec.rb +0 -584
  58. data/spec/readme_spec.rb +0 -410
data/spec/readme_spec.rb DELETED
@@ -1,410 +0,0 @@
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