games_dice 0.3.12 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/spec/die_spec.rb CHANGED
@@ -1,81 +1,81 @@
1
- require 'helpers'
2
-
3
- describe GamesDice::Die do
4
-
5
- before do
6
- # Set state of default PRNG
7
- srand(4567)
8
- end
9
-
10
- describe "#new" do
11
- it "should return an object that represents e.g. a six-sided die" do
12
- die = GamesDice::Die.new(6)
13
- die.min.should == 1
14
- die.max.should == 6
15
- die.sides.should == 6
16
- end
17
-
18
- it "should accept any object with a rand(Integer) method as the second param" do
19
- prng = TestPRNG.new()
20
- die = GamesDice::Die.new(20,prng)
21
- [16,7,3,11,16,18,20,7].each do |expected|
22
- die.roll.should == expected
23
- die.result.should == expected
24
- end
25
- end
26
- end
27
-
28
- describe "#roll and #result" do
29
- it "should return results based on Ruby's internal rand() by default" do
30
- die = GamesDice::Die.new(10)
31
- [5,4,10,4,7,8,1,9].each do |expected|
32
- die.roll.should == expected
33
- die.result.should == expected
34
- end
35
- end
36
- end
37
-
38
- describe "#min and #max" do
39
- it "should calculate correct min, max" do
40
- die = GamesDice::Die.new(20)
41
- die.min.should == 1
42
- die.max.should == 20
43
- end
44
- end
45
-
46
- describe "#probabilities" do
47
- it "should return the die's probability distribution as a GamesDice::Probabilities object" do
48
- die = GamesDice::Die.new(6)
49
- probs = die.probabilities
50
- probs.should be_a GamesDice::Probabilities
51
-
52
- probs.to_h.should be_valid_distribution
53
-
54
- probs.p_eql(1).should be_within(1e-10).of 1/6.0
55
- probs.p_eql(2).should be_within(1e-10).of 1/6.0
56
- probs.p_eql(3).should be_within(1e-10).of 1/6.0
57
- probs.p_eql(4).should be_within(1e-10).of 1/6.0
58
- probs.p_eql(5).should be_within(1e-10).of 1/6.0
59
- probs.p_eql(6).should be_within(1e-10).of 1/6.0
60
-
61
- probs.expected.should be_within(1e-10).of 3.5
62
- end
63
- end
64
-
65
- describe "#all_values" do
66
- it "should return array with one result value per side" do
67
- die = GamesDice::Die.new(8)
68
- die.all_values.should == [1,2,3,4,5,6,7,8]
69
- end
70
- end
71
-
72
- describe "#each_value" do
73
- it "should iterate through all sides of the die" do
74
- die = GamesDice::Die.new(10)
75
- arr = []
76
- die.each_value { |x| arr << x }
77
- arr.should == [1,2,3,4,5,6,7,8,9,10]
78
- end
79
- end
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 'should return an object that represents e.g. a six-sided die' do
13
+ die = GamesDice::Die.new(6)
14
+ expect(die.min).to eql 1
15
+ expect(die.max).to eql 6
16
+ expect(die.sides).to eql 6
17
+ end
18
+
19
+ it 'should accept any object with a rand(Integer) method as the second param' do
20
+ prng = TestPRNG.new
21
+ die = GamesDice::Die.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 "should return results based on Ruby's internal rand() by default" do
31
+ die = GamesDice::Die.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 'should calculate correct min, max' do
41
+ die = GamesDice::Die.new(20)
42
+ expect(die.min).to eql 1
43
+ expect(die.max).to eql 20
44
+ end
45
+ end
46
+
47
+ describe '#probabilities' do
48
+ it "should return the die's probability distribution as a GamesDice::Probabilities object" do
49
+ die = GamesDice::Die.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 'should return array with one result value per side' do
68
+ die = GamesDice::Die.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 'should iterate through all sides of the die' do
75
+ die = GamesDice::Die.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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # games_dice/spec/helpers.rb
2
4
  require 'pathname'
3
5
  require 'coveralls'
@@ -5,31 +7,31 @@ Coveralls.wear!
5
7
 
