games_dice 0.2.0 → 0.2.1

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/.travis.yml CHANGED
@@ -3,8 +3,8 @@ rvm:
3
3
  - "1.8.7"
4
4
  - "1.9.3"
5
5
  - "2.0.0"
6
- - jruby-18mode # JRuby in 1.8 mode
7
- - jruby-19mode # JRuby in 1.9 mode
8
6
  - rbx-18mode
9
7
  - rbx-19mode
8
+ - ruby-head
9
+ - ree
10
10
 
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
+ require "yard"
3
4
 
4
5
  task :default => [:test]
5
6
 
@@ -8,3 +9,7 @@ RSpec::Core::RakeTask.new(:test) do |t|
8
9
  t.pattern = "spec/*_spec.rb"
9
10
  t.verbose = false
10
11
  end
12
+
13
+ YARD::Rake::YardocTask.new do |t|
14
+ t.files = ['lib/**/*.rb']
15
+ end
data/games_dice.gemspec CHANGED
@@ -16,8 +16,10 @@ Gem::Specification.new do |gem|
16
16
 
17
17
  gem.add_development_dependency "rspec", ">= 2.13.0"
18
18
  gem.add_development_dependency "rake", ">= 1.9.1"
19
+ gem.add_development_dependency "yard", ">= 0.8.6"
20
+ gem.add_development_dependency "redcarpet", ">=2.3.0"
19
21
 
20
- gem.add_dependency "parslet", "~> 1.5.0"
22
+ gem.add_dependency "parslet", ">= 1.5.0"
21
23
 
22
24
  gem.files = `git ls-files`.split($/)
23
25
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
data/lib/games_dice.rb CHANGED
@@ -11,6 +11,7 @@ require "games_dice/dice"
11
11
  require "games_dice/parser"
12
12
 
13
13
  module GamesDice
14
+ # @!visibility private
14
15
  @@parser = GamesDice::Parser.new
15
16
 
16
17
  def self.create dice_description, prng = nil
@@ -1,59 +1,82 @@
1
- # complex die that rolls 1..N, and may re-roll and adjust final value based on
2
- # parameters it is instantiated with
3
- # d = GamesDice::DieExploding.new( 6, :explode_up => true ) # 'exploding' die
4
- # d.roll # => GamesDice::DieResult of rolling die
5
- # d.result # => same GamesDice::DieResult as returned by d.roll
1
+ # This class models a die that is built up from a simpler unit by adding rules to re-roll
2
+ # and interpret the value shown.
3
+ #
4
+ # An object of this class represents a single complex die. It rolls 1..#sides, with equal weighting
5
+ # for each value. The value from a roll may be used to trigger yet more rolls that combine together.
6
+ # After any re-rolls, the value can be interpretted ("mapped") as another integer, which is used as
7
+ # the final result.
8
+ #
9
+ # @example An open-ended percentile die from a popular RPG
10
+ # d = GamesDice::ComplexDie.new( 100, :rerolls => [[96, :<=, :reroll_add],[5, :>=, :reroll_subtract]] )
11
+ # d.roll # => #<GamesDice::DieResult:0x007ff03a2415f8 @rolls=[4, 27], ...>
12
+ # d.result.value # => -23
13
+ # d.explain_result # => "[4-27] -23"
14
+ #
15
+ # @example An "exploding" six-sided die with a target number
16
+ # d = GamesDice::ComplexDie.new( 6, :rerolls => [[6, :<=, :reroll_add]], :maps => [[8, :<=, 1, 'Success']] )
17
+ # d.roll # => #<GamesDice::DieResult:0x007ff03a1e8e08 @rolls=[6, 5], ...>
18
+ # d.result.value # => 1
19
+ # d.explain_result # => "[6+5] 11 Success"
20
+ #
21
+
6
22
  class GamesDice::ComplexDie
7
23
 
