games_dice 0.0.2 → 0.0.3
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.
- data/README.md +2 -0
- data/Rakefile +9 -0
- data/games_dice.gemspec +2 -1
- data/lib/games_dice.rb +4 -0
- data/lib/games_dice/bunch.rb +367 -0
- data/lib/games_dice/complex_die.rb +295 -0
- data/lib/games_dice/map_rule.rb +46 -0
- data/lib/games_dice/reroll_rule.rb +64 -0
- data/lib/games_dice/version.rb +1 -1
- data/spec/bunch_spec.rb +414 -0
- data/spec/complex_die_spec.rb +281 -0
- data/spec/map_rule_spec.rb +44 -0
- data/spec/reroll_rule_spec.rb +44 -0
- data/travis.yml +10 -0
- metadata +37 -3
@@ -0,0 +1,46 @@
|
|
1
|
+
module GamesDice
|
2
|
+
# convert integer die result into value used in a game (e.g. 1 for a 'success')
|
3
|
+
class MapRule
|
4
|
+
|
5
|
+
# trigger_op, trigger_value, mapped_value and mapped_name set the attributes of the same name
|
6
|
+
# rule = RPGMapRule.new( 6, :<=, 1, 'Success' ) # score 1 for a result of 6 or more
|
7
|
+
def initialize trigger_value, trigger_op, mapped_value=0, mapped_name=''
|
8
|
+
|
9
|
+
if ! trigger_value.respond_to?( trigger_op )
|
10
|
+
raise ArgumentError, "trigger_value #{trigger_value.inspect} cannot respond to trigger_op #{trigger_value.inspect}"
|
11
|
+
end
|
12
|
+
|
13
|
+
@trigger_value = trigger_value
|
14
|
+
@trigger_op = trigger_op
|
15
|
+
raise TypeError if ! mapped_value.is_a? Numeric
|
16
|
+
@mapped_value = Integer(mapped_value)
|
17
|
+
@mapped_name = mapped_name.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
# an Integer value or Range that will be mapped to a single value. #trigger_op is called against it
|
21
|
+
attr_reader :trigger_value
|
22
|
+
|
23
|
+
# a valid symbol for a method, which will be called against #trigger_value with the current
|
24
|
+
# die result as a param. If the operator returns true for a specific die result, then the
|
25
|
+
# mapped_value will be used in its stead. If the operator returns nil or false, the map is not
|
26
|
+
# triggered. All other values will be returned as the result of the map (allowing you to
|
27
|
+
# specify any method that takes an integer as input and returns something else as the end result)
|
28
|
+
attr_reader :trigger_op
|
29
|
+
|
30
|
+
# an integer value
|
31
|
+
attr_reader :mapped_value
|
32
|
+
|
33
|
+
# a string description of the mapping, e.g. 'S' for a success
|
34
|
+
attr_reader :mapped_name
|
35
|
+
|
36
|
+
# runs the rule against test_value, returning either a new value, or nil if the rule does not apply
|
37
|
+
def map_from test_value
|
38
|
+
op_result = @trigger_value.send( @trigger_op, test_value )
|
39
|
+
return nil unless op_result
|
40
|
+
if op_result == true
|
41
|
+
return @mapped_value
|
42
|
+
end
|
43
|
+
return op_result
|
44
|
+
end
|
45
|
+
end # class MapRule
|
46
|
+
end # module GamesDice
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module GamesDice
|
2
|
+
|
3
|
+
# specifies when and how a ComplexDie should be re-rolled
|
4
|
+
class RerollRule
|
5
|
+
|
6
|
+
# allowed reasons for making a reroll
|
7
|
+
TYPES = {
|
8
|
+
:reroll_add => '+',
|
9
|
+
:reroll_new_die => '*',
|
10
|
+
:reroll_new_keeper => '*',
|
11
|
+
:reroll_subtract => '-',
|
12
|
+
:reroll_replace => '|',
|
13
|
+
:reroll_use_best => '/',
|
14
|
+
:reroll_use_worst => '\\',
|
15
|
+
}
|
16
|
+
|
17
|
+
# trigger_op, trigger_value, type and limit set the attributes of the same name
|
18
|
+
# rule = GamesDice::RerollRule.new( 10, :<=, :reroll_add ) # an 'exploding' die
|
19
|
+
def initialize trigger_value, trigger_op, type, limit=nil
|
20
|
+
|
21
|
+
if ! trigger_value.respond_to?( trigger_op )
|
22
|
+
raise ArgumentError, "trigger_value #{trigger_value.inspect} cannot respond to trigger_op #{trigger_value.inspect}"
|
23
|
+
end
|
24
|
+
|
25
|
+
unless TYPES.has_key?(type)
|
26
|
+
raise ArgumentError, "Unrecognised reason for a re-roll #{type}"
|
27
|
+
end
|
28
|
+
|
29
|
+
@trigger_value = trigger_value
|
30
|
+
@trigger_op = trigger_op
|
31
|
+
@type = type
|
32
|
+
@limit = limit ? Integer(limit) : 1000
|
33
|
+
@limit = 1 if @type == :reroll_subtract
|
34
|
+
end
|
35
|
+
|
36
|
+
# a valid symbol for a method, which will be called against #trigger_value with the current
|
37
|
+
# die result as a param. It should return true or false
|
38
|
+
attr_reader :trigger_op
|
39
|
+
|
40
|
+
# an Integer value or Range that will cause the reroll to occur. #trigger_op is called against it
|
41
|
+
attr_reader :trigger_value
|
42
|
+
|
43
|
+
# a symbol, should be one of the following:
|
44
|
+
# :reroll_add - add result of reroll to running total, and ignore :reroll_subtract for this die
|
45
|
+
# :reroll_new_die - roll a new die of the same type
|
46
|
+
# :reroll_new_keeper - roll a new die of the same type, and keep the result
|
47
|
+
# :reroll_subtract - subtract result of reroll from running total, and reverse sense of any further :reroll_add results
|
48
|
+
# :reroll_replace - use the new value in place of existing value for the die
|
49
|
+
# :reroll_use_best - use the new value if it is higher than the erxisting value
|
50
|
+
# :reroll_use_worst - use the new value if it is higher than the existing value
|
51
|
+
attr_reader :type
|
52
|
+
|
53
|
+
# maximum number of times this rule should be applied to a single die. If type is:reroll_subtract,
|
54
|
+
# this value is always 1. A default value of 100 is used if not set in the constructor
|
55
|
+
attr_reader :limit
|
56
|
+
|
57
|
+
# runs the rule against a test value, returning truth value from calling the trigger_op method
|
58
|
+
def applies? test_value
|
59
|
+
@trigger_value.send( @trigger_op, test_value ) ? true : false
|
60
|
+
end
|
61
|
+
|
62
|
+
end # class RerollRule
|
63
|
+
|
64
|
+
end # module GamesDice
|
data/lib/games_dice/version.rb
CHANGED
data/spec/bunch_spec.rb
ADDED
@@ -0,0 +1,414 @@
|
|
1
|
+
require 'games_dice'
|
2
|
+
|
3
|
+
describe GamesDice::Bunch do
|
4
|
+
|
5
|
+
describe "dice scheme" do
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
srand(67809)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '1d10' do
|
12
|
+
let(:dice) { GamesDice::Bunch.new( :sides => 10, :ndice => 1 ) }
|
13
|
+
|
14
|
+
it "should simulate rolling a ten-sided die" do
|
15
|
+
[3,2,8,8,5,3,7].each do |expected_total|
|
16
|
+
dice.roll.should == expected_total
|
17
|
+
dice.result.should == expected_total
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should concisely explain each result" do
|
22
|
+
["3", "2", "8", "8"].each do |expected_explain|
|
23
|
+
dice.roll
|
24
|
+
dice.explain_result.should == expected_explain
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should calculate correct min, max = 1,10" do
|
29
|
+
dice.min.should == 1
|
30
|
+
dice.max.should == 10
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should have a mean value of 5.5" do
|
34
|
+
dice.expected_result.should be_within(1e-10).of 5.5
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should calculate probabilities correctly" do
|
38
|
+
dice.probabilities[1].should be_within(1e-10).of 0.1
|
39
|
+
dice.probabilities[2].should be_within(1e-10).of 0.1
|
40
|
+
dice.probabilities[3].should be_within(1e-10).of 0.1
|
41
|
+
dice.probabilities[4].should be_within(1e-10).of 0.1
|
42
|
+
dice.probabilities[5].should be_within(1e-10).of 0.1
|
43
|
+
dice.probabilities[6].should be_within(1e-10).of 0.1
|
44
|
+
dice.probabilities[7].should be_within(1e-10).of 0.1
|
45
|
+
dice.probabilities[8].should be_within(1e-10).of 0.1
|
46
|
+
dice.probabilities[9].should be_within(1e-10).of 0.1
|
47
|
+
dice.probabilities[10].should be_within(1e-10).of 0.1
|
48
|
+
dice.probabilities.values.inject(:+).should be_within(1e-9).of 1.0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '2d6' do
|
53
|
+
let(:dice) { GamesDice::Bunch.new( :sides => 6, :ndice => 2 ) }
|
54
|
+
|
55
|
+
it "should simulate rolling two six-sided dice and adding them" do
|
56
|
+
[9,6,11,9,7,7,10].each do |expected_total|
|
57
|
+
dice.roll.should == expected_total
|
58
|
+
dice.result.should == expected_total
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should concisely explain each result" do
|
63
|
+
["3 + 6 = 9","2 + 4 = 6","5 + 6 = 11","3 + 6 = 9","2 + 5 = 7","6 + 1 = 7","5 + 5 = 10",].each do |expected_explain|
|
64
|
+
dice.roll
|
65
|
+
dice.explain_result.should == expected_explain
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should calculate correct min, max = 2,12" do
|
70
|
+
dice.min.should == 2
|
71
|
+
dice.max.should == 12
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should have a mean value of 7.0" do
|
75
|
+
dice.expected_result.should be_within(1e-10).of 7.0
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should calculate probabilities correctly" do
|
79
|
+
dice.probabilities[2].should be_within(1e-10).of 1/36.0
|
80
|
+
dice.probabilities[3].should be_within(1e-10).of 2/36.0
|
81
|
+
dice.probabilities[4].should be_within(1e-10).of 3/36.0
|
82
|
+
dice.probabilities[5].should be_within(1e-10).of 4/36.0
|
83
|
+
dice.probabilities[6].should be_within(1e-10).of 5/36.0
|
84
|
+
dice.probabilities[7].should be_within(1e-10).of 6/36.0
|
85
|
+
dice.probabilities[8].should be_within(1e-10).of 5/36.0
|
86
|
+
dice.probabilities[9].should be_within(1e-10).of 4/36.0
|
87
|
+
dice.probabilities[10].should be_within(1e-10).of 3/36.0
|
88
|
+
dice.probabilities[11].should be_within(1e-10).of 2/36.0
|
89
|
+
dice.probabilities[12].should be_within(1e-10).of 1/36.0
|
90
|
+
dice.probabilities.values.inject(:+).should be_within(1e-9).of 1.0
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '20d10' do
|
95
|
+
let(:dice) { GamesDice::Bunch.new( :sides => 10, :ndice => 20 ) }
|
96
|
+
|
97
|
+
it "should simulate rolling twenty ten-sided dice and adding them" do
|
98
|
+
[132,103,102,124,132,96,111].each do |expected_total|
|
99
|
+
dice.roll.should == expected_total
|
100
|
+
dice.result.should == expected_total
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should concisely explain each result" do
|
105
|
+
["3 + 2 + 8 + 8 + 5 + 3 + 7 + 7 + 6 + 10 + 7 + 6 + 9 + 5 + 5 + 8 + 10 + 9 + 5 + 9 = 132",
|
106
|
+
"3 + 9 + 1 + 4 + 3 + 5 + 7 + 1 + 10 + 4 + 7 + 7 + 6 + 5 + 2 + 7 + 4 + 9 + 7 + 2 = 103",
|
107
|
+
"6 + 1 + 1 + 3 + 1 + 4 + 9 + 6 + 3 + 10 + 9 + 10 + 8 + 4 + 1 + 4 + 2 + 1 + 10 + 9 = 102",
|
108
|
+
].each do |expected_explain|
|
109
|
+
dice.roll
|
110
|
+
dice.explain_result.should == expected_explain
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should calculate correct min, max = 20,200" do
|
115
|
+
dice.min.should == 20
|
116
|
+
dice.max.should == 200
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should have a mean value of 110.0" do
|
120
|
+
dice.expected_result.should be_within(1e-8).of 110.0
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should calculate probabilities correctly" do
|
124
|
+
dice.probabilities[20].should be_within(1e-26).of 1e-20
|
125
|
+
dice.probabilities[110].should be_within(1e-10).of 0.0308191892
|
126
|
+
dice.probabilities.values.inject(:+).should be_within(1e-9).of 1.0
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '4d6 keep best 3' do
|
131
|
+
let(:dice) { GamesDice::Bunch.new( :sides => 6, :ndice => 4, :keep_mode => :keep_best, :keep_number => 3 ) }
|
132
|
+
|
133
|
+
it "should simulate rolling four six-sided dice and adding the best three values" do
|
134
|
+
[13,17,13,12,13,10,14].each do |expected_total|
|
135
|
+
dice.roll.should == expected_total
|
136
|
+
dice.result.should == expected_total
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should concisely explain each result" do
|
141
|
+
["3, 6, 2, 4. Keep: 3 + 4 + 6 = 13",
|
142
|
+
"5, 6, 3, 6. Keep: 5 + 6 + 6 = 17",
|
143
|
+
"2, 5, 6, 1. Keep: 2 + 5 + 6 = 13",
|
144
|
+
"5, 5, 2, 1. Keep: 2 + 5 + 5 = 12",
|
145
|
+
].each do |expected_explain|
|
146
|
+
dice.roll
|
147
|
+
dice.explain_result.should == expected_explain
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should calculate correct min, max = 3,18" do
|
152
|
+
dice.min.should == 3
|
153
|
+
dice.max.should == 18
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should have a mean value of roughly 12.2446" do
|
157
|
+
dice.expected_result.should be_within(1e-9).of 12.244598765
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should calculate probabilities correctly" do
|
161
|
+
dice.probabilities[3].should be_within(1e-10).of 1/1296.0
|
162
|
+
dice.probabilities[4].should be_within(1e-10).of 4/1296.0
|
163
|
+
dice.probabilities[5].should be_within(1e-10).of 10/1296.0
|
164
|
+
dice.probabilities[6].should be_within(1e-10).of 21/1296.0
|
165
|
+
dice.probabilities[7].should be_within(1e-10).of 38/1296.0
|
166
|
+
dice.probabilities[8].should be_within(1e-10).of 62/1296.0
|
167
|
+
dice.probabilities[9].should be_within(1e-10).of 91/1296.0
|
168
|
+
dice.probabilities[10].should be_within(1e-10).of 122/1296.0
|
169
|
+
dice.probabilities[11].should be_within(1e-10).of 148/1296.0
|
170
|
+
dice.probabilities[12].should be_within(1e-10).of 167/1296.0
|
171
|
+
dice.probabilities[13].should be_within(1e-10).of 172/1296.0
|
172
|
+
dice.probabilities[14].should be_within(1e-10).of 160/1296.0
|
173
|
+
dice.probabilities[15].should be_within(1e-10).of 131/1296.0
|
174
|
+
dice.probabilities[16].should be_within(1e-10).of 94/1296.0
|
175
|
+
dice.probabilities[17].should be_within(1e-10).of 54/1296.0
|
176
|
+
dice.probabilities[18].should be_within(1e-10).of 21/1296.0
|
177
|
+
dice.probabilities.values.inject(:+).should be_within(1e-9).of 1.0
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe '10d10 keep worst one' do
|
182
|
+
let(:dice) { GamesDice::Bunch.new( :sides => 10, :ndice => 10, :keep_mode => :keep_worst, :keep_number => 1 ) }
|
183
|
+
|
184
|
+
it "should simulate rolling ten ten-sided dice and keeping the worst value" do
|
185
|
+
[2,5,1,2,1,1,2].each do |expected_total|
|
186
|
+
dice.roll.should == expected_total
|
187
|
+
dice.result.should == expected_total
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should concisely explain each result" do
|
192
|
+
["3, 2, 8, 8, 5, 3, 7, 7, 6, 10. Keep: 2",
|
193
|
+
"7, 6, 9, 5, 5, 8, 10, 9, 5, 9. Keep: 5",
|
194
|
+
"3, 9, 1, 4, 3, 5, 7, 1, 10, 4. Keep: 1",
|
195
|
+
"7, 7, 6, 5, 2, 7, 4, 9, 7, 2. Keep: 2",
|
196
|
+
].each do |expected_explain|
|
197
|
+
dice.roll
|
198
|
+
dice.explain_result.should == expected_explain
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should calculate correct min, max = 1,10" do
|
203
|
+
dice.min.should == 1
|
204
|
+
dice.max.should == 10
|
205
|
+
end
|
206
|
+
|
207
|
+
it "should have a mean value of roughly 1.491" do
|
208
|
+
dice.expected_result.should be_within(1e-9).of 1.4914341925
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should calculate probabilities correctly" do
|
212
|
+
dice.probabilities[1].should be_within(1e-10).of 0.6513215599
|
213
|
+
dice.probabilities[2].should be_within(1e-10).of 0.2413042577
|
214
|
+
dice.probabilities[3].should be_within(1e-10).of 0.0791266575
|
215
|
+
dice.probabilities[4].should be_within(1e-10).of 0.0222009073
|
216
|
+
dice.probabilities[5].should be_within(1e-10).of 0.0050700551
|
217
|
+
dice.probabilities[6].should be_within(1e-10).of 0.0008717049
|
218
|
+
dice.probabilities[7].should be_within(1e-10).of 0.0000989527
|
219
|
+
dice.probabilities[8].should be_within(1e-10).of 0.0000058025
|
220
|
+
dice.probabilities[9].should be_within(1e-10).of 0.0000001023
|
221
|
+
dice.probabilities[10].should be_within(1e-18).of 1e-10
|
222
|
+
dice.probabilities.values.inject(:+).should be_within(1e-9).of 1.0
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe '5d10, re-roll and add on 10s, keep best 2' do
|
227
|
+
let(:dice) {
|
228
|
+
GamesDice::Bunch.new(
|
229
|
+
:sides => 10, :ndice => 5, :keep_mode => :keep_best, :keep_number => 2,
|
230
|
+
:rerolls => [GamesDice::RerollRule.new(10,:==,:reroll_add)]
|
231
|
+
) }
|
232
|
+
|
233
|
+
it "should simulate rolling five ten-sided 'exploding' dice and adding the best two values" do
|
234
|
+
[16,24,17,28,12,21,16].each do |expected_total|
|
235
|
+
dice.roll.should == expected_total
|
236
|
+
dice.result.should == expected_total
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should concisely explain each result" do
|
241
|
+
["3, 2, 8, 8, 5. Keep: 8 + 8 = 16",
|
242
|
+
"3, 7, 7, 6, [10+7] 17. Keep: 7 + 17 = 24",
|
243
|
+
"6, 9, 5, 5, 8. Keep: 8 + 9 = 17",
|
244
|
+
"[10+9] 19, 5, 9, 3, 9. Keep: 9 + 19 = 28",
|
245
|
+
].each do |expected_explain|
|
246
|
+
dice.roll
|
247
|
+
dice.explain_result.should == expected_explain
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should calculate correct min, max = 2, > 100" do
|
252
|
+
dice.min.should == 2
|
253
|
+
dice.max.should > 100
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should have a mean value of roughly 18.986" do
|
257
|
+
pending "Too slow"
|
258
|
+
dice.expected_result.should be_within(1e-9).of 18.9859925804
|
259
|
+
end
|
260
|
+
|
261
|
+
it "should calculate probabilities correctly" do
|
262
|
+
pending "Too slow"
|
263
|
+
dice.probabilities[2].should be_within(1e-10).of 0.00001
|
264
|
+
dice.probabilities[3].should be_within(1e-10).of 0.00005
|
265
|
+
dice.probabilities[4].should be_within(1e-10).of 0.00031
|
266
|
+
dice.probabilities[5].should be_within(1e-10).of 0.00080
|
267
|
+
dice.probabilities[6].should be_within(1e-10).of 0.00211
|
268
|
+
dice.probabilities[7].should be_within(1e-10).of 0.00405
|
269
|
+
dice.probabilities[8].should be_within(1e-10).of 0.00781
|
270
|
+
dice.probabilities[9].should be_within(1e-10).of 0.01280
|
271
|
+
dice.probabilities[10].should be_within(1e-10).of 0.02101
|
272
|
+
dice.probabilities[12].should be_within(1e-10).of 0.045715
|
273
|
+
dice.probabilities[13].should be_within(1e-10).of 0.060830
|
274
|
+
dice.probabilities[14].should be_within(1e-10).of 0.077915
|
275
|
+
dice.probabilities[15].should be_within(1e-10).of 0.090080
|
276
|
+
dice.probabilities[16].should be_within(1e-10).of 0.097935
|
277
|
+
dice.probabilities[17].should be_within(1e-10).of 0.091230
|
278
|
+
dice.probabilities[18].should be_within(1e-10).of 0.070015
|
279
|
+
dice.probabilities[19].should be_within(1e-10).of 0.020480
|
280
|
+
dice.probabilities[20].should be_within(1e-10).of 0.032805
|
281
|
+
dice.probabilities[22].should be_within(1e-10).of 0.0334626451
|
282
|
+
dice.probabilities[23].should be_within(1e-10).of 0.0338904805
|
283
|
+
dice.probabilities[24].should be_within(1e-10).of 0.0338098781
|
284
|
+
dice.probabilities[25].should be_within(1e-10).of 0.0328226480
|
285
|
+
dice.probabilities[26].should be_within(1e-10).of 0.0304393461
|
286
|
+
dice.probabilities[27].should be_within(1e-10).of 0.0260456005
|
287
|
+
dice.probabilities[28].should be_within(1e-10).of 0.0189361531
|
288
|
+
dice.probabilities[29].should be_within(1e-10).of 0.0082804480
|
289
|
+
dice.probabilities[30].should be_within(1e-10).of 0.0103524151
|
290
|
+
dice.probabilities.values.inject(:+).should be_within(1e-9).of 1.0
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
describe 'roll 2d20, keep best value' do
|
295
|
+
let(:dice) do
|
296
|
+
GamesDice::Bunch.new(
|
297
|
+
:sides => 20, :ndice => 2, :keep_mode => :keep_best, :keep_number => 1
|
298
|
+
)
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should simulate rolling two twenty-sided dice and keeping the best value" do
|
302
|
+
[19,18,14,6,13,10,16].each do |expected_total|
|
303
|
+
dice.roll.should == expected_total
|
304
|
+
dice.result.should == expected_total
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
it "should concisely explain each result" do
|
309
|
+
["19, 14. Keep: 19",
|
310
|
+
"18, 16. Keep: 18",
|
311
|
+
"5, 14. Keep: 14",
|
312
|
+
"3, 6. Keep: 6",
|
313
|
+
].each do |expected_explain|
|
314
|
+
dice.roll
|
315
|
+
dice.explain_result.should == expected_explain
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
it "should calculate correct min, max = 1,20" do
|
320
|
+
dice.min.should == 1
|
321
|
+
dice.max.should == 20
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should have a mean value of 13.825" do
|
325
|
+
dice.expected_result.should be_within(1e-9).of 13.825
|
326
|
+
end
|
327
|
+
|
328
|
+
it "should calculate probabilities correctly" do
|
329
|
+
dice.probabilities[1].should be_within(1e-10).of 1/400.0
|
330
|
+
dice.probabilities[2].should be_within(1e-10).of 3/400.0
|
331
|
+
dice.probabilities[3].should be_within(1e-10).of 5/400.0
|
332
|
+
dice.probabilities[4].should be_within(1e-10).of 7/400.0
|
333
|
+
dice.probabilities[5].should be_within(1e-10).of 9/400.0
|
334
|
+
dice.probabilities[6].should be_within(1e-10).of 11/400.0
|
335
|
+
dice.probabilities[7].should be_within(1e-10).of 13/400.0
|
336
|
+
dice.probabilities[8].should be_within(1e-10).of 15/400.0
|
337
|
+
dice.probabilities[9].should be_within(1e-10).of 17/400.0
|
338
|
+
dice.probabilities[10].should be_within(1e-10).of 19/400.0
|
339
|
+
dice.probabilities[11].should be_within(1e-10).of 21/400.0
|
340
|
+
dice.probabilities[12].should be_within(1e-10).of 23/400.0
|
341
|
+
dice.probabilities[13].should be_within(1e-10).of 25/400.0
|
342
|
+
dice.probabilities[14].should be_within(1e-10).of 27/400.0
|
343
|
+
dice.probabilities[15].should be_within(1e-10).of 29/400.0
|
344
|
+
dice.probabilities[16].should be_within(1e-10).of 31/400.0
|
345
|
+
dice.probabilities[17].should be_within(1e-10).of 33/400.0
|
346
|
+
dice.probabilities[18].should be_within(1e-10).of 35/400.0
|
347
|
+
dice.probabilities[19].should be_within(1e-10).of 37/400.0
|
348
|
+
dice.probabilities[20].should be_within(1e-10).of 39/400.0
|
349
|
+
dice.probabilities.values.inject(:+).should be_within(1e-9).of 1.0
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
describe 'roll 2d20, keep worst value' do
|
354
|
+
let(:dice) do
|
355
|
+
GamesDice::Bunch.new(
|
356
|
+
:sides => 20, :ndice => 2, :keep_mode => :keep_worst, :keep_number => 1
|
357
|
+
)
|
358
|
+
end
|
359
|
+
|
360
|
+
it "should simulate rolling two twenty-sided dice and keeping the best value" do
|
361
|
+
[14,16,5,3,7,5,9].each do |expected_total|
|
362
|
+
dice.roll.should == expected_total
|
363
|
+
dice.result.should == expected_total
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
it "should concisely explain each result" do
|
368
|
+
["19, 14. Keep: 14",
|
369
|
+
"18, 16. Keep: 16",
|
370
|
+
"5, 14. Keep: 5",
|
371
|
+
"3, 6. Keep: 3",
|
372
|
+
].each do |expected_explain|
|
373
|
+
dice.roll
|
374
|
+
dice.explain_result.should == expected_explain
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
it "should calculate correct min, max = 1,20" do
|
379
|
+
dice.min.should == 1
|
380
|
+
dice.max.should == 20
|
381
|
+
end
|
382
|
+
|
383
|
+
it "should have a mean value of 7.175" do
|
384
|
+
dice.expected_result.should be_within(1e-9).of 7.175
|
385
|
+
end
|
386
|
+
|
387
|
+
it "should calculate probabilities correctly" do
|
388
|
+
dice.probabilities[1].should be_within(1e-10).of 39/400.0
|
389
|
+
dice.probabilities[2].should be_within(1e-10).of 37/400.0
|
390
|
+
dice.probabilities[3].should be_within(1e-10).of 35/400.0
|
391
|
+
dice.probabilities[4].should be_within(1e-10).of 33/400.0
|
392
|
+
dice.probabilities[5].should be_within(1e-10).of 31/400.0
|
393
|
+
dice.probabilities[6].should be_within(1e-10).of 29/400.0
|
394
|
+
dice.probabilities[7].should be_within(1e-10).of 27/400.0
|
395
|
+
dice.probabilities[8].should be_within(1e-10).of 25/400.0
|
396
|
+
dice.probabilities[9].should be_within(1e-10).of 23/400.0
|
397
|
+
dice.probabilities[10].should be_within(1e-10).of 21/400.0
|
398
|
+
dice.probabilities[11].should be_within(1e-10).of 19/400.0
|
399
|
+
dice.probabilities[12].should be_within(1e-10).of 17/400.0
|
400
|
+
dice.probabilities[13].should be_within(1e-10).of 15/400.0
|
401
|
+
dice.probabilities[14].should be_within(1e-10).of 13/400.0
|
402
|
+
dice.probabilities[15].should be_within(1e-10).of 11/400.0
|
403
|
+
dice.probabilities[16].should be_within(1e-10).of 9/400.0
|
404
|
+
dice.probabilities[17].should be_within(1e-10).of 7/400.0
|
405
|
+
dice.probabilities[18].should be_within(1e-10).of 5/400.0
|
406
|
+
dice.probabilities[19].should be_within(1e-10).of 3/400.0
|
407
|
+
dice.probabilities[20].should be_within(1e-10).of 1/400.0
|
408
|
+
dice.probabilities.values.inject(:+).should be_within(1e-9).of 1.0
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
end
|
413
|
+
|
414
|
+
end
|