games_dice 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/neilslater/games_dice.png?branch=master)](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
|