games_dice 0.3.12 → 0.4.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/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