8
- # arbitrary limit to simplify calculations and stay in Integer range for convenience. It should
9
- # be much larger than anything seen in real-world tabletop games.
24
+ # @!visibility private
25
+ # arbitrary limit to speed up probability calculations. It should
26
+ # be larger than anything seen in real-world tabletop games.
10
27
  MAX_REROLLS = 1000
11
28
 
12
- # sides is e.g. 6 for traditional cubic die, or 20 for icosahedron.
13
- # It can take non-traditional values, such as 7, but must be at least 1.
14
- #
15
- # options_hash may contain keys setting following attributes
16
- # :rerolls => an array of rules that cause the die to roll again, see #rerolls
17
- # :maps => an array of rules to convert a value into a final result for the die, see #maps
18
- # :prng => any object that has a rand(x) method, which will be used instead of internal rand()
19
- def initialize(sides, options_hash = {})
20
- @basic_die = GamesDice::Die.new(sides, options_hash[:prng])
29
+ # Creates new instance of GamesDice::ComplexDie
30
+ # @param [Integer] sides Number of sides on a single die, passed to GamesDice::Die's constructor
31
+ # @param [Hash] options
32
+ # @option options [Array<GamesDice::RerollRule,Array>] :rerolls The rules that cause the die to roll again
33
+ # @option options [Array<GamesDice::MapRule,Array>] :maps The rules to convert a value into a final result for the die
34
+ # @option options [#rand] :prng An alternative source of randomness to Ruby's built-in #rand, passed to GamesDice::Die's constructor
35
+ # @return [GamesDice::ComplexDie]
36
+ def initialize( sides, options = {} )
37
+ @basic_die = GamesDice::Die.new(sides, options[:prng])
21
38
 
22
- @rerolls = construct_rerolls( options_hash[:rerolls] )
23
- @maps = construct_maps( options_hash[:maps] )
39
+ @rerolls = construct_rerolls( options[:rerolls] )
40
+ @maps = construct_maps( options[:maps] )
24
41
 
25
42
  @total = nil
26
43
  @result = nil
27
44
  end
28
45
 
29
- # underlying GamesDice::Die object, used to generate all individual rolls
46
+ # The simple component used by this complex one
47
+ # @return [GamesDice::Die] Object used to make individual dice rolls for the complex die
30
48
  attr_reader :basic_die
31
49
 
32
- # may be nil, in which case no re-rolls are triggered, or an array of GamesDice::RerollRule objects
50
+ # @return [Array<GamesDice::RerollRule>, nil] Sequence of re-roll rules, or nil if re-rolls are not required.
33
51
  attr_reader :rerolls
34
52
 
35
- # may be nil, in which case no mappings apply, or an array of GamesDice::MapRule objects
53
+ # @return [Array<GamesDice::MapRule>, nil] Sequence of map rules, or nil if mapping is not required.
36
54
  attr_reader :maps
37
55
 
38
- # result of last call to #roll, nil if no call made yet
56
+ # @return [GamesDice::DieResult, nil] Result of last call to #roll, nil if no call made yet
39
57
  attr_reader :result
40
58
 
41
- # true if probability calculation did not hit any limitations, so has covered all possible scenarios
42
- # false if calculation was cut short and probabilities are an approximation
43
- # nil if probabilities have not been calculated yet
59
+ # Whether or not #probabilities includes all possible outcomes.
60
+ # True if all possible results are represented and assigned a probability. Dice with open-ended re-rolls
61
+ # may have calculations cut short, and will result in a false value of this attribute. Even when this
62
+ # attribute is false, probabilities should still be accurate to nearest 1e-9.
63
+ # @return [Boolean, nil] Depending on completeness when generating #probabilites
44
64
  attr_reader :probabilities_complete
45
65
 
46
- # number of sides, same as #basic_die.sides
66
+ # @!attribute [r] sides
67
+ # @return [Integer] Number of sides.
47
68
  def sides
48
69
  @basic_die.sides
49
70
  end
50
71
 