6
8
  require 'games_dice'
7
9
 
8
-
9
- def fixture name
10
- (Pathname.new(__FILE__).dirname + "fixtures" + name).to_s
10
+ def fixture(name)
11
+ "#{__dir__}/fixtures/#{name}"
11
12
  end
12
13
 
13
14
  # TestPRNG tests short predictable series
14
15
  class TestPRNG
15
16
  def initialize
16
- @numbers = [0.123,0.234,0.345,0.999,0.876,0.765,0.543,0.111,0.333,0.777]
17
+ @numbers = [0.123, 0.234, 0.345, 0.999, 0.876, 0.765, 0.543, 0.111, 0.333, 0.777]
17
18
  end
18
- def rand(n)
19
- Integer( n * @numbers.pop )
19
+
20
+ def rand(num)
21
+ Integer(num * @numbers.pop)
20
22
  end
21
23
  end
22
24
 
23
25
  # TestPRNGMax checks behaviour of re-rolls
24
26
  class TestPRNGMax
25
- def rand(n)
26
- Integer( n ) - 1
27
+ def rand(num)
28
+ Integer(num) - 1
27
29
  end
28
30
  end
29
31
 
30
32
  # TestPRNGMin checks behaviour of re-rolls
31
33
  class TestPRNGMin
32
- def rand(n)
34
+ def rand(_num)
33
35
  1
34
36
  end
35
37
  end
@@ -42,13 +44,13 @@ end
42
44
  RSpec::Matchers.define :be_valid_distribution do
43
45
  match do |given|
44
46
  @error = nil
45
- if ! given.is_a?(Hash)
47
+ if !given.is_a?(Hash)
46
48
  @error = "distribution should be a Hash, but it is a #{given.class}"
47
- elsif given.keys.any? { |k| ! k.is_a?(Integer) }
48
- bad_key = given.keys.first { |k| ! k.is_a?(Integer) }
49
+ elsif given.keys.any? { |k| !k.is_a?(Integer) }
50
+ bad_key = given.keys.first { |k| !k.is_a?(Integer) }
49
51
  @error = "all keys should be Integers, but found '#{bad_key.inspect}' which is a #{bad_key.class}"
50
- elsif given.values.any? { |v| ! v.is_a?(Float) }
51
- bad_value = given.values.find { |v| ! v.is_a?(Float) }
52
+ elsif given.values.any? { |v| !v.is_a?(Float) }
53
+ bad_value = given.values.find { |v| !v.is_a?(Float) }
52
54
  @error = "all values should be Floats, but found '#{bad_value.inspect}' which is a #{bad_value.class}"
53
55
  elsif given.values.any? { |v| v < 0.0 || v > 1.0 }
54
56
  bad_value = given.values.find { |v| v < 0.0 || v > 1.0 }
@@ -57,18 +59,18 @@ RSpec::Matchers.define :be_valid_distribution do
57
59
  total_probs = given.values.inject(:+)
58
60
  @error = "sum of values should be 1.0, but got #{total_probs}"
59
61
  end
60
- ! @error
62
+ !@error
61
63
  end
62
64
 
63
- failure_message do |given|
64
- @error ? @error : 'Distribution is valid and complete'
65
+ failure_message do |_given|
66
+ @error || 'Distribution is valid and complete'
65
67
  end
66
68
 
67
- failure_message_when_negated do |given|
68
- @error ? @error : 'Distribution is valid and complete'
69
+ failure_message_when_negated do |_given|
70
+ @error || 'Distribution is valid and complete'
69
71
  end
70
72
 
71
- description do |given|
72
- "a hash describing a complete probability distribution of integer results"
73
+ description do |_given|
74
+ 'a hash describing a complete probability distribution of integer results'
73
75
  end
74
76
  end
