games_dice 0.1.3 → 0.2.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.
- data/README.md +74 -18
- data/lib/games_dice/bunch.rb +6 -0
- data/lib/games_dice/dice.rb +31 -0
- data/lib/games_dice/parser.rb +31 -12
- data/lib/games_dice/version.rb +1 -1
- data/spec/parser_spec.rb +4 -4
- data/spec/readme_spec.rb +167 -2
- metadata +4 -4
data/README.md
CHANGED
@@ -2,33 +2,33 @@
|
|
2
2
|
|
3
3
|
[](http://travis-ci.org/neilslater/games_dice)
|
4
4
|
|
5
|
-
A library for simulating dice
|
6
|
-
role-playing and board games.
|
5
|
+
A library for simulating dice. Use it to construct dice-rolling systems used in role-playing and board games.
|
7
6
|
|
8
7
|
## Description
|
9
8
|
|
10
|
-
GamesDice
|
11
|
-
|
9
|
+
GamesDice can emulate a variety of rules-driven dice systems that are used to generate integer results
|
10
|
+
within a game.
|
12
11
|
|
13
|
-
|
14
|
-
player to make decisions or apply other rules from the game. There are no game mechanics implemented
|
15
|
-
in GamesDice (such as the chance to hit in a combat game).
|
16
|
-
|
17
|
-
The main features of GamesDice are
|
12
|
+
The main features of GamesDice are:
|
18
13
|
|
19
14
|
* Uses string dice descriptions, the basics of which are familiar to many game players e.g. '2d6 + 3'
|
20
|
-
* Supports common features of dice systems
|
15
|
+
* Supports some common features of dice systems:
|
21
16
|
* Re-rolls that replace or modify the previous roll
|
22
17
|
* Counting number of "successes" from a set of dice
|
23
18
|
* Keeping the best, or worst, results from a set of dice
|
24
19
|
* Can explain how a result was achieved in terms of the individual die rolls
|
25
|
-
* Can calculate probabilities and expected values (
|
20
|
+
* Can calculate probabilities and expected values (with some limitations)
|
21
|
+
|
22
|
+
There are no game mechanics implemented in GamesDice, such as the chance to hit in a fantasy combat
|
23
|
+
game. There is no support for player interaction within a roll, such as player choice on whether or
|
24
|
+
not to re-roll a specific die within a combined set. These things are of course possible if you use the
|
25
|
+
gem as-is, and add them as features within your project code.
|
26
26
|
|
27
27
|
## Special Note on Versions Prior to 1.0.0
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
As of version 0.2.0, the gem has the same feature set as planned for version
|
30
|
+
1.0.0. Versions between 0.2.0 and 1.0.0 are being used mainly to improve
|
31
|
+
code quality, documentation and performance.
|
32
32
|
|
33
33
|
## Installation
|
34
34
|
|
@@ -91,6 +91,17 @@ Returns the value from the last call to roll. This will be nil if no roll has be
|
|
91
91
|
dice.roll
|
92
92
|
dice.result # => 12
|
93
93
|
|
94
|
+
#### dice.explain_result
|
95
|
+
|
96
|
+
Returns a string that attempts to show how the result from the last call to roll was composed
|
97
|
+
from individual results. This will be nil if no roll has been made yet.
|
98
|
+
|
99
|
+
dice.explain_result # => nil
|
100
|
+
dice.roll # => 12
|
101
|
+
dice.explain_result # => "3d6: 4 + 2 + 6 = 12"
|
102
|
+
|
103
|
+
The exact format is the subject of refinement in future versions of the gem.
|
104
|
+
|
94
105
|
#### dice.max
|
95
106
|
|
96
107
|
Returns the maximum possible value from a roll of the dice. Dice with the possibility of rolling
|
@@ -255,13 +266,13 @@ Where:
|
|
255
266
|
* add - add result of reroll to running total, and ignore any subtract rules
|
256
267
|
* subtract - subtract result of reroll from running total, and reverse sense of any further add results
|
257
268
|
* use_best - use the new value if it is higher than the existing value
|
258
|
-
* use_worst - use the new value if it is
|
269
|
+
* use_worst - use the new value if it is lower than the existing value
|
259
270
|
* LIMIT is an integer that sets the maximum number of times that the rule can be triggered, the default is 1000
|
260
271
|
|
261
272
|
Examples:
|
262
273
|
|
263
274
|
1d6r:1. # Same as "1d6r1"
|
264
|
-
1d10r:10,replace,1. #
|
275
|
+
1d10r:10,replace,1. # Roll a 10-sided die, re-roll a result of 10 and take the value of the second roll
|
265
276
|
1d20r:<=10,use_best,1. # Roll a 20-sided die, re-roll a result if 10 or lower, and use best result
|
266
277
|
|
267
278
|
#### Maps
|
@@ -275,6 +286,24 @@ The simple form specifies a value above which the result is considered to be 1,
|
|
275
286
|
|
276
287
|
When rolled, this will score from 0 to 3 - the number of the ten-sided dice that scored 6 or higher.
|
277
288
|
|
289
|
+
The full version of this modifier, allows you to specify from 1 to 3 parameters:
|
290
|
+
|
291
|
+
3d10m:[VALUE_COMPARISON],[MAP_VALUE],[DESCRIPTION].
|
292
|
+
|
293
|
+
Where:
|
294
|
+
|
295
|
+
* VALUE_COMPARISON is one of >, >= (default), ==, <= < plus an integer to set conditions on when the map should occur
|
296
|
+
* MAP_VALUE is an integer that will be used in place of a result from a die, default value is 1
|
297
|
+
* maps are tested in order that they are declared, and first one that matches is applied
|
298
|
+
* when at least one map has been defined, all unmapped values default to 0
|
299
|
+
* DESCRIPTION is a word or character to use to denote the map in any explanation
|
300
|
+
|
301
|
+
Examples:
|
302
|
+
|
303
|
+
9d6x.m:10. # Roll 9 six-sided "exploding" dice, and count 1 for any result of 10 or more
|
304
|
+
9d6x.m:10,1,S. # Same as above, but with each success marked with "S" in the explanation
|
305
|
+
5d10m:>=6,1,S.m:==1,-1,F. # Roll 5 ten-sided dice, count 1 for any result of 6 or more, or -1 for any result of 1
|
306
|
+
|
278
307
|
#### Keepers
|
279
308
|
|
280
309
|
You can specify that only a sub-set of highest or lowest dice values will contribute to the final
|
@@ -287,6 +316,30 @@ The simple form indicates the number of highest value dice to keep.
|
|
287
316
|
When rolled, this will score from 2 to 20 - the sum of the two highest scoring ten-sided dice, out of
|
288
317
|
five.
|
289
318
|
|
319
|
+
The full version of this modifier, allows you to specify from 1 to 2 parameters:
|
320
|
+
|
321
|
+
5d10k:[KEEP_NUM],[KEEP_TYPE].
|
322
|
+
|
323
|
+
Where:
|
324
|
+
|
325
|
+
* KEEP_NUM is an integer specifying the number of dice to keep.
|
326
|
+
* KEEP_TYPE is one of
|
327
|
+
* best - keep highest values and add them together
|
328
|
+
* worst - keep lowest values and add them together
|
329
|
+
|
330
|
+
Examples:
|
331
|
+
|
332
|
+
4d6k:3.r:1,replace,1. # Roll 4 six-sided dice, re-roll any 1s, and keep best 3.
|
333
|
+
2d20k:1,worst. # Roll 2 twenty-sided dice, return lowest of the two results.
|
334
|
+
|
335
|
+
#### Combinations
|
336
|
+
|
337
|
+
* When there are many modifiers, they are applied in strict order:
|
338
|
+
* First by type: re-rolls, maps, keepers
|
339
|
+
* Then according to the order they were specified
|
340
|
+
* A maximum of one re-roll modifier, and one map modifier are applied to each individual die rolled
|
341
|
+
* Only one keepers modifier is applied per dice type. Specifying a second one will cause an error
|
342
|
+
|
290
343
|
#### Aliases
|
291
344
|
|
292
345
|
Some combinations of modifiers crop up in well-known games, and have been allocated single-character
|
@@ -296,8 +349,8 @@ This is an alias for "exploding" dice:
|
|
296
349
|
|
297
350
|
5d10x # Same as '5d10r:10,add.'
|
298
351
|
|
299
|
-
When rolled, this will score from 5 to theoretically any number, as results of 10 on any
|
300
|
-
die rolls again and the result is added on.
|
352
|
+
When rolled, this will score from 5 to theoretically any higher number, as results of 10 on any
|
353
|
+
die mean that die rolls again and the result is added on.
|
301
354
|
|
302
355
|
## Contributing
|
303
356
|
|
@@ -306,3 +359,6 @@ die rolls again and the result is added on.
|
|
306
359
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
307
360
|
4. Push to the branch (`git push origin my-new-feature`)
|
308
361
|
5. Create new Pull Request
|
362
|
+
|
363
|
+
I am always interested to receive information about dice rolling schemes that this library could or
|
364
|
+
should include in its repertoire.
|
data/lib/games_dice/bunch.rb
CHANGED
@@ -81,6 +81,12 @@ class GamesDice::Bunch
|
|
81
81
|
# after calling #roll, this is set to the final integer value from using the dice as specified
|
82
82
|
attr_reader :result
|
83
83
|
|
84
|
+
# Needs refinement. Returns best available string description of the bunch.
|
85
|
+
def label
|
86
|
+
return @name if @name != ''
|
87
|
+
return @ndice.to_s + 'd' + @sides.to_s
|
88
|
+
end
|
89
|
+
|
84
90
|
# either nil, or an array of GamesDice::RerollRule objects that are assessed on each roll of #single_die
|
85
91
|
# Reroll types :reroll_new_die and :reroll_new_keeper do not affect the #single_die, but are instead
|
86
92
|
# assessed in this container object
|
data/lib/games_dice/dice.rb
CHANGED
@@ -66,4 +66,35 @@ class GamesDice::Dice
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
def explain_result
|
70
|
+
return nil unless @result
|
71
|
+
explanations = @bunches.map { |bunch| bunch.label + ": " + bunch.explain_result }
|
72
|
+
|
73
|
+
if explanations.count == 0
|
74
|
+
return @offset.to_s
|
75
|
+
end
|
76
|
+
|
77
|
+
if explanations.count == 1
|
78
|
+
if @offset !=0
|
79
|
+
return explanations[0] + '. ' + array_to_sum( [ @bunches[0].result, @offset ] )
|
80
|
+
else
|
81
|
+
return explanations[0]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
bunch_values = @bunch_multipliers.zip(@bunches).map { |m,b| m * b.result }
|
86
|
+
bunch_values << @offset if @offset != 0
|
87
|
+
explanations << array_to_sum( bunch_values )
|
88
|
+
return explanations.join('. ')
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def array_to_sum array
|
94
|
+
sum_parts = [ array.first < 0 ? '-' + array.first.abs.to_s : array.first.to_s ]
|
95
|
+
sum_parts += array.drop(1).map { |n| n < 0 ? '- ' + n.abs.to_s : '+ ' + n.to_s }
|
96
|
+
sum_parts += [ '=', array.inject(:+) ]
|
97
|
+
sum_parts.join(' ')
|
98
|
+
end
|
99
|
+
|
69
100
|
end # class Dice
|
data/lib/games_dice/parser.rb
CHANGED
@@ -3,10 +3,9 @@ require 'parslet'
|
|
3
3
|
# converts string dice descriptions to data usable for the GamesDice::Dice constructor
|
4
4
|
class GamesDice::Parser < Parslet::Parser
|
5
5
|
|
6
|
-
#
|
7
|
-
# use of Parslet, and could do with logical a clean-up.
|
8
|
-
|
6
|
+
# Parslet rules that define the dice string grammar.
|
9
7
|
rule(:integer) { match('[0-9]').repeat(1) }
|
8
|
+
rule(:plus_minus_integer) { ( match('[+-]') >> integer ) | integer }
|
10
9
|
rule(:range) { integer.as(:range_start) >> str('..') >> integer.as(:range_end) }
|
11
10
|
rule(:dlabel) { match('[d]') }
|
12
11
|
rule(:space) { match('\s').repeat(1) }
|
@@ -33,20 +32,25 @@ class GamesDice::Parser < Parslet::Parser
|
|
33
32
|
rule(:stop) { str('.') }
|
34
33
|
|
35
34
|
rule(:condition_only) { opint_or_int.as(:condition) }
|
35
|
+
rule(:num_only) { integer.as(:num) }
|
36
|
+
|
36
37
|
|
37
38
|
rule(:condition_and_type) { opint_or_int.as(:condition) >> comma >> ctl_string.as(:type) }
|
38
|
-
rule(:condition_and_num) { opint_or_int.as(:condition) >> comma >>
|
39
|
+
rule(:condition_and_num) { opint_or_int.as(:condition) >> comma >> plus_minus_integer.as(:num) }
|
39
40
|
|
40
41
|
rule(:condition_type_and_num) { opint_or_int.as(:condition) >> comma >> ctl_string.as(:type) >> comma >> integer.as(:num) }
|
41
|
-
rule(:condition_num_and_output) { opint_or_int.as(:condition) >> comma >>
|
42
|
+
rule(:condition_num_and_output) { opint_or_int.as(:condition) >> comma >> plus_minus_integer.as(:num) >> comma >> output_string.as(:output) }
|
43
|
+
rule(:num_and_type) { integer.as(:num) >> comma >> ctl_string.as(:type) }
|
42
44
|
|
43
45
|
rule(:reroll_params) { condition_type_and_num | condition_and_type | condition_only }
|
44
46
|
rule(:map_params) { condition_num_and_output | condition_and_num | condition_only }
|
47
|
+
rule(:keeper_params) { num_and_type | num_only }
|
45
48
|
|
46
49
|
rule(:full_reroll) { reroll_label >> str(':') >> reroll_params >> stop }
|
47
50
|
rule(:full_map) { map_label >> str(':') >> map_params >> stop }
|
51
|
+
rule(:full_keepers) { keep_label >> str(':') >> keeper_params >> stop }
|
48
52
|
|
49
|
-
rule(:complex_modifier) { full_reroll | full_map }
|
53
|
+
rule(:complex_modifier) { full_reroll | full_map | full_keepers }
|
50
54
|
|
51
55
|
rule(:bunch_modifier) { complex_modifier | ( single_modifier >> stop.maybe ) | ( simple_modifier >> stop.maybe ) }
|
52
56
|
rule(:bunch) { bunch_start >> bunch_modifier.repeat.as(:mods) }
|
@@ -58,13 +62,11 @@ class GamesDice::Parser < Parslet::Parser
|
|
58
62
|
rule(:expressions) { dice_expression.repeat.as(:bunches) }
|
59
63
|
root :expressions
|
60
64
|
|
61
|
-
def parse dice_description
|
65
|
+
def parse dice_description
|
62
66
|
dice_description = dice_description.to_s.strip
|
63
|
-
dice_name ||= dice_description
|
64
67
|
# Force first item to start '+' for simpler parse rules
|
65
68
|
dice_description = '+' + dice_description unless dice_description =~ /\A[+-]/
|
66
69
|
dice_expressions = super( dice_description )
|
67
|
-
|
68
70
|
{ :bunches => collect_bunches( dice_expressions ), :offset => collect_offset( dice_expressions ) }
|
69
71
|
end
|
70
72
|
|
@@ -137,9 +139,10 @@ class GamesDice::Parser < Parslet::Parser
|
|
137
139
|
def collect_reroll_rule reroll_mod, out_hash
|
138
140
|
out_hash[:rerolls] ||= []
|
139
141
|
if reroll_mod[:simple_value]
|
140
|
-
out_hash[:rerolls] << [ reroll_mod[:simple_value].to_i, :>=, :reroll_replace
|
142
|
+
out_hash[:rerolls] << [ reroll_mod[:simple_value].to_i, :>=, :reroll_replace ]
|
141
143
|
return
|
142
144
|
end
|
145
|
+
|
143
146
|
# Typical reroll_mod: {:reroll=>"r"@5, :condition=>{:compare_num=>"10"@7}, :type=>"add"@10}
|
144
147
|
op = get_op_symbol( reroll_mod[:condition][:comparison] || '==' )
|
145
148
|
v = reroll_mod[:condition][:compare_num].to_i
|
@@ -154,12 +157,16 @@ class GamesDice::Parser < Parslet::Parser
|
|
154
157
|
|
155
158
|
# Called for any parsed keeper mode
|
156
159
|
def collect_keeper_rule keeper_mod, out_hash
|
160
|
+
raise "Cannot set keepers for a bunch twice" if out_hash[:keep_mode]
|
157
161
|
if keeper_mod[:simple_value]
|
158
162
|
out_hash[:keep_mode] = :keep_best
|
159
163
|
out_hash[:keep_number] = keeper_mod[:simple_value].to_i
|
160
164
|
return
|
161
165
|
end
|
162
|
-
|
166
|
+
|
167
|
+
# Typical keeper_mod: {:keep=>"k"@5, :num=>"1"@7, :type=>"worst"@9}
|
168
|
+
out_hash[:keep_number] = keeper_mod[:num].to_i
|
169
|
+
out_hash[:keep_mode] = ( 'keep_' + ( keeper_mod[:type] || 'best' ) ).to_sym
|
163
170
|
end
|
164
171
|
|
165
172
|
# Called for any parsed map mode
|
@@ -170,7 +177,19 @@ class GamesDice::Parser < Parslet::Parser
|
|
170
177
|
return
|
171
178
|
end
|
172
179
|
|
173
|
-
# Typical
|
180
|
+
# Typical map_mod: {:map=>"m"@4, :condition=>{:compare_num=>"5"@6}, :num=>"2"@8, :output=>"Qwerty"@10}
|
181
|
+
op = get_op_symbol( map_mod[:condition][:comparison] || '>=' )
|
182
|
+
v = map_mod[:condition][:compare_num].to_i
|
183
|
+
out_val = 1
|
184
|
+
if map_mod[:num]
|
185
|
+
out_val = map_mod[:num].to_i
|
186
|
+
end
|
187
|
+
|
188
|
+
if map_mod[:output]
|
189
|
+
out_hash[:maps] << [ v, op, out_val, map_mod[:output].to_s ]
|
190
|
+
else
|
191
|
+
out_hash[:maps] << [ v, op, out_val ]
|
192
|
+
end
|
174
193
|
end
|
175
194
|
|
176
195
|
# The dice description language uses (r).op.x, whilst GamesDice::RerollRule uses x.op.(r), so
|
data/lib/games_dice/version.rb
CHANGED
data/spec/parser_spec.rb
CHANGED
@@ -24,9 +24,9 @@ describe GamesDice::Parser do
|
|
24
24
|
|
25
25
|
it "should parse 'NdXrY' as 'roll N times X-sided dice, re-roll and replace a Y or less (once)'" do
|
26
26
|
variations = {
|
27
|
-
'1d6r1' => { :bunches => [{:ndice=>1, :sides=>6, :multiplier=>1, :rerolls=>[ [1,:>=,:reroll_replace
|
28
|
-
'2d20r7' => { :bunches => [{:ndice=>2, :sides=>20, :multiplier=>1, :rerolls=>[ [7,:>=,:reroll_replace
|
29
|
-
'1d8r2' => { :bunches => [{:ndice=>1, :sides=>8, :multiplier=>1, :rerolls=>[ [2,:>=,:reroll_replace
|
27
|
+
'1d6r1' => { :bunches => [{:ndice=>1, :sides=>6, :multiplier=>1, :rerolls=>[ [1,:>=,:reroll_replace] ]}], :offset => 0 },
|
28
|
+
'2d20r7' => { :bunches => [{:ndice=>2, :sides=>20, :multiplier=>1, :rerolls=>[ [7,:>=,:reroll_replace] ]}], :offset => 0 },
|
29
|
+
'1d8r2' => { :bunches => [{:ndice=>1, :sides=>8, :multiplier=>1, :rerolls=>[ [2,:>=,:reroll_replace] ]}], :offset => 0 },
|
30
30
|
}
|
31
31
|
|
32
32
|
variations.each do |input,expected_output|
|
@@ -68,7 +68,7 @@ describe GamesDice::Parser do
|
|
68
68
|
|
69
69
|
it "should successfully parse combinations of modifiers in any valid order" do
|
70
70
|
variations = {
|
71
|
-
'5d10r1x' => { :bunches => [{:ndice=>5, :sides=>10, :multiplier=>1, :rerolls=>[ [1,:>=,:reroll_replace
|
71
|
+
'5d10r1x' => { :bunches => [{:ndice=>5, :sides=>10, :multiplier=>1, :rerolls=>[ [1,:>=,:reroll_replace], [10,:==,:reroll_add] ]}], :offset => 0 },
|
72
72
|
'3d6xk2' => { :bunches => [{:ndice=>3, :sides=>6, :multiplier=>1, :rerolls=>[ [6,:==,:reroll_add] ], :keep_mode=>:keep_best, :keep_number=>2 }], :offset => 0 },
|
73
73
|
'4d6m8x' => { :bunches => [{:ndice=>4, :sides=>6, :multiplier=>1, :maps=>[ [8,:<=,1] ], :rerolls=>[ [6,:==,:reroll_add] ] }], :offset => 0 },
|
74
74
|
}
|
data/spec/readme_spec.rb
CHANGED
@@ -57,6 +57,25 @@ describe GamesDice::Dice do
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
+
describe "#explain_result" do
|
61
|
+
it "attempts to show how the result from the last call to roll was composed" do
|
62
|
+
expected_results = [
|
63
|
+
"3d6: 3 + 6 + 2 = 11",
|
64
|
+
"3d6: 4 + 5 + 6 = 15",
|
65
|
+
"3d6: 3 + 6 + 2 = 11",
|
66
|
+
"3d6: 5 + 6 + 1 = 12"
|
67
|
+
]
|
68
|
+
expected_results.each do |expected|
|
69
|
+
dice.roll
|
70
|
+
dice.explain_result.should == expected
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "will be nil if no roll has been made yet" do
|
75
|
+
dice.explain_result.should be_nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
60
79
|
describe "#max" do
|
61
80
|
it "returns the maximum possible value from a roll of the dice" do
|
62
81
|
dice.max.should == 18
|
@@ -210,9 +229,155 @@ describe 'String Dice Description' do
|
|
210
229
|
end
|
211
230
|
|
212
231
|
describe "'5d10x'" do
|
213
|
-
it "
|
232
|
+
it "is the same as '5d10r:10,add.'" do
|
233
|
+
srand(235241)
|
214
234
|
d = GamesDice.create '5d10x'
|
215
|
-
(1..
|
235
|
+
results1 = (1..50).map { d.roll }
|
236
|
+
|
237
|
+
srand(235241)
|
238
|
+
d = GamesDice.create '5d10r:10,add.'
|
239
|
+
results2 = (1..50).map { d.roll }
|
240
|
+
|
241
|
+
results1.should == results2
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
describe "'1d6r:1.'" do
|
246
|
+
it "should return same as '1d6r1'" do
|
247
|
+
srand(235241)
|
248
|
+
d = GamesDice.create '1d6r:1.'
|
249
|
+
results1 = (1..50).map { d.roll }
|
250
|
+
|
251
|
+
srand(235241)
|
252
|
+
d = GamesDice.create '1d6r1'
|
253
|
+
results2 = (1..50).map { d.roll }
|
254
|
+
|
255
|
+
results1.should == results2
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe "'1d10r:10,replace,1.'" do
|
260
|
+
it "should roll a 10-sided die, re-roll a result of 10 and take the value of the second roll" do
|
261
|
+
d = GamesDice.create '1d10r:10,replace,1.'
|
262
|
+
(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]
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe "'1d20r:<=10,use_best,1.'" do
|
267
|
+
it "should roll a 20-sided die, re-roll a result if 10 or lower, and use best result" do
|
268
|
+
d = GamesDice.create '1d20r:<=10,use_best,1.'
|
269
|
+
(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 ]
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
describe "'5d10r:10,add.k2', '5d10xk2' and '5d10x.k2'" do
|
274
|
+
it "should all be equivalent" do
|
275
|
+
srand(135241)
|
276
|
+
d = GamesDice.create '5d10r:10,add.k2'
|
277
|
+
results1 = (1..50).map { d.roll }
|
278
|
+
|
279
|
+
srand(135241)
|
280
|
+
d = GamesDice.create '5d10xk2'
|
281
|
+
results2 = (1..50).map { d.roll }
|
282
|
+
|
283
|
+
srand(135241)
|
284
|
+
d = GamesDice.create '5d10x.k2'
|
285
|
+
results3 = (1..50).map { d.roll }
|
286
|
+
|
287
|
+
results1.should == results2
|
288
|
+
results2.should == results3
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe "'5d10r:>8,add.'" do
|
293
|
+
it "returns expected results from rolling" do
|
294
|
+
d = GamesDice.create '5d10r:>8,add.'
|
295
|
+
(1..5).map { |n| d.roll }.should == [22, 22, 31, 64, 26]
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
describe "'9d6x.m:10.'" do
|
300
|
+
it "returns expected results from rolling" do
|
301
|
+
d = GamesDice.create '9d6x.m:10.'
|
302
|
+
(1..5).map { |n| d.roll }.should == [1, 2, 1, 1, 1]
|
303
|
+
end
|
304
|
+
it "can be explained as number of exploding dice scoring 10+" do
|
305
|
+
d = GamesDice.create '9d6x.m:10.'
|
306
|
+
(1..5).map { |n| d.roll; d.explain_result }.should == [
|
307
|
+
"9d6: [6+3] 9, 2, 3, 4, [6+4] 10, 2, [6+3] 9, 3, 5. Successes: 1",
|
308
|
+
"9d6: [6+6+3] 15, [6+5] 11, 2, 1, 4, 2, 1, 3, 5. Successes: 2",
|
309
|
+
"9d6: 1, [6+6+1] 13, 2, 1, 1, 3, [6+1] 7, 5, 4. Successes: 1",
|
310
|
+
"9d6: [6+4] 10, 3, 4, 5, 5, 1, [6+3] 9, 3, 5. Successes: 1",
|
311
|
+
"9d6: [6+3] 9, 3, [6+5] 11, 4, 2, 2, 1, 4, 5. Successes: 1"
|
312
|
+
]
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
describe "'9d6x.m:10,1,S.'" do
|
317
|
+
it "returns expected results from rolling" do
|
318
|
+
d = GamesDice.create '9d6x.m:10,1,S.'
|
319
|
+
(1..5).map { |n| d.roll }.should == [1, 2, 1, 1, 1]
|
320
|
+
end
|
321
|
+
it "includes the string 'S' next to each success" do
|
322
|
+
d = GamesDice.create '9d6x.m:10,1,S.'
|
323
|
+
(1..5).map { |n| d.roll; d.explain_result }.should == [
|
324
|
+
"9d6: [6+3] 9, 2, 3, 4, [6+4] 10 S, 2, [6+3] 9, 3, 5. Successes: 1",
|
325
|
+
"9d6: [6+6+3] 15 S, [6+5] 11 S, 2, 1, 4, 2, 1, 3, 5. Successes: 2",
|
326
|
+
"9d6: 1, [6+6+1] 13 S, 2, 1, 1, 3, [6+1] 7, 5, 4. Successes: 1",
|
327
|
+
"9d6: [6+4] 10 S, 3, 4, 5, 5, 1, [6+3] 9, 3, 5. Successes: 1",
|
328
|
+
"9d6: [6+3] 9, 3, [6+5] 11 S, 4, 2, 2, 1, 4, 5. Successes: 1"
|
329
|
+
]
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
describe "'5d10m:>=6,1,S.m:==1,-1,F.'" do
|
334
|
+
it "returns expected results from rolling" do
|
335
|
+
d = GamesDice.create '5d10m:>=6,1,S.m:==1,-1,F.'
|
336
|
+
(1..10).map { |n| d.roll }.should == [2, 2, 4, 3, 2, 1, 1, 3, 3, 0]
|
337
|
+
end
|
338
|
+
it "includes the string 'S' next to each success, and 'F' next to each 'fumble'" do
|
339
|
+
d = GamesDice.create '5d10m:>=6,1,S.m:==1,-1,F.'
|
340
|
+
(1..5).map { |n| d.roll; d.explain_result }.should == [
|
341
|
+
"5d10: 2, 3, 4, 7 S, 6 S. Successes: 2",
|
342
|
+
"5d10: 7 S, 4, 2, 6 S, 3. Successes: 2",
|
343
|
+
"5d10: 7 S, 5, 6 S, 7 S, 6 S. Successes: 4",
|
344
|
+
"5d10: 6 S, 5, 10 S, 9 S, 4. Successes: 3",
|
345
|
+
"5d10: 10 S, 9 S, 8 S, 3, 1 F. Successes: 2"
|
346
|
+
]
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
describe "'4d6k:3.r:1,replace,1.'" do
|
351
|
+
it "represents roll 4 six-sided dice, re-roll any 1s, and keep best 3." do
|
352
|
+
d = GamesDice.create '4d6k:3.r:1,replace,1.'
|
353
|
+
(1..10).map { |n| d.roll }.should == [12, 14, 14, 18, 11, 17, 11, 15, 14, 14]
|
354
|
+
end
|
355
|
+
it "includes re-rolls and keeper choice in explanations" do
|
356
|
+
d = GamesDice.create '4d6k:3.r:1,replace,1.'
|
357
|
+
(1..5).map { |n| d.roll; d.explain_result }.should == [
|
358
|
+
"4d6: 6, 3, 2, 3. Keep: 3 + 3 + 6 = 12",
|
359
|
+
"4d6: 4, 6, 4, 2. Keep: 4 + 4 + 6 = 14",
|
360
|
+
"4d6: 6, 3, 3, 5. Keep: 3 + 5 + 6 = 14",
|
361
|
+
"4d6: 6, 6, 3, 6. Keep: 6 + 6 + 6 = 18",
|
362
|
+
"4d6: 5, 2, [1|4] 4, 2. Keep: 2 + 4 + 5 = 11"
|
363
|
+
]
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
describe "'2d20k:1,worst.'" do
|
368
|
+
it "represents roll 2 twenty-sided dice, return lowest of the two results" do
|
369
|
+
d = GamesDice.create '2d20k:1,worst.'
|
370
|
+
(1..10).map { |n| d.roll }.should == [18, 6, 2, 3, 5, 10, 15, 1, 7, 10]
|
371
|
+
end
|
372
|
+
it "includes keeper choice in explanations" do
|
373
|
+
d = GamesDice.create '2d20k:1,worst.'
|
374
|
+
(1..5).map { |n| d.roll; d.explain_result }.should == [
|
375
|
+
"2d20: 18, 19. Keep: 18",
|
376
|
+
"2d20: 20, 6. Keep: 6",
|
377
|
+
"2d20: 20, 2. Keep: 2",
|
378
|
+
"2d20: 3, 11. Keep: 3",
|
379
|
+
"2d20: 5, 7. Keep: 5"
|
380
|
+
]
|
216
381
|
end
|
217
382
|
end
|
218
383
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: games_dice
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -112,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
112
|
version: '0'
|
113
113
|
segments:
|
114
114
|
- 0
|
115
|
-
hash:
|
115
|
+
hash: 4046591063539756555
|
116
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
117
|
none: false
|
118
118
|
requirements:
|
@@ -121,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
121
|
version: '0'
|
122
122
|
segments:
|
123
123
|
- 0
|
124
|
-
hash:
|
124
|
+
hash: 4046591063539756555
|
125
125
|
requirements: []
|
126
126
|
rubyforge_project:
|
127
127
|
rubygems_version: 1.8.24
|