51
- # string explanation of roll, including any re-rolls etc, same as #result.explain_value
72
+ # @!attribute [r] explain_result
73
+ # @return [String,nil] Explanation of result, or nil if no call to #roll yet.
52
74
  def explain_result
53
75
  @result.explain_value
54
76
  end
55
77
 
56
- # minimum possible value
78
+ # @!attribute [r] min
79
+ # @return [Integer] Minimum possible result from a call to #roll
57
80
  def min
58
81
  return @min_result if @min_result
59
82
  @min_result, @max_result = [probabilities.min, probabilities.max]
@@ -63,8 +86,8 @@ class GamesDice::ComplexDie
63
86
  @min_result
64
87
  end
65
88
 
66
- # maximum possible value. A ComplexDie with open-ended additive re-rolls will calculate roughly 1001 times the
67
- # maximum of #basic_die.max (although the true value is infinite)
89
+ # @!attribute [r] max
90
+ # @return [Integer] Maximum possible result from a call to #roll
68
91
  def max
69
92
  return @max_result if @max_result
70
93
  @min_result, @max_result = [probabilities.min, probabilities.max]
@@ -74,9 +97,10 @@ class GamesDice::ComplexDie
74
97
  @max_result
75
98
  end
76
99
 
77
- # returns a hash of value (Integer) => probability (Float) pairs. For efficiency with re-rolls, the calculation may cut
78
- # short based on depth of recursion or closeness to total 1.0 probability. Therefore low probabilities
79
- # (less than one in a billion) in open-ended re-rolls are not always represented in the hash.
100
+ # Calculates the probability distribution for the die. For open-ended re-roll rules, there are some
101
+ # arbitrary limits imposed to prevent large amounts of recursion. Probabilities should be to nearest
102
+ # 1e-9 at worst.
103
+ # @return [GamesDice::Probabilities] Probability distribution of die.
80
104
  def probabilities
81
105
  return @probabilities if @probabilities
82
106
  @probabilities_complete = true
@@ -106,8 +130,9 @@ class GamesDice::ComplexDie
106
130
  @probabilities = GamesDice::Probabilities.new( prob_hash )
107
131
  end
108
132
 
109
- # generates Integer between #min and #max, using rand()
110
- # first roll reason can be over-ridden, required for re-roll types that spawn new dice
133
+ # Simulates rolling the die
134
+ # @param [Symbol] reason Assign a reason for rolling the first die.
135
+ # @return [GamesDice::DieResult] Detailed results from rolling the die, including resolution of rules.
111
136
  def roll( reason = :basic )
112
137
  # Important bit - actually roll the die
113
138
  @result = GamesDice::DieResult.new( @basic_die.roll, reason )
@@ -1,12 +1,28 @@
1
- # basic die that rolls 1..N, typically with equal weighting for each value
2
- # d = Die.new(6)
1
+ # This class models the simplest, most-familiar kind of die.
2
+ #
3
+ # An object of the class represents a basic die that rolls 1..#sides, with equal weighting for each value.
4
+ #
5
+ # @example Create a 6-sided die, and roll it
6
+ # d = GamesDice::Die.new( 6 )
3
7
  # d.roll # => Integer in range 1..6
4
- # d.result # => same Integer value as returned by d.roll
8
+ # d.result # => same Integer value as just returned by d.roll
9
+ #
10
+ # @example Create a 10-sided die, that rolls using a monkey-patch to SecureRandom
11
+ # module SecureRandom
12
+ # def self.rand n
13
+ # random_number( n )
14
+ # end
15
+ # end
16
+ # d = GamesDice::Die.new( 10, SecureRandom )
17
+ # d.roll # => (secure) Integer in range 1..10
18
+ # d.result # => same Integer value as just returned by d.roll
19
+
5
20
  class GamesDice::Die
