games_dice 0.3.9 → 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,82 +1,81 @@
1
- require 'games_dice'
2
- require 'helpers'
3
-
4
- describe GamesDice::Die do
5
-
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
- die.min.should == 1
15
- die.max.should == 6
16
- die.sides.should == 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
- die.roll.should == expected
24
- die.result.should == 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
- die.roll.should == expected
34
- die.result.should == 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
- die.min.should == 1
43
- die.max.should == 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
- probs.is_a?( GamesDice::Probabilities ).should be_true
52
-
53
- probs.to_h.should be_valid_distribution
54
-
55
- probs.p_eql(1).should be_within(1e-10).of 1/6.0
56
- probs.p_eql(2).should be_within(1e-10).of 1/6.0
57
- probs.p_eql(3).should be_within(1e-10).of 1/6.0
58
- probs.p_eql(4).should be_within(1e-10).of 1/6.0
59
- probs.p_eql(5).should be_within(1e-10).of 1/6.0
60
- probs.p_eql(6).should be_within(1e-10).of 1/6.0
61
-
62
- probs.expected.should 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
- die.all_values.should == [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
- arr.should == [1,2,3,4,5,6,7,8,9,10]
79
- end
80
- end
81
-
82
- 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,33 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # games_dice/spec/helpers.rb
2
4
  require 'pathname'
3
5
  require 'coveralls'
4
-
5
6
  Coveralls.wear!
6
7
 
7
- def fixture name
8
- (Pathname.new(__FILE__).dirname + "fixtures" + name).to_s
8
+ require 'games_dice'
9
+
10
+ def fixture(name)
11
+ "#{__dir__}/fixtures/#{name}"
9
12
  end
10
13
 
11
14
  # TestPRNG tests short predictable series
12
15
  class TestPRNG
13
16
  def initialize
14
- @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]
15
18
  end
16
- def rand(n)
17
- Integer( n * @numbers.pop )
19
+
20
+ def rand(num)
21
+ Integer(num * @numbers.pop)
18
22
  end
19
23
  end
20
24
 
21
25
  # TestPRNGMax checks behaviour of re-rolls
22
26
  class TestPRNGMax
23
- def rand(n)
24
- Integer( n ) - 1
27
+ def rand(num)
28
+ Integer(num) - 1
25
29
  end
26
30
  end
27
31
 
28
32
  # TestPRNGMin checks behaviour of re-rolls
29
33
  class TestPRNGMin
30
- def rand(n)
34
+ def rand(_num)
31
35
  1
32
36
  end
33
37
  end
@@ -40,13 +44,13 @@ end
40
44
  RSpec::Matchers.define :be_valid_distribution do
41
45
  match do |given|
42
46
  @error = nil
43
- if ! given.is_a?(Hash)
47
+ if !given.is_a?(Hash)
44
48
  @error = "distribution should be a Hash, but it is a #{given.class}"
45
- elsif given.keys.any? { |k| ! k.is_a?(Fixnum) }
46
- bad_key = given.keys.first { |k| ! k.is_a?(Fixnum) }
47
- @error = "all keys should be Fixnums, but found '#{bad_key.inspect}' which is a #{bad_key.class}"
48
- elsif given.values.any? { |v| ! v.is_a?(Float) }
49
- bad_value = given.values.find { |v| ! v.is_a?(Float) }
49
+ elsif given.keys.any? { |k| !k.is_a?(Integer) }
50
+ bad_key = given.keys.first { |k| !k.is_a?(Integer) }
51
+ @error = "all keys should be Integers, but found '#{bad_key.inspect}' which is a #{bad_key.class}"
52
+ elsif given.values.any? { |v| !v.is_a?(Float) }
53
+ bad_value = given.values.find { |v| !v.is_a?(Float) }
50
54
  @error = "all values should be Floats, but found '#{bad_value.inspect}' which is a #{bad_value.class}"
51
55
  elsif given.values.any? { |v| v < 0.0 || v > 1.0 }
52
56
  bad_value = given.values.find { |v| v < 0.0 || v > 1.0 }
@@ -55,18 +59,18 @@ RSpec::Matchers.define :be_valid_distribution do
55
59
  total_probs = given.values.inject(:+)
56
60
  @error = "sum of values should be 1.0, but got #{total_probs}"
57
61
  end
58
- ! @error
62
+ !@error
59
63
  end
60
64
 
61
- failure_message_for_should do |given|
62
- @error ? @error : 'Distribution is valid and complete'
65
+ failure_message do |_given|
66
+ @error || 'Distribution is valid and complete'
63
67
  end
64
68
 
65
- failure_message_for_should_not do |given|
66
- @error ? @error : 'Distribution is valid and complete'
69
+ failure_message_when_negated do |_given|
70
+ @error || 'Distribution is valid and complete'
67
71
  end
68
72
 
69
- description do |given|
70
- "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'
71
75
  end
72
76
  end
@@ -1,44 +1,40 @@
1
- require 'games_dice'
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 false for no match" do
35
- rule = GamesDice::MapRule.new( 5, :>, -1 )
36
- rule.map_from(6).should be_false
37
-
38
- rule = GamesDice::MapRule.new( (1..5), :member?, 3 )
39
- rule.map_from(6).should be_false
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 'games_dice'
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