games_dice 0.3.12 → 0.4.2
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 +5 -5
- data/.github/workflows/ci.yml +38 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +53 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +18 -0
- data/Gemfile +12 -0
- data/README.md +11 -33
- data/Rakefile +10 -23
- data/ext/games_dice/extconf.rb +4 -22
- data/ext/games_dice/games_dice.c +1 -1
- data/ext/games_dice/probabilities.c +128 -9
- data/ext/games_dice/probabilities.h +1 -1
- data/games_dice.gemspec +22 -36
- data/lib/games_dice/bunch.rb +203 -247
- data/lib/games_dice/bunch_helpers.rb +68 -0
- data/lib/games_dice/complex_die.rb +151 -270
- data/lib/games_dice/complex_die_helpers.rb +260 -60
- data/lib/games_dice/constants.rb +10 -10
- data/lib/games_dice/dice.rb +153 -143
- data/lib/games_dice/die.rb +101 -97
- data/lib/games_dice/die_result.rb +206 -189
- data/lib/games_dice/map_rule.rb +72 -70
- data/lib/games_dice/marshal.rb +41 -13
- data/lib/games_dice/parser.rb +245 -218
- data/lib/games_dice/reroll_rule.rb +76 -77
- data/lib/games_dice/version.rb +4 -1
- data/lib/games_dice.rb +23 -25
- data/spec/bunch_spec.rb +399 -420
- data/spec/complex_die_spec.rb +314 -305
- data/spec/dice_spec.rb +33 -34
- data/spec/die_result_spec.rb +174 -181
- data/spec/die_spec.rb +81 -81
- data/spec/helpers.rb +25 -25
- data/spec/map_rule_spec.rb +40 -44
- data/spec/parser_spec.rb +106 -82
- data/spec/probability_spec.rb +522 -526
- data/spec/readme_spec.rb +410 -390
- data/spec/reroll_rule_spec.rb +40 -44
- metadata +17 -129
- data/.travis.yml +0 -14
- data/lib/games_dice/prob_helpers.rb +0 -259
- data/lib/games_dice/probabilities.rb +0 -244
data/spec/die_spec.rb
CHANGED
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
die
|
|
14
|
-
die.
|
|
15
|
-
die.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
die.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
die.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
die
|
|
42
|
-
die.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
probs
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
probs.p_eql(
|
|
56
|
-
probs.p_eql(
|
|
57
|
-
probs.p_eql(
|
|
58
|
-
probs.p_eql(
|
|
59
|
-
probs.p_eql(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
die
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'helpers'
|
|
4
|
+
|
|
5
|
+
describe GamesDice::Die do
|
|
6
|
+
before do
|
|
7
|
+
# Set state of default PRNG
|
|
8
|
+
srand(4567)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe '#new' do
|
|
12
|
+
it 'return an object that represents e.g. a six-sided die' do
|
|
13
|
+
die = described_class.new(6)
|
|
14
|
+
expect(die.min).to be 1
|
|
15
|
+
expect(die.max).to be 6
|
|
16
|
+
expect(die.sides).to be 6
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'accept any object with a rand(Integer) method as the second param' do
|
|
20
|
+
prng = TestPRNG.new
|
|
21
|
+
die = described_class.new(20, prng)
|
|
22
|
+
[16, 7, 3, 11, 16, 18, 20, 7].each do |expected|
|
|
23
|
+
expect(die.roll).to eql expected
|
|
24
|
+
expect(die.result).to eql expected
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe '#roll and #result' do
|
|
30
|
+
it "return results based on Ruby's internal rand() by default" do
|
|
31
|
+
die = described_class.new(10)
|
|
32
|
+
[5, 4, 10, 4, 7, 8, 1, 9].each do |expected|
|
|
33
|
+
expect(die.roll).to eql expected
|
|
34
|
+
expect(die.result).to eql expected
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#min and #max' do
|
|
40
|
+
it 'calculate correct min, max' do
|
|
41
|
+
die = described_class.new(20)
|
|
42
|
+
expect(die.min).to be 1
|
|
43
|
+
expect(die.max).to be 20
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '#probabilities' do
|
|
48
|
+
it "return the die's probability distribution as a GamesDice::Probabilities object" do
|
|
49
|
+
die = described_class.new(6)
|
|
50
|
+
probs = die.probabilities
|
|
51
|
+
expect(probs).to be_a GamesDice::Probabilities
|
|
52
|
+
|
|
53
|
+
expect(probs.to_h).to be_valid_distribution
|
|
54
|
+
|
|
55
|
+
expect(probs.p_eql(1)).to be_within(1e-10).of 1 / 6.0
|
|
56
|
+
expect(probs.p_eql(2)).to be_within(1e-10).of 1 / 6.0
|
|
57
|
+
expect(probs.p_eql(3)).to be_within(1e-10).of 1 / 6.0
|
|
58
|
+
expect(probs.p_eql(4)).to be_within(1e-10).of 1 / 6.0
|
|
59
|
+
expect(probs.p_eql(5)).to be_within(1e-10).of 1 / 6.0
|
|
60
|
+
expect(probs.p_eql(6)).to be_within(1e-10).of 1 / 6.0
|
|
61
|
+
|
|
62
|
+
expect(probs.expected).to be_within(1e-10).of 3.5
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe '#all_values' do
|
|
67
|
+
it 'return array with one result value per side' do
|
|
68
|
+
die = described_class.new(8)
|
|
69
|
+
expect(die.all_values).to eql [1, 2, 3, 4, 5, 6, 7, 8]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe '#each_value' do
|
|
74
|
+
it 'iterate through all sides of the die' do
|
|
75
|
+
die = described_class.new(10)
|
|
76
|
+
arr = []
|
|
77
|
+
die.each_value { |x| arr << x }
|
|
78
|
+
expect(arr).to eql [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
data/spec/helpers.rb
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# games_dice/spec/helpers.rb
|
|
2
4
|
require 'pathname'
|
|
3
|
-
require 'coveralls'
|
|
4
|
-
Coveralls.wear!
|
|
5
5
|
|
|
6
6
|
require 'games_dice'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
(Pathname.new(__FILE__).dirname + "fixtures" + name).to_s
|
|
8
|
+
def fixture(name)
|
|
9
|
+
"#{__dir__}/fixtures/#{name}"
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
# TestPRNG tests short predictable series
|
|
14
13
|
class TestPRNG
|
|
15
14
|
def initialize
|
|
16
|
-
@numbers = [0.123,0.234,0.345,0.999,0.876,0.765,0.543,0.111,0.333,0.777]
|
|
15
|
+
@numbers = [0.123, 0.234, 0.345, 0.999, 0.876, 0.765, 0.543, 0.111, 0.333, 0.777]
|
|
17
16
|
end
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
|
|
18
|
+
def rand(num)
|
|
19
|
+
Integer(num * @numbers.pop)
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# TestPRNGMax checks behaviour of re-rolls
|
|
24
24
|
class TestPRNGMax
|
|
25
|
-
def rand(
|
|
26
|
-
Integer(
|
|
25
|
+
def rand(num)
|
|
26
|
+
Integer(num) - 1
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# TestPRNGMin checks behaviour of re-rolls
|
|
31
31
|
class TestPRNGMin
|
|
32
|
-
def rand(
|
|
32
|
+
def rand(_num)
|
|
33
33
|
1
|
|
34
34
|
end
|
|
35
35
|
end
|
|
@@ -42,33 +42,33 @@ end
|
|
|
42
42
|
RSpec::Matchers.define :be_valid_distribution do
|
|
43
43
|
match do |given|
|
|
44
44
|
@error = nil
|
|
45
|
-
if !
|
|
45
|
+
if !given.is_a?(Hash)
|
|
46
46
|
@error = "distribution should be a Hash, but it is a #{given.class}"
|
|
47
|
-
elsif given.keys.any? { |k| !
|
|
48
|
-
bad_key = given.keys.first { |k| !
|
|
47
|
+
elsif given.keys.any? { |k| !k.is_a?(Integer) }
|
|
48
|
+
bad_key = given.keys.first { |k| !k.is_a?(Integer) }
|
|
49
49
|
@error = "all keys should be Integers, but found '#{bad_key.inspect}' which is a #{bad_key.class}"
|
|
50
|
-
elsif given.values.any? { |v| !
|
|
51
|
-
bad_value = given.values.find { |v| !
|
|
50
|
+
elsif given.values.any? { |v| !v.is_a?(Float) }
|
|
51
|
+
bad_value = given.values.find { |v| !v.is_a?(Float) }
|
|
52
52
|
@error = "all values should be Floats, but found '#{bad_value.inspect}' which is a #{bad_value.class}"
|
|
53
53
|
elsif given.values.any? { |v| v < 0.0 || v > 1.0 }
|
|
54
54
|
bad_value = given.values.find { |v| v < 0.0 || v > 1.0 }
|
|
55
55
|
@error = "all values should be in range (0.0..1.0), but found #{bad_value}"
|
|
56
|
-
elsif (1.0 - given.values.
|
|
57
|
-
total_probs = given.values.
|
|
56
|
+
elsif (1.0 - given.values.sum).abs > 1e-6
|
|
57
|
+
total_probs = given.values.sum
|
|
58
58
|
@error = "sum of values should be 1.0, but got #{total_probs}"
|
|
59
59
|
end
|
|
60
|
-
|
|
60
|
+
!@error
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
failure_message do |
|
|
64
|
-
@error
|
|
63
|
+
failure_message do |_given|
|
|
64
|
+
@error || 'Distribution is valid and complete'
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
failure_message_when_negated do |
|
|
68
|
-
|
|
67
|
+
failure_message_when_negated do |_given|
|
|
68
|
+
@error || 'Distribution is valid and complete'
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
description do |
|
|
72
|
-
|
|
71
|
+
description do |_given|
|
|
72
|
+
'a hash describing a complete probability distribution of integer results'
|
|
73
73
|
end
|
|
74
74
|
end
|
data/spec/map_rule_spec.rb
CHANGED
|
@@ -1,44 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
it
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
it
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
it
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
rule.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
rule.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'helpers'
|
|
4
|
+
|
|
5
|
+
describe GamesDice::MapRule do
|
|
6
|
+
describe '#new' do
|
|
7
|
+
it 'accept self-consistent operator/value pairs as a trigger' do
|
|
8
|
+
expect { described_class.new(5, :>, 1) }.not_to raise_error
|
|
9
|
+
expect { described_class.new(1..5, :member?, 17) }.not_to raise_error
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'reject inconsistent operator/value pairs for a trigger' do
|
|
13
|
+
expect { described_class.new(5, :member?, -1) }.to raise_error(ArgumentError)
|
|
14
|
+
expect { described_class.new(1..5, :>, 12) }.to raise_error(ArgumentError)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'reject non-Integer map results' do
|
|
18
|
+
expect { described_class.new(5, :>, :reroll_again) }.to raise_error(TypeError)
|
|
19
|
+
expect { described_class.new(1..5, :member?, 'foo') }.to raise_error(TypeError)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe '#map_from' do
|
|
24
|
+
it 'return the mapped value for a match' do
|
|
25
|
+
rule = described_class.new(5, :>, -1)
|
|
26
|
+
expect(rule.map_from(4)).to be(-1)
|
|
27
|
+
|
|
28
|
+
rule = described_class.new(1..5, :member?, 3)
|
|
29
|
+
expect(rule.map_from(4)).to be 3
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'return nil for no match' do
|
|
33
|
+
rule = described_class.new(5, :>, -1)
|
|
34
|
+
expect(rule.map_from(6)).to be_nil
|
|
35
|
+
|
|
36
|
+
rule = described_class.new(1..5, :member?, 3)
|
|
37
|
+
expect(rule.map_from(6)).to be_nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/spec/parser_spec.rb
CHANGED
|
@@ -1,82 +1,106 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
|
|
14
|
-
'
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
'
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
variations.each do |input,expected_output|
|
|
44
|
-
parser.parse(
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
it "
|
|
49
|
-
variations = {
|
|
50
|
-
'
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'helpers'
|
|
4
|
+
|
|
5
|
+
describe GamesDice::Parser do
|
|
6
|
+
describe '#parse' do
|
|
7
|
+
let(:parser) { described_class.new }
|
|
8
|
+
|
|
9
|
+
it 'parse simple dice sums' do
|
|
10
|
+
variations = {
|
|
11
|
+
'1d6' => { bunches: [{ ndice: 1, sides: 6, multiplier: 1 }], offset: 0 },
|
|
12
|
+
'2d8-1d4' => { bunches: [{ ndice: 2, sides: 8, multiplier: 1 }, { ndice: 1, sides: 4, multiplier: -1 }],
|
|
13
|
+
offset: 0 },
|
|
14
|
+
'+ 2d10 - 1d4 ' => {
|
|
15
|
+
bunches: [{ ndice: 2, sides: 10, multiplier: 1 },
|
|
16
|
+
{ ndice: 1, sides: 4, multiplier: -1 }], offset: 0
|
|
17
|
+
},
|
|
18
|
+
' + 3d6 + 12 ' => { bunches: [{ ndice: 3, sides: 6, multiplier: 1 }], offset: 12 },
|
|
19
|
+
'-7 + 2d4 + 1 ' => { bunches: [{ ndice: 2, sides: 4, multiplier: 1 }], offset: -6 },
|
|
20
|
+
'- 3 + 7d20 - 1 ' => { bunches: [{ ndice: 7, sides: 20, multiplier: 1 }], offset: -4 },
|
|
21
|
+
' - 2d4' => { bunches: [{ ndice: 2, sides: 4, multiplier: -1 }], offset: 0 },
|
|
22
|
+
'3d12+5+2d8+1d6' => {
|
|
23
|
+
bunches: [{ ndice: 3, sides: 12, multiplier: 1 }, { ndice: 2, sides: 8, multiplier: 1 },
|
|
24
|
+
{ ndice: 1, sides: 6, multiplier: 1 }], offset: 5
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
variations.each do |input, expected_output|
|
|
29
|
+
expect(parser.parse(input)).to eql expected_output
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "parse 'NdXrY' as 'roll N times X-sided dice, re-roll and replace a Y or less (once)'" do
|
|
34
|
+
variations = {
|
|
35
|
+
'1d6r1' => { bunches: [{ ndice: 1, sides: 6, multiplier: 1, rerolls: [[1, :>=, :reroll_replace]] }],
|
|
36
|
+
offset: 0 },
|
|
37
|
+
'2d20r7' => { bunches: [{ ndice: 2, sides: 20, multiplier: 1, rerolls: [[7, :>=, :reroll_replace]] }],
|
|
38
|
+
offset: 0 },
|
|
39
|
+
'1d8r2' => { bunches: [{ ndice: 1, sides: 8, multiplier: 1, rerolls: [[2, :>=, :reroll_replace]] }],
|
|
40
|
+
offset: 0 }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
variations.each do |input, expected_output|
|
|
44
|
+
expect(parser.parse(input)).to eql expected_output
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "parse 'NdXmZ' as 'roll N times X-sided dice, a value of Z or more equals 1 (success)'" do
|
|
49
|
+
variations = {
|
|
50
|
+
'5d6m6' => { bunches: [{ ndice: 5, sides: 6, multiplier: 1, maps: [[6, :<=, 1]] }],
|
|
51
|
+
offset: 0 },
|
|
52
|
+
'2d10m7' => { bunches: [{ ndice: 2, sides: 10, multiplier: 1, maps: [[7, :<=, 1]] }],
|
|
53
|
+
offset: 0 }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
variations.each do |input, expected_output|
|
|
57
|
+
expect(parser.parse(input)).to eql expected_output
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "parse 'NdXkC' as 'roll N times X-sided dice, add together the best C'" do
|
|
62
|
+
variations = {
|
|
63
|
+
'5d10k3' => { bunches: [{ ndice: 5, sides: 10, multiplier: 1, keep_mode: :keep_best, keep_number: 3 }],
|
|
64
|
+
offset: 0 }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
variations.each do |input, expected_output|
|
|
68
|
+
expect(parser.parse(input)).to eql expected_output
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "parse 'NdXx' as 'roll N times X-sided *exploding* dice'" do
|
|
73
|
+
variations = {
|
|
74
|
+
'5d10x' => { bunches: [{ ndice: 5, sides: 10, multiplier: 1, rerolls: [[10, :==, :reroll_add]] }],
|
|
75
|
+
offset: 0 },
|
|
76
|
+
'3d6x' => { bunches: [{ ndice: 3, sides: 6, multiplier: 1, rerolls: [[6, :==, :reroll_add]] }],
|
|
77
|
+
offset: 0 }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
variations.each do |input, expected_output|
|
|
81
|
+
expect(parser.parse(input)).to eql expected_output
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'successfully parse combinations of modifiers in any valid order' do
|
|
86
|
+
variations = {
|
|
87
|
+
'5d10r1x' => {
|
|
88
|
+
bunches: [{ ndice: 5, sides: 10, multiplier: 1,
|
|
89
|
+
rerolls: [[1, :>=, :reroll_replace], [10, :==, :reroll_add]] }], offset: 0
|
|
90
|
+
},
|
|
91
|
+
'3d6xk2' => {
|
|
92
|
+
bunches: [{ ndice: 3, sides: 6, multiplier: 1, rerolls: [[6, :==, :reroll_add]],
|
|
93
|
+
keep_mode: :keep_best, keep_number: 2 }], offset: 0
|
|
94
|
+
},
|
|
95
|
+
'4d6m8x' => {
|
|
96
|
+
bunches: [{ ndice: 4, sides: 6, multiplier: 1, maps: [[8, :<=, 1]],
|
|
97
|
+
rerolls: [[6, :==, :reroll_add]] }], offset: 0
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
variations.each do |input, expected_output|
|
|
102
|
+
expect(parser.parse(input)).to eql expected_output
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|