6
- # sides is e.g. 6 for traditional cubic die, or 20 for icosahedron.
7
- # It can take non-traditional values, such as 7, but must be at least 1.
8
- # prng is an object that has a rand(x) method. If provided, it will be called as
9
- # prng.rand(sides), and is expected to return an integer in range 0...sides
21
+
22
+ # Creates new instance of GamesDice::Die
23
+ # @param [Integer] sides the number of sides
24
+ # @param [#rand] prng random number generator, GamesDice::Die will use Ruby's built-in #rand() by default
25
+ # @return [GamesDice::Die]
10
26
  def initialize( sides, prng=nil )
11
27
  @sides = Integer(sides)
12
28
  raise ArgumentError, "sides value #{sides} is too low, it must be 1 or greater" if @sides < 1
@@ -15,29 +31,36 @@ class GamesDice::Die
15
31
  @result = nil
16
32
  end
17
33
 
18
- # number of sides as set by #new
34
+ # @return [Integer] number of sides on simulated die
19
35
  attr_reader :sides
20
36
 
21
- # integer result of last call to #roll, nil if no call made yet
37
+ # @return [Integer] result of last call to #roll, nil if no call made yet
22
38
  attr_reader :result
23
39
 
24
- # minimum possible value
40
+ # @return [Object] random number generator as supplied to constructor, may be nil
41
+ attr_reader :prng
42
+
43
+ # @!attribute [r] min
44
+ # @return [Integer] minimum possible result from a call to #roll
25
45
  def min
26
46
  1
27
47
  end
28
48
 
29
- # maximum possible value
49
+ # @!attribute [r] max
50
+ # @return [Integer] maximum possible result from a call to #roll
30
51
  def max
31
52
  @sides
32
53
  end
33
54
 
34
- # returns a GamesDice::Probabilities object that models distribution of the die
55
+ # Calculates probability distribution for this die.
56
+ # @return [GamesDice::Probabilities] probability distribution of the die
35
57
  def probabilities
36
58
  return @probabilities if @probabilities
37
59
  @probabilities = GamesDice::Probabilities.for_fair_die( @sides )
38
60
  end
39
61
 
40
- # generates Integer between #min and #max, using rand()
62
+ # Simulates rolling the die
63
+ # @return [Integer] selected value between 1 and #sides inclusive
41
64
  def roll
42
65
  if @prng
43
66
  @result = @prng.rand(@sides) + 1
@@ -46,13 +69,17 @@ class GamesDice::Die
46
69
  end
47
70
  end
48
71
 
49
- # always nil, available for compatibility with ComplexDie
72
+ # @!attribute [r] rerolls
73
+ # Rules for when to re-roll this die.
74
+ # @return [nil] always nil, available for interface equivalence with GamesDice::ComplexDie
50
75
  def rerolls
51
76
  nil
52
77
  end
53
78
 
54
- # always nil, available for compatibility with ComplexDie
79
+ # @!attribute [r] maps
80
+ # Rules for when to map return value of this die.
81
+ # @return [nil] always nil, available for interface equivalence with GamesDice::ComplexDie
55
82
  def maps
56
83
  nil
57
84
  end
58
- end # class Die
85
+ end # class GamesDice::Die
@@ -1,3 +1,3 @@
1
1
  module GamesDice
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
data/spec/die_spec.rb CHANGED
@@ -46,8 +46,10 @@ describe GamesDice::Die do
46
46
  describe "#probabilities" do
47
47
  it "should return the die's probability distribution as a GamesDice::Probabilities object" do
48
48
  die = GamesDice::Die.new(6)
49
- die.probabilities.is_a?( GamesDice::Probabilities ).should be_true
50
49
  probs = die.probabilities
50
+ probs.is_a?( GamesDice::Probabilities ).should be_true
51
+
52
+ probs.to_h.should be_valid_distribution
51
53
 
52
54
  probs.p_eql(1).should be_within(1e-10).of 1/6.0
53
55
  probs.p_eql(2).should be_within(1e-10).of 1/6.0
@@ -55,23 +57,6 @@ describe GamesDice::Die do
55
57
  probs.p_eql(4).should be_within(1e-10).of 1/6.0
