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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +11 -1
- data/.gitignore +0 -1
- data/.rubocop.yml +13 -48
- data/CHANGELOG.md +19 -0
- data/Gemfile +5 -3
- data/README.md +46 -5
- data/Rakefile +12 -5
- data/ext/games_dice/extconf.rb +0 -3
- data/ext/games_dice/probabilities.c +293 -193
- data/ext/games_dice/probabilities.h +2 -12
- data/games_dice.gemspec +1 -1
- data/lib/games_dice/bunch.rb +50 -36
- data/lib/games_dice/bunch_helpers.rb +35 -14
- data/lib/games_dice/complex_die.rb +64 -22
- data/lib/games_dice/complex_die_helpers.rb +122 -144
- data/lib/games_dice/dice.rb +61 -17
- data/lib/games_dice/dice_helpers.rb +37 -0
- data/lib/games_dice/die_result.rb +6 -8
- data/lib/games_dice/marshal.rb +2 -7
- data/lib/games_dice/parser.rb +30 -0
- data/lib/games_dice/probability_budget.rb +90 -0
- data/lib/games_dice/probability_calculation.rb +95 -0
- data/lib/games_dice/probability_shortcuts.rb +65 -0
- data/lib/games_dice/recipe_cache.rb +155 -0
- data/lib/games_dice/version.rb +1 -1
- data/lib/games_dice.rb +4 -0
- data/script/check_documentation.rb +10 -0
- data/spec/games_dice/bunch_regressions_spec.rb +46 -0
- data/spec/{bunch_spec.rb → games_dice/bunch_spec.rb} +65 -112
- data/spec/games_dice/complex_die_budget_spec.rb +60 -0
- data/spec/games_dice/complex_die_limits_spec.rb +119 -0
- data/spec/games_dice/complex_die_spec.rb +228 -0
- data/spec/games_dice/dice_mutation_spec.rb +165 -0
- data/spec/games_dice/dice_regressions_spec.rb +50 -0
- data/spec/games_dice/dice_spec.rb +61 -0
- data/spec/games_dice/die_result_spec.rb +221 -0
- data/spec/{die_spec.rb → games_dice/die_spec.rb} +15 -14
- data/spec/{map_rule_spec.rb → games_dice/map_rule_spec.rb} +8 -1
- data/spec/{parser_spec.rb → games_dice/parser_spec.rb} +12 -2
- data/spec/games_dice/probabilities_boundaries_spec.rb +161 -0
- data/spec/games_dice/probabilities_marshal_spec.rb +62 -0
- data/spec/games_dice/probabilities_spec.rb +484 -0
- data/spec/games_dice/probability_budget_spec.rb +17 -0
- data/spec/{reroll_rule_spec.rb → games_dice/reroll_rule_spec.rb} +5 -1
- data/spec/games_dice_spec.rb +382 -0
- data/spec/helpers.rb +64 -2
- data/spec/simple_cov_spec.rb +42 -0
- data/spec/support/coverage.rb +12 -0
- data/spec/support/validation.rb +14 -0
- data/spec/yard/cli/stats_spec.rb +43 -0
- metadata +29 -12
- data/spec/complex_die_spec.rb +0 -314
- data/spec/dice_spec.rb +0 -33
- data/spec/die_result_spec.rb +0 -254
- data/spec/fixtures/probs_fair_die_6.dat +0 -2
- data/spec/probability_spec.rb +0 -584
- data/spec/readme_spec.rb +0 -410
data/spec/complex_die_spec.rb
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'helpers'
|
|
4
|
-
|
|
5
|
-
describe GamesDice::ComplexDie do
|
|
6
|
-
before do
|
|
7
|
-
# Set state of default PRNG
|
|
8
|
-
srand(4567)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
it 'represent a basic die as an object' do
|
|
12
|
-
die = described_class.new(6)
|
|
13
|
-
expect(die.min).to be 1
|
|
14
|
-
expect(die.max).to be 6
|
|
15
|
-
expect(die.sides).to be 6
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it "return results based on Ruby's internal rand() by default" do
|
|
19
|
-
die = described_class.new(10)
|
|
20
|
-
[5, 4, 10, 4, 7, 8, 1, 9].each do |expected|
|
|
21
|
-
expect(die.roll.value).to eql expected
|
|
22
|
-
expect(die.result.value).to eql expected
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
it 'use any object with a rand(Integer) method' do
|
|
27
|
-
prng = TestPRNG.new
|
|
28
|
-
die = described_class.new(20, prng: prng)
|
|
29
|
-
[16, 7, 3, 11, 16, 18, 20, 7].each do |expected|
|
|
30
|
-
expect(die.roll.value).to eql expected
|
|
31
|
-
expect(die.result.value).to eql expected
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it 'optionally accept a rerolls param' do
|
|
36
|
-
described_class.new(10, rerolls: [])
|
|
37
|
-
described_class.new(10, rerolls: [GamesDice::RerollRule.new(6, :<=, :reroll_add)])
|
|
38
|
-
described_class.new(10,
|
|
39
|
-
rerolls: [GamesDice::RerollRule.new(6, :<=, :reroll_add),
|
|
40
|
-
GamesDice::RerollRule.new(1, :>=, :reroll_subtract)])
|
|
41
|
-
described_class.new(10, rerolls: [[6, :<=, :reroll_add]])
|
|
42
|
-
described_class.new(10, rerolls: [[6, :<=, :reroll_add], [1, :>=, :reroll_subtract]])
|
|
43
|
-
|
|
44
|
-
expect do
|
|
45
|
-
described_class.new(10, rerolls: 7)
|
|
46
|
-
end.to raise_error(TypeError)
|
|
47
|
-
|
|
48
|
-
expect do
|
|
49
|
-
described_class.new(10, rerolls: ['hello'])
|
|
50
|
-
end.to raise_error(TypeError)
|
|
51
|
-
|
|
52
|
-
expect do
|
|
53
|
-
described_class.new(10, rerolls: [GamesDice::RerollRule.new(6, :<=, :reroll_add), :reroll_add])
|
|
54
|
-
end.to raise_error(TypeError)
|
|
55
|
-
|
|
56
|
-
expect do
|
|
57
|
-
described_class.new(10, rerolls: [7])
|
|
58
|
-
end.to raise_error(TypeError)
|
|
59
|
-
|
|
60
|
-
expect do
|
|
61
|
-
described_class.new(10, rerolls: [['hello']])
|
|
62
|
-
end.to raise_error(ArgumentError)
|
|
63
|
-
|
|
64
|
-
expect do
|
|
65
|
-
described_class.new(10, rerolls: [[6, :<=, :reroll_add], :reroll_add])
|
|
66
|
-
end.to raise_error(TypeError)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it 'optionally accept a maps param' do
|
|
70
|
-
described_class.new(10, maps: [])
|
|
71
|
-
described_class.new(10, maps: [GamesDice::MapRule.new(7, :<=, 1)])
|
|
72
|
-
described_class.new(10, maps: [GamesDice::MapRule.new(7, :<=, 1), GamesDice::MapRule.new(1, :>, -1)])
|
|
73
|
-
described_class.new(10, maps: [[7, :<=, 1]])
|
|
74
|
-
described_class.new(10, maps: [[7, :<=, 1], [1, :>, -1]])
|
|
75
|
-
|
|
76
|
-
expect do
|
|
77
|
-
described_class.new(10, maps: 7)
|
|
78
|
-
end.to raise_error(TypeError)
|
|
79
|
-
|
|
80
|
-
expect do
|
|
81
|
-
described_class.new(10, maps: [7])
|
|
82
|
-
end.to raise_error(TypeError)
|
|
83
|
-
|
|
84
|
-
expect do
|
|
85
|
-
described_class.new(10, maps: [[7]])
|
|
86
|
-
end.to raise_error(ArgumentError)
|
|
87
|
-
|
|
88
|
-
expect do
|
|
89
|
-
described_class.new(10, maps: ['hello'])
|
|
90
|
-
end.to raise_error(TypeError)
|
|
91
|
-
|
|
92
|
-
expect do
|
|
93
|
-
described_class.new(10,
|
|
94
|
-
maps: [GamesDice::MapRule.new(7, :<=, 1),
|
|
95
|
-
GamesDice::RerollRule.new(6, :<=, :reroll_add)])
|
|
96
|
-
end.to raise_error(TypeError)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
describe 'with rerolls' do
|
|
100
|
-
it 'calculate correct minimum and maximum results' do
|
|
101
|
-
die = described_class.new(10, rerolls: [GamesDice::RerollRule.new(10, :<=, :reroll_add, 3)])
|
|
102
|
-
expect(die.min).to be 1
|
|
103
|
-
expect(die.max).to be 40
|
|
104
|
-
|
|
105
|
-
die = described_class.new(10, rerolls: [[1, :>=, :reroll_subtract]])
|
|
106
|
-
expect(die.min).to be(-9)
|
|
107
|
-
expect(die.max).to be 10
|
|
108
|
-
|
|
109
|
-
die = described_class.new(10, rerolls: [GamesDice::RerollRule.new(10, :<=, :reroll_add)])
|
|
110
|
-
expect(die.min).to be 1
|
|
111
|
-
expect(die.max).to be 10_010
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
it 'simulate a d10 that rerolls and adds on a result of 10' do
|
|
115
|
-
die = described_class.new(10, rerolls: [[10, :<=, :reroll_add]])
|
|
116
|
-
[5, 4, 14, 7, 8, 1, 9].each do |expected|
|
|
117
|
-
expect(die.roll.value).to eql expected
|
|
118
|
-
expect(die.result.value).to eql expected
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
it 'explain how it got results outside range 1 to 10 on a d10' do
|
|
123
|
-
die = described_class.new(10, rerolls: [[10, :<=, :reroll_add], [1, :>=, :reroll_subtract]])
|
|
124
|
-
['5', '4', '[10+4] 14', '7', '8', '[1-9] -8'].each do |expected|
|
|
125
|
-
die.roll
|
|
126
|
-
expect(die.explain_result).to eql expected
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
it 'calculate an expected result' do
|
|
131
|
-
die = described_class.new(10, rerolls: [[10, :<=, :reroll_add], [1, :>=, :reroll_subtract]])
|
|
132
|
-
expect(die.probabilities.expected).to be_within(1e-10).of 5.5
|
|
133
|
-
|
|
134
|
-
die = described_class.new(10, rerolls: [GamesDice::RerollRule.new(1, :<=, :reroll_use_best, 1)])
|
|
135
|
-
expect(die.probabilities.expected).to be_within(1e-10).of 7.15
|
|
136
|
-
|
|
137
|
-
die = described_class.new(10, rerolls: [GamesDice::RerollRule.new(1, :<=, :reroll_use_worst, 2)])
|
|
138
|
-
expect(die.probabilities.expected).to be_within(1e-10).of 3.025
|
|
139
|
-
|
|
140
|
-
die = described_class.new(6, rerolls: [GamesDice::RerollRule.new(6, :<=, :reroll_add)])
|
|
141
|
-
expect(die.probabilities.expected).to be_within(1e-10).of 4.2
|
|
142
|
-
|
|
143
|
-
die = described_class.new(8, rerolls: [GamesDice::RerollRule.new(1, :>=, :reroll_use_best)])
|
|
144
|
-
expect(die.probabilities.expected).to be_within(1e-10).of 5.0
|
|
145
|
-
|
|
146
|
-
die = described_class.new(4, rerolls: [GamesDice::RerollRule.new(1, :>=, :reroll_replace, 1)])
|
|
147
|
-
expect(die.probabilities.expected).to be_within(1e-10).of 2.875
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
it 'calculate probabilities of each possible result' do
|
|
151
|
-
die = described_class.new(6, rerolls: [GamesDice::RerollRule.new(7, :>, :reroll_add, 1)])
|
|
152
|
-
probs = die.probabilities.to_h
|
|
153
|
-
expect(probs[11]).to be_within(1e-10).of 2 / 36.0
|
|
154
|
-
expect(probs[8]).to be_within(1e-10).of 5 / 36.0
|
|
155
|
-
expect(probs.values.sum).to be_within(1e-9).of 1.0
|
|
156
|
-
|
|
157
|
-
die = described_class.new(10, rerolls: [GamesDice::RerollRule.new(10, :<=, :reroll_add)])
|
|
158
|
-
probs = die.probabilities.to_h
|
|
159
|
-
expect(probs[8]).to be_within(1e-10).of 0.1
|
|
160
|
-
expect(probs[10]).to be_nil
|
|
161
|
-
expect(probs[13]).to be_within(1e-10).of 0.01
|
|
162
|
-
expect(probs[27]).to be_within(1e-10).of 0.001
|
|
163
|
-
expect(probs.values.sum).to be_within(1e-9).of 1.0
|
|
164
|
-
|
|
165
|
-
die = described_class.new(6, rerolls: [GamesDice::RerollRule.new(1, :>=, :reroll_replace, 1)])
|
|
166
|
-
probs = die.probabilities.to_h
|
|
167
|
-
expect(probs[1]).to be_within(1e-10).of 1 / 36.0
|
|
168
|
-
expect(probs[2]).to be_within(1e-10).of 7 / 36.0
|
|
169
|
-
expect(probs.values.sum).to be_within(1e-9).of 1.0
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
it 'calculate aggregate probabilities' do
|
|
173
|
-
die = described_class.new(6, rerolls: [GamesDice::RerollRule.new(7, :>, :reroll_add, 1)])
|
|
174
|
-
probs = die.probabilities
|
|
175
|
-
expect(probs.p_gt(7)).to be_within(1e-10).of 15 / 36.0
|
|
176
|
-
expect(probs.p_gt(-10)).to be 1.0
|
|
177
|
-
expect(probs.p_gt(12)).to be 0.0
|
|
178
|
-
|
|
179
|
-
expect(probs.p_ge(7)).to be_within(1e-10).of 21 / 36.0
|
|
180
|
-
expect(probs.p_ge(2)).to be 1.0
|
|
181
|
-
expect(probs.p_ge(15)).to be 0.0
|
|
182
|
-
|
|
183
|
-
expect(probs.p_lt(7)).to be_within(1e-10).of 15 / 36.0
|
|
184
|
-
expect(probs.p_lt(-10)).to be 0.0
|
|
185
|
-
expect(probs.p_lt(13)).to be 1.0
|
|
186
|
-
|
|
187
|
-
expect(probs.p_le(7)).to be_within(1e-10).of 21 / 36.0
|
|
188
|
-
expect(probs.p_le(1)).to be 0.0
|
|
189
|
-
expect(probs.p_le(12)).to be 1.0
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
describe 'with maps' do
|
|
194
|
-
it 'calculate correct minimum and maximum results' do
|
|
195
|
-
die = described_class.new(10, maps: [GamesDice::MapRule.new(7, :<=, 1, 'S')])
|
|
196
|
-
expect(die.min).to be 0
|
|
197
|
-
expect(die.max).to be 1
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
it 'simulate a d10 that scores 1 for success on a value of 7 or more' do
|
|
201
|
-
die = described_class.new(10, maps: [[7, :<=, 1, 'S']])
|
|
202
|
-
[0, 0, 1, 0, 1, 1, 0, 1].each do |expected|
|
|
203
|
-
expect(die.roll.value).to eql expected
|
|
204
|
-
expect(die.result.value).to eql expected
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
it 'label the mappings applied with the provided names' do
|
|
209
|
-
die = described_class.new(10, maps: [[7, :<=, 1, 'S'], [1, :>=, -1, 'F']])
|
|
210
|
-
['5', '4', '10 S', '4', '7 S', '8 S', '1 F', '9 S'].each do |expected|
|
|
211
|
-
die.roll
|
|
212
|
-
expect(die.explain_result).to eql expected
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
it 'calculate an expected result' do
|
|
217
|
-
die = described_class.new(10,
|
|
218
|
-
maps: [GamesDice::MapRule.new(7, :<=, 1, 'S'),
|
|
219
|
-
GamesDice::MapRule.new(1, :>=, -1, 'F')])
|
|
220
|
-
expect(die.probabilities.expected).to be_within(1e-10).of 0.3
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
it 'calculate probabilities of each possible result' do
|
|
224
|
-
die = described_class.new(10,
|
|
225
|
-
maps: [GamesDice::MapRule.new(7, :<=, 1, 'S'),
|
|
226
|
-
GamesDice::MapRule.new(1, :>=, -1, 'F')])
|
|
227
|
-
probs_hash = die.probabilities.to_h
|
|
228
|
-
expect(probs_hash[1]).to be_within(1e-10).of 0.4
|
|
229
|
-
expect(probs_hash[0]).to be_within(1e-10).of 0.5
|
|
230
|
-
expect(probs_hash[-1]).to be_within(1e-10).of 0.1
|
|
231
|
-
expect(probs_hash.values.sum).to be_within(1e-9).of 1.0
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
it 'calculate aggregate probabilities' do
|
|
235
|
-
die = described_class.new(10,
|
|
236
|
-
maps: [GamesDice::MapRule.new(7, :<=, 1, 'S'),
|
|
237
|
-
GamesDice::MapRule.new(1, :>=, -1, 'F')])
|
|
238
|
-
probs = die.probabilities
|
|
239
|
-
expect(probs.p_gt(-2)).to be 1.0
|
|
240
|
-
expect(probs.p_gt(-1)).to be_within(1e-10).of 0.9
|
|
241
|
-
expect(probs.p_gt(1)).to be 0.0
|
|
242
|
-
|
|
243
|
-
expect(probs.p_ge(1)).to be_within(1e-10).of 0.4
|
|
244
|
-
expect(probs.p_ge(-1)).to be 1.0
|
|
245
|
-
expect(probs.p_ge(2)).to be 0.0
|
|
246
|
-
|
|
247
|
-
expect(probs.p_lt(1)).to be_within(1e-10).of 0.6
|
|
248
|
-
expect(probs.p_lt(-1)).to be 0.0
|
|
249
|
-
expect(probs.p_lt(2)).to be 1.0
|
|
250
|
-
|
|
251
|
-
expect(probs.p_le(-1)).to be_within(1e-10).of 0.1
|
|
252
|
-
expect(probs.p_le(-2)).to be 0.0
|
|
253
|
-
expect(probs.p_le(1)).to be 1.0
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
describe 'with rerolls and maps together' do
|
|
258
|
-
before do
|
|
259
|
-
@die = described_class.new(6,
|
|
260
|
-
rerolls: [[6, :<=, :reroll_add]],
|
|
261
|
-
maps: [GamesDice::MapRule.new(9, :<=, 1, 'Success')])
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
it 'calculate correct minimum and maximum results' do
|
|
265
|
-
expect(@die.min).to be 0
|
|
266
|
-
expect(@die.max).to be 1
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
it 'calculate an expected result' do
|
|
270
|
-
expect(@die.probabilities.expected).to be_within(1e-10).of 4 / 36.0
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
it 'calculate probabilities of each possible result' do
|
|
274
|
-
probs_hash = @die.probabilities.to_h
|
|
275
|
-
expect(probs_hash[1]).to be_within(1e-10).of 4 / 36.0
|
|
276
|
-
expect(probs_hash[0]).to be_within(1e-10).of 32 / 36.0
|
|
277
|
-
expect(probs_hash.values.sum).to be_within(1e-9).of 1.0
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
it 'calculate aggregate probabilities' do
|
|
281
|
-
probs = @die.probabilities
|
|
282
|
-
|
|
283
|
-
expect(probs.p_gt(0)).to be_within(1e-10).of 4 / 36.0
|
|
284
|
-
expect(probs.p_gt(-2)).to be 1.0
|
|
285
|
-
expect(probs.p_gt(1)).to be 0.0
|
|
286
|
-
|
|
287
|
-
expect(probs.p_ge(1)).to be_within(1e-10).of 4 / 36.0
|
|
288
|
-
expect(probs.p_ge(-1)).to be 1.0
|
|
289
|
-
expect(probs.p_ge(2)).to be 0.0
|
|
290
|
-
|
|
291
|
-
expect(probs.p_lt(1)).to be_within(1e-10).of 32 / 36.0
|
|
292
|
-
expect(probs.p_lt(0)).to be 0.0
|
|
293
|
-
expect(probs.p_lt(2)).to be 1.0
|
|
294
|
-
|
|
295
|
-
expect(probs.p_le(0)).to be_within(1e-10).of 32 / 36.0
|
|
296
|
-
expect(probs.p_le(-1)).to be 0.0
|
|
297
|
-
expect(probs.p_le(1)).to be 1.0
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
it 'apply mapping to final re-rolled result' do
|
|
301
|
-
[0, 1, 0, 0].each do |expected|
|
|
302
|
-
expect(@die.roll.value).to eql expected
|
|
303
|
-
expect(@die.result.value).to eql expected
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
it 'explain how it got each result' do
|
|
308
|
-
['5', '[6+4] 10 Success', '[6+2] 8', '5'].each do |expected|
|
|
309
|
-
@die.roll
|
|
310
|
-
expect(@die.explain_result).to eql expected
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
end
|
data/spec/dice_spec.rb
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'helpers'
|
|
4
|
-
|
|
5
|
-
describe GamesDice::Dice do
|
|
6
|
-
describe 'dice scheme' do
|
|
7
|
-
before do
|
|
8
|
-
srand(67_809)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
describe '1d10+2' do
|
|
12
|
-
let(:dice) { described_class.new([{ sides: 10, ndice: 1 }], 2) }
|
|
13
|
-
|
|
14
|
-
it 'simulate rolling a ten-sided die, and adding two to each result' do
|
|
15
|
-
[5, 4, 10, 10, 7, 5, 9].each do |expected_total|
|
|
16
|
-
expect(dice.roll).to eql expected_total
|
|
17
|
-
expect(dice.result).to eql expected_total
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
describe '2d6+6' do
|
|
23
|
-
let(:dice) { described_class.new([{ sides: 6, ndice: 2 }], 6) }
|
|
24
|
-
|
|
25
|
-
it 'simulate rolling two six-sided dice and adding six to the result' do
|
|
26
|
-
[15, 12, 17, 15, 13, 13, 16].each do |expected_total|
|
|
27
|
-
expect(dice.roll).to eql expected_total
|
|
28
|
-
expect(dice.result).to eql expected_total
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
data/spec/die_result_spec.rb
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'helpers'
|
|
4
|
-
|
|
5
|
-
describe GamesDice::DieResult do
|
|
6
|
-
describe '.new' do
|
|
7
|
-
it "work without parameters to represent 'no results yet'" do
|
|
8
|
-
die_result = described_class.new
|
|
9
|
-
expect(die_result.value).to be_nil
|
|
10
|
-
expect(die_result.rolls).to eql []
|
|
11
|
-
expect(die_result.roll_reasons).to eql []
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
it 'work with a single Integer param to represent an initial result' do
|
|
15
|
-
die_result = described_class.new(8)
|
|
16
|
-
expect(die_result.value).to be 8
|
|
17
|
-
expect(die_result.rolls).to eql [8]
|
|
18
|
-
expect(die_result.roll_reasons).to eql [:basic]
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
it 'not accept a param that cannot be coerced to Integer' do
|
|
22
|
-
expect { described_class.new([]) }.to raise_error(TypeError)
|
|
23
|
-
expect { described_class.new('N') }.to raise_error(ArgumentError)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
it 'not accept unknown reasons for making a roll' do
|
|
27
|
-
expect { described_class.new(8, 'wooo') }.to raise_error(ArgumentError)
|
|
28
|
-
expect { described_class.new(8, :frabulous) }.to raise_error(ArgumentError)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
describe '#add_roll' do
|
|
33
|
-
context "when starting from 'no results yet'" do
|
|
34
|
-
let(:die_result) { described_class.new }
|
|
35
|
-
|
|
36
|
-
it 'create an initial result' do
|
|
37
|
-
die_result.add_roll(5)
|
|
38
|
-
expect(die_result.value).to be 5
|
|
39
|
-
expect(die_result.rolls).to eql [5]
|
|
40
|
-
expect(die_result.roll_reasons).to eql [:basic]
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
it 'accept non-basic reasons for the first roll' do
|
|
44
|
-
die_result.add_roll(4, :reroll_subtract)
|
|
45
|
-
expect(die_result.value).to be(-4)
|
|
46
|
-
expect(die_result.rolls).to eql [4]
|
|
47
|
-
expect(die_result.roll_reasons).to eql [:reroll_subtract]
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
it 'not accept a first param that cannot be coerced to Integer' do
|
|
51
|
-
expect { die_result.add_roll([]) }.to raise_error(TypeError)
|
|
52
|
-
expect { die_result.add_roll('N') }.to raise_error(ArgumentError)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it 'not accept an unsupported second param' do
|
|
56
|
-
expect { die_result.add_roll(5, []) }.to raise_error(ArgumentError)
|
|
57
|
-
expect { die_result.add_roll(15, :bam) }.to raise_error(ArgumentError)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
context 'when starting with an initial result' do
|
|
62
|
-
let(:die_result) { described_class.new(7) }
|
|
63
|
-
|
|
64
|
-
it 'not accept a first param that cannot be coerced to Integer' do
|
|
65
|
-
expect { die_result.add_roll([]) }.to raise_error(TypeError)
|
|
66
|
-
expect { die_result.add_roll('N') }.to raise_error(ArgumentError)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it 'not accept an unsupported second param' do
|
|
70
|
-
expect { die_result.add_roll(5, []) }.to raise_error(ArgumentError)
|
|
71
|
-
expect { die_result.add_roll(15, :bam) }.to raise_error(ArgumentError)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
context 'when adding another basic roll' do
|
|
75
|
-
it 'replace an initial result, as if the die were re-rolled' do
|
|
76
|
-
die_result.add_roll(5)
|
|
77
|
-
expect(die_result.value).to be 5
|
|
78
|
-
expect(die_result.rolls).to eql [7, 5]
|
|
79
|
-
expect(die_result.roll_reasons).to eql %i[basic basic]
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
context 'with exploding dice' do
|
|
84
|
-
it 'add to value when exploding up' do
|
|
85
|
-
die_result.add_roll(6, :reroll_add)
|
|
86
|
-
expect(die_result.value).to be 13
|
|
87
|
-
expect(die_result.rolls).to eql [7, 6]
|
|
88
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_add]
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
it 'subtract from value when exploding down' do
|
|
92
|
-
die_result.add_roll(4, :reroll_subtract)
|
|
93
|
-
expect(die_result.value).to be 3
|
|
94
|
-
expect(die_result.rolls).to eql [7, 4]
|
|
95
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_subtract]
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
context 'with re-roll dice' do
|
|
100
|
-
it 'optionally replace roll unconditionally' do
|
|
101
|
-
die_result.add_roll(2, :reroll_replace)
|
|
102
|
-
expect(die_result.value).to be 2
|
|
103
|
-
expect(die_result.rolls).to eql [7, 2]
|
|
104
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_replace]
|
|
105
|
-
|
|
106
|
-
die_result.add_roll(5, :reroll_replace)
|
|
107
|
-
expect(die_result.value).to be 5
|
|
108
|
-
expect(die_result.rolls).to eql [7, 2, 5]
|
|
109
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_replace reroll_replace]
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
it 'optionally use best roll' do
|
|
113
|
-
die_result.add_roll(2, :reroll_use_best)
|
|
114
|
-
expect(die_result.value).to be 7
|
|
115
|
-
expect(die_result.rolls).to eql [7, 2]
|
|
116
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_use_best]
|
|
117
|
-
|
|
118
|
-
die_result.add_roll(9, :reroll_use_best)
|
|
119
|
-
expect(die_result.value).to be 9
|
|
120
|
-
expect(die_result.rolls).to eql [7, 2, 9]
|
|
121
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_use_best reroll_use_best]
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
it 'optionally use worst roll' do
|
|
125
|
-
die_result.add_roll(4, :reroll_use_worst)
|
|
126
|
-
expect(die_result.value).to be 4
|
|
127
|
-
expect(die_result.rolls).to eql [7, 4]
|
|
128
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_use_worst]
|
|
129
|
-
|
|
130
|
-
die_result.add_roll(5, :reroll_use_worst)
|
|
131
|
-
expect(die_result.value).to be 4
|
|
132
|
-
expect(die_result.rolls).to eql [7, 4, 5]
|
|
133
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_use_worst reroll_use_worst]
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
context 'with combinations of reroll reasons' do
|
|
138
|
-
it 'correctly handle valid reasons for extra rolls in combination' do
|
|
139
|
-
die_result.add_roll(10, :reroll_add)
|
|
140
|
-
die_result.add_roll(3, :reroll_subtract)
|
|
141
|
-
expect(die_result.value).to be 14
|
|
142
|
-
expect(die_result.rolls).to eql [7, 10, 3]
|
|
143
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_add reroll_subtract]
|
|
144
|
-
|
|
145
|
-
die_result.add_roll(12, :reroll_replace)
|
|
146
|
-
expect(die_result.value).to be 12
|
|
147
|
-
expect(die_result.rolls).to eql [7, 10, 3, 12]
|
|
148
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_add reroll_subtract reroll_replace]
|
|
149
|
-
|
|
150
|
-
die_result.add_roll(9, :reroll_use_best)
|
|
151
|
-
expect(die_result.value).to be 12
|
|
152
|
-
expect(die_result.rolls).to eql [7, 10, 3, 12, 9]
|
|
153
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_add reroll_subtract reroll_replace
|
|
154
|
-
reroll_use_best]
|
|
155
|
-
|
|
156
|
-
die_result.add_roll(15, :reroll_add)
|
|
157
|
-
expect(die_result.value).to be 27
|
|
158
|
-
expect(die_result.rolls).to eql [7, 10, 3, 12, 9, 15]
|
|
159
|
-
expect(die_result.roll_reasons).to eql %i[basic reroll_add reroll_subtract reroll_replace
|
|
160
|
-
reroll_use_best reroll_add]
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
describe '#explain_value' do
|
|
167
|
-
let(:die_result) { described_class.new }
|
|
168
|
-
|
|
169
|
-
it "be empty string for 'no results yet'" do
|
|
170
|
-
expect(die_result.explain_value).to eql ''
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
it 'be a simple stringified number when there is one die roll' do
|
|
174
|
-
die_result.add_roll(3)
|
|
175
|
-
expect(die_result.explain_value).to eql '3'
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
it 'describe all single rolls made and how they combine' do
|
|
179
|
-
die_result.add_roll(6)
|
|
180
|
-
expect(die_result.explain_value).to eql '6'
|
|
181
|
-
|
|
182
|
-
die_result.add_roll(5, :reroll_add)
|
|
183
|
-
expect(die_result.explain_value).to eql '[6+5] 11'
|
|
184
|
-
|
|
185
|
-
die_result.add_roll(2, :reroll_replace)
|
|
186
|
-
expect(die_result.explain_value).to eql '[6+5|2] 2'
|
|
187
|
-
|
|
188
|
-
die_result.add_roll(7, :reroll_subtract)
|
|
189
|
-
expect(die_result.explain_value).to eql '[6+5|2-7] -5'
|
|
190
|
-
|
|
191
|
-
die_result.add_roll(4, :reroll_use_worst)
|
|
192
|
-
expect(die_result.explain_value).to eql '[6+5|2-7\\4] -5'
|
|
193
|
-
|
|
194
|
-
die_result.add_roll(3, :reroll_use_best)
|
|
195
|
-
expect(die_result.explain_value).to eql '[6+5|2-7\\4/3] 3'
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
it 'combine via +,- and * intuitively based on #value' do
|
|
200
|
-
die_result = described_class.new(7)
|
|
201
|
-
expect(die_result + 3).to be 10
|
|
202
|
-
expect(4 + die_result).to be 11
|
|
203
|
-
expect(die_result - 2).to be 5
|
|
204
|
-
expect(9 - die_result).to be 2
|
|
205
|
-
|
|
206
|
-
expect(die_result + 7.7).to be 14.7
|
|
207
|
-
expect(4.1 + die_result).to be 11.1
|
|
208
|
-
|
|
209
|
-
expect(die_result * 2).to be 14
|
|
210
|
-
expect(1 * die_result).to be 7
|
|
211
|
-
|
|
212
|
-
other_die_result = described_class.new(6)
|
|
213
|
-
other_die_result.add_roll(6, :reroll_add)
|
|
214
|
-
expect(die_result + other_die_result).to be 19
|
|
215
|
-
expect(other_die_result - die_result).to be 5
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
it 'support comparison with >,<,>=,<= as if it were an integer, based on #value' do
|
|
219
|
-
die_result = described_class.new(7)
|
|
220
|
-
|
|
221
|
-
expect(die_result > 3).to be true
|
|
222
|
-
expect(die_result < 14).to be true
|
|
223
|
-
expect(die_result >= 7).to be true
|
|
224
|
-
expect(die_result <= 9.5).to be true
|
|
225
|
-
expect(die_result < 3).to be false
|
|
226
|
-
expect(die_result > 14).to be false
|
|
227
|
-
expect(die_result <= 8).to be true
|
|
228
|
-
expect(die_result >= 14).to be false
|
|
229
|
-
|
|
230
|
-
other_die_result = described_class.new(6)
|
|
231
|
-
other_die_result.add_roll(6, :reroll_add)
|
|
232
|
-
expect(die_result > other_die_result).to be false
|
|
233
|
-
expect(other_die_result > die_result).to be true
|
|
234
|
-
expect(die_result >= other_die_result).to be false
|
|
235
|
-
expect(other_die_result >= die_result).to be true
|
|
236
|
-
expect(die_result < other_die_result).to be true
|
|
237
|
-
expect(other_die_result < die_result).to be false
|
|
238
|
-
expect(die_result <= other_die_result).to be true
|
|
239
|
-
expect(other_die_result <= die_result).to be false
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
it 'sort, based on #value' do
|
|
243
|
-
die_results = [
|
|
244
|
-
described_class.new(7), described_class.new(5), described_class.new(8), described_class.new(3)
|
|
245
|
-
]
|
|
246
|
-
|
|
247
|
-
die_results.sort!
|
|
248
|
-
|
|
249
|
-
expect(die_results[0].value).to be 3
|
|
250
|
-
expect(die_results[1].value).to be 5
|
|
251
|
-
expect(die_results[2].value).to be 7
|
|
252
|
-
expect(die_results[3].value).to be 8
|
|
253
|
-
end
|
|
254
|
-
end
|