@@ -1,44 +1,40 @@
1
- require 'helpers'
2
-
3
- describe GamesDice::MapRule do
4
-
5
- describe "#new" do
6
-
7
- it "should accept self-consistent operator/value pairs as a trigger" do
8
- GamesDice::MapRule.new( 5, :>, 1 )
9
- GamesDice::MapRule.new( (1..5), :member?, 17 )
10
- end
11
-
12
- it "should reject inconsistent operator/value pairs for a trigger" do
13
- lambda { GamesDice::MapRule.new( 5, :member?, -1 ) }.should raise_error( ArgumentError )
14
- lambda { GamesDice::MapRule.new( (1..5), :>, 12 ) }.should raise_error( ArgumentError )
15
- end
16
-
17
- it "should reject non-Integer map results" do
18
- lambda { GamesDice::MapRule.new( 5, :>, :reroll_again ) }.should raise_error( TypeError )
19
- lambda { GamesDice::MapRule.new( (1..5), :member?, 'foo' ) }.should raise_error( TypeError )
20
- end
21
-
22
- end
23
-
24
- describe '#map_from' do
25
-
26
- it "should return the mapped value for a match" do
27
- rule = GamesDice::MapRule.new( 5, :>, -1 )
28
- rule.map_from(4).should == -1
29
-
30
- rule = GamesDice::MapRule.new( (1..5), :member?, 3 )
31
- rule.map_from(4).should == 3
32
- end
33
-
34
- it "should return nil for no match" do
35
- rule = GamesDice::MapRule.new( 5, :>, -1 )
36
- rule.map_from(6).should be_nil
37
-
38
- rule = GamesDice::MapRule.new( (1..5), :member?, 3 )
39
- rule.map_from(6).should be_nil
40
- end
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 'should accept self-consistent operator/value pairs as a trigger' do
8
+ GamesDice::MapRule.new(5, :>, 1)
9
+ GamesDice::MapRule.new((1..5), :member?, 17)
10
+ end
11
+
12
+ it 'should reject inconsistent operator/value pairs for a trigger' do
13
+ expect(-> { GamesDice::MapRule.new(5, :member?, -1) }).to raise_error(ArgumentError)
14
+ expect(-> { GamesDice::MapRule.new((1..5), :>, 12) }).to raise_error(ArgumentError)
15
+ end
16
+
17
+ it 'should reject non-Integer map results' do
18
+ expect(-> { GamesDice::MapRule.new(5, :>, :reroll_again) }).to raise_error(TypeError)
19
+ expect(-> { GamesDice::MapRule.new((1..5), :member?, 'foo') }).to raise_error(TypeError)
20
+ end
21
+ end
22
+
23
+ describe '#map_from' do
24
+ it 'should return the mapped value for a match' do
25
+ rule = GamesDice::MapRule.new(5, :>, -1)
26
+ expect(rule.map_from(4)).to eql(-1)
27
+
28
+ rule = GamesDice::MapRule.new((1..5), :member?, 3)
29
+ expect(rule.map_from(4)).to eql 3
30
+ end
31
+
32
+ it 'should return nil for no match' do
33
+ rule = GamesDice::MapRule.new(5, :>, -1)
34
+ expect(rule.map_from(6)).to be_nil
35
+
36
+ rule = GamesDice::MapRule.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
- require 'helpers'
2
-
3
- describe GamesDice::Parser do
4
-
5
- describe "#parse" do
6
- let(:parser) { GamesDice::Parser.new }
7
-
8
- it "should parse simple dice sums" do
9
- variations = {
10
- '1d6' => { :bunches => [{:ndice=>1, :sides=>6, :multiplier=>1}], :offset => 0 },
11
- '2d8-1d4' => { :bunches => [{:ndice=>2, :sides=>8, :multiplier=>1},{:ndice=>1, :sides=>4, :multiplier=>-1}], :offset => 0 },
12
- '+ 2d10 - 1d4 ' => { :bunches => [{:ndice=>2, :sides=>10, :multiplier=>1},{:ndice=>1, :sides=>4, :multiplier=>-1}], :offset => 0 },
13
- ' + 3d6 + 12 ' => { :bunches => [{:ndice=>3, :sides=>6, :multiplier=>1}], :offset => 12 },
14
- '-7 + 2d4 + 1 ' => { :bunches => [{:ndice=>2, :sides=>4, :multiplier=>1}], :offset => -6 },
15
- '- 3 + 7d20 - 1 ' => { :bunches => [{:ndice=>7, :sides=>20, :multiplier=>1}], :offset => -4 },
16
- ' - 2d4' => { :bunches => [{:ndice=>2, :sides=>4, :multiplier=>-1}], :offset => 0 },
17
- '3d12+5+2d8+1d6' => { :bunches => [{:ndice=>3, :sides=>12, :multiplier=>1},{:ndice=>2, :sides=>8, :multiplier=>1},{:ndice=>1, :sides=>6, :multiplier=>1}], :offset => 5 },
18
- }
19
-
20
- variations.each do |input,expected_output|
21
- parser.parse( input ).should == expected_output
22
- end
23
- end
24
-
25
- it "should parse 'NdXrY' as 'roll N times X-sided dice, re-roll and replace a Y or less (once)'" do
26
- variations = {
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
- }
31
-
32
- variations.each do |input,expected_output|
33
- parser.parse( input ).should == expected_output
34
- end
35
- end
36
-
37
- it "should parse 'NdXmZ' as 'roll N times X-sided dice, a value of Z or more equals 1 (success)'" do
38
- variations = {
39
- '5d6m6' => { :bunches => [{:ndice=>5, :sides=>6, :multiplier=>1, :maps=>[ [6,:<=,1] ]}], :offset => 0 },
40
- '2d10m7' => { :bunches => [{:ndice=>2, :sides=>10, :multiplier=>1, :maps=>[ [7,:<=,1] ]}], :offset => 0 },
41
- }
42
-
43
- variations.each do |input,expected_output|
44
- parser.parse( input ).should == expected_output
45
- end
46
- end
47
-
48
- it "should parse 'NdXkC' as 'roll N times X-sided dice, add together the best C'" do
49
- variations = {
50
- '5d10k3' => { :bunches => [{:ndice=>5, :sides=>10, :multiplier=>1, :keep_mode=>:keep_best, :keep_number=>3}], :offset => 0 },
51
- }
52
-
53
- variations.each do |input,expected_output|
54
- parser.parse( input ).should == expected_output
55
- end
56
- end
57
-
58
- it "should parse 'NdXx' as 'roll N times X-sided *exploding* dice'" do
59
- variations = {
60
- '5d10x' => { :bunches => [{:ndice=>5, :sides=>10, :multiplier=>1, :rerolls=>[ [10,:==,:reroll_add] ]}], :offset => 0 },
61
- '3d6x' => { :bunches => [{:ndice=>3, :sides=>6, :multiplier=>1, :rerolls=>[ [6,:==,:reroll_add] ]}], :offset => 0 },
62
- }
63
-
64
- variations.each do |input,expected_output|
65
- parser.parse( input ).should == expected_output
66
- end
67
- end
68
-
69
- it "should successfully parse combinations of modifiers in any valid order" do
70
- variations = {
71
- '5d10r1x' => { :bunches => [{:ndice=>5, :sides=>10, :multiplier=>1, :rerolls=>[ [1,:>=,:reroll_replace], [10,:==,:reroll_add] ]}], :offset => 0 },
72
- '3d6xk2' => { :bunches => [{:ndice=>3, :sides=>6, :multiplier=>1, :rerolls=>[ [6,:==,:reroll_add] ], :keep_mode=>:keep_best, :keep_number=>2 }], :offset => 0 },
73
- '4d6m8x' => { :bunches => [{:ndice=>4, :sides=>6, :multiplier=>1, :maps=>[ [8,:<=,1] ], :rerolls=>[ [6,:==,:reroll_add] ] }], :offset => 0 },
74
- }
75
-
76
- variations.each do |input,expected_output|
77
- parser.parse( input ).should == expected_output
78
- end
79
- end
80
-
81
- end # describe "#parse"
82
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'helpers'
4
+
5
+ describe GamesDice::Parser do
6
+ describe '#parse' do
7
+ let(:parser) { GamesDice::Parser.new }
8
+
9
+ it 'should 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 "should 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 "should 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 "should 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 "should 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 'should 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