56
58
  probs.p_eql(5).should be_within(1e-10).of 1/6.0
57
59
  probs.p_eql(6).should be_within(1e-10).of 1/6.0
58
- probs.to_h.values.inject(:+).should be_within(1e-9).of 1.0
59
-
60
- probs.p_gt(6).should == 0.0
61
- probs.p_gt(-20).should == 1.0
62
- probs.p_gt(4).should be_within(1e-10).of 2/6.0
63
-
64
- probs.p_ge(20).should == 0.0
65
- probs.p_ge(1).should == 1.0
66
- probs.p_ge(4).should be_within(1e-10).of 0.5
67
-
68
- probs.p_le(6).should == 1.0
69
- probs.p_le(-3).should == 0.0
70
- probs.p_le(5).should be_within(1e-10).of 5/6.0
71
-
72
- probs.p_lt(7).should == 1.0
73
- probs.p_lt(1).should == 0.0
74
- probs.p_lt(3).should be_within(1e-10).of 2/6.0
75
60
 
76
61
  probs.expected.should be_within(1e-10).of 3.5
77
62
  end
data/spec/helpers.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # games_dice/spec/helpers.rb
2
2
 
3
+ # TestPRNG tests short predictable series
3
4
  class TestPRNG
4
5
  def initialize
5
6
  @numbers = [0.123,0.234,0.345,0.999,0.876,0.765,0.543,0.111,0.333,0.777]
@@ -9,6 +10,20 @@ class TestPRNG
9
10
  end
10
11
  end
11
12
 
13
+ # TestPRNGMax checks behaviour of re-rolls
14
+ class TestPRNGMax
15
+ def rand(n)
16
+ Integer( n ) - 1
17
+ end
18
+ end
19
+
20
+ # TestPRNGMax checks behaviour of re-rolls
21
+ class TestPRNGMin
22
+ def rand(n)
23
+ 1
24
+ end
25
+ end
26
+
12
27
  # A valid distribution is:
13
28
  # A hash
14
29
  # Keys are all Integers
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.2.0
4
+ version: 0.2.1
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-30 00:00:00.000000000 Z
12
+ date: 2013-06-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -43,12 +43,44 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: 1.9.1
46
+ - !ruby/object:Gem::Dependency
47
+ name: yard
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 0.8.6
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.8.6
62
+ - !ruby/object:Gem::Dependency
63
+ name: redcarpet
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 2.3.0
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 2.3.0
46
78
  - !ruby/object:Gem::Dependency
47
79
  name: parslet
48
80
  requirement: !ruby/object:Gem::Requirement
49
81
  none: false
50
82
  requirements:
51
- - - ~>
83
+ - - ! '>='
52
84
  - !ruby/object:Gem::Version
53
85
  version: 1.5.0
54
86
  type: :runtime
@@ -56,7 +88,7 @@ dependencies:
56
88
  version_requirements: !ruby/object:Gem::Requirement
57
89
  none: false
58
90
  requirements:
59
- - - ~>
91
+ - - ! '>='
60
92
  - !ruby/object:Gem::Version
61
93
  version: 1.5.0
62
94
  description: ! "A simulated-dice library, with flexible rules that allow dice systems
@@ -112,7 +144,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
112
144
  version: '0'
113
145
  segments:
114
146
  - 0
115
- hash: 4046591063539756555
147
+ hash: -3596350699515982404
116
148
  required_rubygems_version: !ruby/object:Gem::Requirement
117
149
  none: false
118
150
  requirements:
@@ -121,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
153
  version: '0'
122
154
  segments:
123
155
  - 0
124
- hash: 4046591063539756555
156
+ hash: -3596350699515982404
125
157
  requirements: []
126
158
  rubyforge_project:
127
159
  rubygems_version: 1.8.24
@@ -141,3 +173,4 @@ test_files:
141
173
  - spec/probability_spec.rb
142
174
  - spec/readme_spec.rb
143
175
  - spec/reroll_rule_spec.rb
176
+ has_rdoc: