games_dice 0.2.2 → 0.2.3

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/README.md CHANGED
@@ -197,6 +197,12 @@ Returns the probability of a result less than the integer n.
197
197
  probabilities.p_lt( 17 ) # => 0.9953703703703
198
198
  probabilities.p_lt( 3 ) # => 0.0
199
199
 
200
+ #### probabilities.expected
201
+
202
+ Returns the mean result, weighted by probabality of each value.
203
+
204
+ probabilities.expected # => 10.5 (rounded to nearest 1e-9)
205
+
200
206
  ## String Dice Descriptions
201
207
 
202
208
  The dice descriptions are a mini-language. A simple six-sided die is described like this:
@@ -1,126 +1,159 @@
1
- # models a set of identical dice, that can be "rolled" and combined into a simple integer result. The
2
- # dice are identical in number of sides, and any re-roll or mapping rules that apply to them
1
+ # This class models a number of identical dice, which may be either GamesDice::Die or
2
+ # GamesDice::ComplexDie objects.
3
+ #
4
+ # An object of this class represents a fixed number of indentical dice that may be rolled and their
5
+ # values summed to make a total for the bunch.
6
+ #
7
+ # @example The ubiquitous '3d6'
8
+ # d = GamesDice::Bunch.new( :ndice => 3, :sides => 6 )
9
+ # d.roll # => 14
10
+ # d.result # => 14
11
+ # d.explain_result # => "2 + 6 + 6 = 14"
12
+ # d.max # => 18
13
+ #
14
+ # @example Roll 5d10, and keep the best 2
15
+ # d = GamesDice::Bunch.new( :ndice => 5, :sides => 10 , :keep_mode => :keep_best, :keep_number => 2 )
16
+ # d.roll # => 18
17
+ # d.result # => 18
18
+ # d.explain_result # => "4, 9, 2, 9, 1. Keep: 9 + 9 = 18"
19
+ #
20
+
3
21
  class GamesDice::Bunch
4
- # attributes is a hash of symbols used to set attributes of the new Bunch object. Each
5
- # attribute is explained in more detail in its own section. The following hash keys and values
6
- # are mandatory:
7
- # :ndice
8
- # :sides
9
- # The following are optional, and modify the behaviour of the Bunch object
10
- # :name
11
- # :prng
12
- # :rerolls
13
- # :maps
14
- # :keep_mode
15
- # :keep_number
16
- # Any other keys provided to the constructor are ignored
17
- def initialize( attributes )
18
- @name = attributes[:name].to_s
19
- @ndice = Integer(attributes[:ndice])
22
+ # The constructor accepts parameters that are suitable for either GamesDice::Die or GamesDice::ComplexDie
23
+ # and decides which of those classes to instantiate.
24
+ # @param [Hash] options
25
+ # @option options [Integer] :ndice Number of dice in the bunch, *mandatory*
26
+ # @option options [Integer] :sides Number of sides on a single die in the bunch, *mandatory*
27
+ # @option options [String] :name Optional name for the bunch
28
+ # @option options [Array<GamesDice::RerollRule,Array>] :rerolls Optional rules that cause the die to roll again
29
+ # @option options [Array<GamesDice::MapRule,Array>] :maps Optional rules to convert a value into a final result for the die
30
+ # @option options [#rand] :prng Optional alternative source of randomness to Ruby's built-in #rand, passed to GamesDice::Die's constructor
31
+ # @option options [Symbol] :keep_mode Optional, either *:keep_best* or *:keep_worst*
32
+ # @option options [Integer] :keep_number Optional number of dice to keep when :keep_mode is not nil
33
+ # @return [GamesDice::Bunch]
34
+ def initialize( options )
35
+ @name = options[:name].to_s
36
+ @ndice = Integer(options[:ndice])
20
37
  raise ArgumentError, ":ndice must be 1 or more, but got #{@ndice}" unless @ndice > 0
21
- @sides = Integer(attributes[:sides])
38
+ @sides = Integer(options[:sides])
22
39
  raise ArgumentError, ":sides must be 1 or more, but got #{@sides}" unless @sides > 0
23
40
 
24
- options = Hash.new
41
+ attr = Hash.new
25
42
 
26
- if attributes[:prng]
43
+ if options[:prng]
27
44
  # We deliberately do not clone this object, it will often be intended that it is shared
28
- prng = attributes[:prng]
45
+ prng = options[:prng]
29
46
  raise ":prng does not support the rand() method" if ! prng.respond_to?(:rand)
30
47
  end
31
48
 
32
49
  needs_complex_die = false
33
50
 
34
- if attributes[:rerolls]
51
+ if options[:rerolls]
35
52
  needs_complex_die = true
36
- options[:rerolls] = attributes[:rerolls].clone
53
+ attr[:rerolls] = options[:rerolls].clone
37
54
  end
38
55
 
39
- if attributes[:maps]
56
+ if options[:maps]
40
57
  needs_complex_die = true
41
- options[:maps] = attributes[:maps].clone
58
+ attr[:maps] = options[:maps].clone
42
59
  end
43
60
 
44
61
  if needs_complex_die
45
- options[:prng] = prng
46
- @single_die = GamesDice::ComplexDie.new( @sides, options )
62
+ attr[:prng] = prng
63
+ @single_die = GamesDice::ComplexDie.new( @sides, attr )
47
64
  else
48
65
  @single_die = GamesDice::Die.new( @sides, prng )
49
66
  end
50
67
 
51
- case attributes[:keep_mode]
68
+ case options[:keep_mode]
52
69
  when nil then
53
70
  @keep_mode = nil
54
71
  when :keep_best then
55
72
  @keep_mode = :keep_best
56
- @keep_number = Integer(attributes[:keep_number] || 1)
73
+ @keep_number = Integer(options[:keep_number] || 1)
57
74
  when :keep_worst then
58
75
  @keep_mode = :keep_worst
59
- @keep_number = Integer(attributes[:keep_number] || 1)
76
+ @keep_number = Integer(options[:keep_number] || 1)
60
77
  else
61
- raise ArgumentError, ":keep_mode can be nil, :keep_best or :keep_worst. Got #{attributes[:keep_mode].inspect}"
78
+ raise ArgumentError, ":keep_mode can be nil, :keep_best or :keep_worst. Got #{options[:keep_mode].inspect}"
62
79
  end
63
80
  end
64
81
 
65
- # the string name as provided to the constructor, it will appear in explain_result
82
+ # Name to help identify bunch
83
+ # @return [String]
66
84
  attr_reader :name
67
85
 
68
- # integer number of dice to roll (initially, before re-rolls etc)
86
+ # Number of dice to roll
87
+ # @return [Integer]
69
88
  attr_reader :ndice
70
89
 
71
- # individual die that will be rolled, #ndice times, an GamesDice::Die or GamesDice::ComplexDie object.
90
+ # Individual die from the bunch
91
+ # @return [GamesDice::Die,GamesDice::ComplexDie]
72
92
  attr_reader :single_die
73
93
 
74
- # may be nil, :keep_best or :keep_worst
94
+ # Can be nil, :keep_best or :keep_worst
95
+ # @return [Symbol,nil]
75
96
  attr_reader :keep_mode
76
97
 
77
- # number of "best" or "worst" results to select when #keep_mode is not nil. This attribute is
78
- # 1 by default if :keep_mode is supplied, or nil by default otherwise.
98
+ # Number of "best" or "worst" results to select when #keep_mode is not nil.
99
+ # @return [Integer,nil]
79
100
  attr_reader :keep_number
80
101
 
81
- # after calling #roll, this is set to the final integer value from using the dice as specified
102
+ # Result of most-recent roll, or nil if no roll made yet.
103
+ # @return [Integer,nil]
82
104
  attr_reader :result
83
105
 
84
- # Needs refinement. Returns best available string description of the bunch.
106
+ # @!attribute [r] label
107
+ # Description that will be used in explanations with more than one bunch
108
+ # @return [String]
85
109
  def label
86
110
  return @name if @name != ''
87
111
  return @ndice.to_s + 'd' + @sides.to_s
88
112
  end
89
113
 
90
- # either nil, or an array of GamesDice::RerollRule objects that are assessed on each roll of #single_die
91
- # Reroll types :reroll_new_die and :reroll_new_keeper do not affect the #single_die, but are instead
92
- # assessed in this container object
114
+ # @!attribute [r] rerolls
115
+ # Sequence of re-roll rules, or nil if re-rolls are not required.
116
+ # @return [Array<GamesDice::RerollRule>, nil]
93
117
  def rerolls
94
118
  @single_die.rerolls
95
119
  end
96
120
 
97
- # either nil, or an array of GamesDice::MapRule objects that are assessed on each result of #single_die (after rerolls are completed)
121
+ # @!attribute [r] maps
122
+ # Sequence of map rules, or nil if mapping is not required.
123
+ # @return [Array<GamesDice::MapRule>, nil]
98
124
  def maps
99
- @single_die.rerolls
125
+ @single_die.maps
100
126
  end
101
127
 
102
- # after calling #roll, this is an array of GamesDice::DieResult objects, one from each #single_die rolled,
128
+ # @!attribute [r] result_details
129
+ # After calling #roll, this is an array of GamesDice::DieResult objects. There is one from each #single_die rolled,
103
130
  # allowing inspection of how the result was obtained.
131
+ # @return [Array<GamesDice::DieResult>, nil] Sequence of GamesDice::DieResult objects.
104
132
  def result_details
105
133
  return nil unless @raw_result_details
106
134
  @raw_result_details.map { |r| r.is_a?(Fixnum) ? GamesDice::DieResult.new(r) : r }
107
135
  end
108
136
 
109
- # minimum possible integer value
137
+ # @!attribute [r] min
138
+ # Minimum possible result from a call to #roll
139
+ # @return [Integer]
110
140
  def min
111
141
  n = @keep_mode ? [@keep_number,@ndice].min : @ndice
112
142
  return n * @single_die.min
113
143
  end
114
144
 
115
- # maximum possible integer value
145
+ # @!attribute [r] max
146
+ # Maximum possible result from a call to #roll
147
+ # @return [Integer]
116
148
  def max
117
149
  n = @keep_mode ? [@keep_number,@ndice].min : @ndice
118
150
  return n * @single_die.max
119
151
  end
120
152
 
121
- # returns a hash of value (Integer) => probability (Float) pairs. Warning: Some dice schemes
122
- # cause this method to take a long time, and use a lot of memory. The worst-case offenders are
123
- # dice schemes with a #keep_mode of :keep_best or :keep_worst.
153
+ # Calculates the probability distribution for the bunch. When the bunch is composed of dice with
154
+ # open-ended re-roll rules, there are some arbitrary limits imposed to prevent large amounts of
155
+ # recursion.
156
+ # @return [GamesDice::Probabilities] Probability distribution of bunch.
124
157
  def probabilities
125
158
  return @probabilities if @probabilities
126
159
  @probabilities_complete = true
@@ -158,7 +191,8 @@ class GamesDice::Bunch
158
191
  @probabilities = GamesDice::Probabilities.new( combined_probs )
159
192
  end
160
193
 
161
- # simulate dice roll according to spec. Returns integer final total, and also stores it in #result
194
+ # Simulates rolling the bunch of identical dice
195
+ # @return [Integer] Sum of all rolled dice, or sum of all keepers
162
196
  def roll
163
197
  @result = 0
164
198
  @raw_result_details = []
@@ -184,6 +218,9 @@ class GamesDice::Bunch
184
218
  @result = use_dice.inject(0) { |so_far, die_result| so_far + die_result }
185
219
  end
186
220
 
221
+ # @!attribute [r] explain_result
222
+ # Explanation of result, or nil if no call to #roll yet.
223
+ # @return [String,nil]
187
224
  def explain_result
188
225
  return nil unless @result
189
226
 
@@ -1,12 +1,36 @@
1
- # models any combination of zero or more Bunches, plus a constant offset, summing them
2
- # to create a total result when rolled
1
+ # This class models a combination of GamesDice::Bunch objects plus a fixed offset.
2
+ #
3
+ # An object of this class is a dice "recipe" that specifies the numbers and types of
4
+ # dice that can be rolled to generate an integer value.
5
+ #
6
+ # @example '3d6+6' hitpoints, whatever that means in the game you are playing
7
+ # d = GamesDice::Dice.new( [{:ndice => 3, :sides => 6}], 6, 'Hit points' )
8
+ # d.roll # => 20
9
+ # d.result # => 20
10
+ # d.explain_result # => "3d6: 3 + 5 + 6 = 14. 14 + 6 = 20"
11
+ # d.probabilities.expected # => 16.5
12
+ #
13
+ # @example Roll d20 twice, take best result, and add 5.
14
+ # d = GamesDice::Dice.new( [{:ndice => 2, :sides => 20 , :keep_mode => :keep_best, :keep_number => 1}], 5 )
15
+ # d.roll # => 21
16
+ # d.result # => 21
17
+ # d.explain_result # => "2d20: 4, 16. Keep: 16. 16 + 5 = 21"
18
+ #
3
19
  class GamesDice::Dice
4
- # bunches is an Array of Hashes, each of which describes a GamesDice::Bunch
5
- # and may contain any of the keys that can be used to initialize
6
- # the Bunch, plus the following optional key:
7
- # :multiplier => any Integer, but typically 1 or -1 to describe whether the Bunch total is to be added or subtracted
8
- # offset is an Integer which will be added to the result when rolling all the bunches
9
- # name can be any String, and is used to identify the dice being rolled.
20
+ # The first parameter is an array of values that are passed to GamesDice::Bunch constructors.
21
+ # @param [Array<Hash>] bunches Array of options for creating bunches
22
+ # @param [Integer] offset Total offset
23
+ # @param [String] name Optional label for the dice
24
+ # @option bunches [Integer] :ndice Number of dice in the bunch, *mandatory*
25
+ # @option bunches [Integer] :sides Number of sides on a single die in the bunch, *mandatory*
26
+ # @option bunches [String] :name Optional name for the bunch
27
+ # @option bunches [Array<GamesDice::RerollRule,Array>] :rerolls Optional rules that cause the die to roll again
28
+ # @option bunches [Array<GamesDice::MapRule,Array>] :maps Optional rules to convert a value into a final result for the die
29
+ # @option bunches [#rand] :prng Optional alternative source of randomness to Ruby's built-in #rand, passed to GamesDice::Die's constructor
30
+ # @option bunches [Symbol] :keep_mode Optional, either *:keep_best* or *:keep_worst*
31
+ # @option bunches [Integer] :keep_number Optional number of dice to keep when :keep_mode is not nil
32
+ # @option bunches [Integer] :multiplier Optional, defaults to 1, and typically 1 or -1 to describe whether the Bunch total is to be added or subtracted
33
+ # @return [GamesDice::Dice]
10
34
  def initialize( bunches, offset = 0, name = '' )
11
35
  @name = name
12
36
  @offset = offset
@@ -15,24 +39,29 @@ class GamesDice::Dice
15
39
  @result = nil
16
40
  end
17
41
 
18
- # the string name as provided to the constructor, it will appear in explain_result
42
+ # Name to help identify dice
43
+ # @return [String]
19
44
  attr_reader :name
20
45
 
21
- # an array of GamesDice::Bunch objects that together describe all the dice and roll-altering
22
- # rules that apply to the GamesDice::Dice object
46
+ # Bunches of dice that are components of the object
47
+ # @return [Array<GamesDice::Bunch>]
23
48
  attr_reader :bunches
24
49
 
25
- # an array of Integers, used to multiply result from each bunch when total results are summed
50
+ # Multipliers for each bunch of identical dice. Typically 1 or -1 to represent groups of dice that
51
+ # are either added or subtracted from the total.
52
+ # @return [Array<Integer>]
26
53
  attr_reader :bunch_multipliers
27
54
 
28
- # the integer offset that is added to the total result from all bunches
55
+ # Fixed offset added to sum of all bunches.
56
+ # @return [Integer]
29
57
  attr_reader :offset
30
58
 
31
- # after calling #roll, this is set to the total integer value as calculated by simulating all the
32
- # defined dice and their rules
59
+ # Result of most-recent roll, or nil if no roll made yet.
60
+ # @return [Integer,nil]
33
61
  attr_reader :result
34
62
 
35
- # simulate dice roll. Returns integer final total, and also stores same value in #result
63
+ # Simulates rolling dice
64
+ # @return [Integer] Sum of all rolled dice
36
65
  def roll
37
66
  @result = @offset + @bunch_multipliers.zip(@bunches).inject(0) do |total,mb|
38
67
  m,b = mb
@@ -40,6 +69,9 @@ class GamesDice::Dice
40
69
  end
41
70
  end
42
71
 
72
+ # @!attribute [r] min
73
+ # Minimum possible result from a call to #roll
74
+ # @return [Integer]
43
75
  def min
44
76
  @min ||= @offset + @bunch_multipliers.zip(@bunches).inject(0) do |total,mb|
45
77
  m,b = mb
@@ -47,6 +79,9 @@ class GamesDice::Dice
47
79
  end
48
80
  end
49
81
 
82
+ # @!attribute [r] max
83
+ # Maximum possible result from a call to #roll
84
+ # @return [Integer]
50
85
  def max
51
86
  @max ||= @offset + @bunch_multipliers.zip(@bunches).inject(0) do |total,mb|
52
87
  m,b = mb
@@ -54,10 +89,17 @@ class GamesDice::Dice
54
89
  end
55
90
  end
56
91
 
92
+ # @!attribute [r] minmax
93
+ # Convenience method, same as [dice.min, dice.max]
94
+ # @return [Array<Integer>]
57
95
  def minmax
58
96
  [min,max]
59
97
  end
60
98
 
99
+ # Calculates the probability distribution for the dice. When the dice include components with
100
+ # open-ended re-roll rules, there are some arbitrary limits imposed to prevent large amounts of
101
+ # recursion.
102
+ # @return [GamesDice::Probabilities] Probability distribution of dice.
61
103
  def probabilities
62
104
  return @probabilities if @probabilities
63
105
  probs = @bunch_multipliers.zip(@bunches).inject( GamesDice::Probabilities.new( { @offset => 1.0 } ) ) do |probs, mb|
@@ -66,6 +108,8 @@ class GamesDice::Dice
66
108
  end
67
109
  end
68
110
 
111
+ # @!attribute [r] explain_result
112
+ # @return [String,nil] Explanation of result, or nil if no call to #roll yet.
69
113
  def explain_result
70
114
  return nil unless @result
71
115
  explanations = @bunches.map { |bunch| bunch.label + ": " + bunch.explain_result }
@@ -1,16 +1,37 @@
1
- # returned by any complex die roll (i.e. one that may be subject to re-rolls or adjustments to each die)
1
+ # This class models the output of GamesDice::ComplexDie.
2
+ #
3
+ # An object of the class represents the results of a roll of a ComplexDie, including any re-rolls and
4
+ # value mapping.
5
+ #
6
+ # @example Building up a result manually
2
7
  # dr = GamesDice::DieResult.new
3
- # dr.add_roll(5)
4
- # dr.add_roll(4,:reroll_add)
5
- # dr.value # => 9
6
- # dr.rolls # => [5,4]
7
- # dr.roll_reasons # => [:basic,:reroll_add]
8
- # dr + 5 # => 14
9
- # As the last example implies, GamesDice::DieResult objects coerce to the #value attribute
8
+ # dr.add_roll 5
9
+ # dr.add_roll 4, :reroll_replace
10
+ # dr.value # => 4
11
+ # dr.rolls # => [5, 4]
12
+ # dr.roll_reasons # => [:basic, :reroll_replace]
13
+ # # dr can behave as dr.value due to coercion and support for some operators
14
+ # dr + 6 # => 10
15
+ #
16
+ # @example Using a result from GamesDice::ComplexDie
17
+ # # An "exploding" six-sided die that needs a result of 8 to score "1 Success"
18
+ # d = GamesDice::ComplexDie.new( 6, :rerolls => [[6, :<=, :reroll_add]], :maps => [[8, :<=, 1, 'Success']] )
19
+ # # Generate result object by rolling the die
20
+ # dr = d.roll
21
+ # dr.rolls # => [6, 3]
22
+ # dr.roll_reasons # => [:basic, :reroll_add]
23
+ # dr.total # => 9
24
+ # dr.value # => 1
25
+ # dr.explain_value # => "[6+3] 9 Success"
26
+ #
27
+
10
28
  class GamesDice::DieResult
11
29
  include Comparable
12
30
 
13
- # first_roll_result is optional value of first roll of the die
31
+ # Creates new instance of GamesDice::DieResult. The object can be initialised "empty" or with a first result.
32
+ # @param [Integer,nil] first_roll_result Value for first roll of the die.
33
+ # @param [Symbol] first_roll_reason Reason for first roll of the die.
34
+ # @return [GamesDice::DieResult]
14
35
  def initialize( first_roll_result=nil, first_roll_reason=:basic )
15
36
  unless GamesDice::REROLL_TYPES.has_key?(first_roll_reason)
16
37
  raise ArgumentError, "Unrecognised reason for roll #{first_roll_reason}"
@@ -29,27 +50,31 @@ class GamesDice::DieResult
29
50
  @value = @total
30
51
  end
31
52
 
32
- public
33
-
34
- # array of integers
53
+ # The individual die rolls that combined to generate this result.
54
+ # @return [Array<Integer>] Un-processed values of each die roll used for this result.
35
55
  attr_reader :rolls
36
56
 
37
- # array of symbol reasons for making roll
57
+ # The individual reasons for each roll of the die. See GamesDice::RerollRule for allowed values.
58
+ # @return [Array<Symbol>] Reasons for each die roll, indexes match the #rolls Array.
38
59
  attr_reader :roll_reasons
39
60
 
40
- # combined numeric value of all rolls, nil if nothing calculated yet. Often the same number as
41
- # #result, but may be different if there has been a call to #apply_map
61
+ # Combined result of all rolls, *before* mapping.
62
+ # @return [Integer,nil]
42
63
  attr_reader :total
43
64
 
44
- # overall result of applying all rolls, nil if nothing calculated yet
65
+ # Combined result of all rolls, *after* mapping.
66
+ # @return [Integer,nil]
45
67
  attr_reader :value
46
68
 
47
- # true if #apply_map has been called, and no more roll results added since
69
+ # Whether or not #value has been mapped from #total.
70
+ # @return [Boolean]
48
71
  attr_reader :mapped
49
72
 
50
- # stores the value of a simple die roll. roll_result should be an Integer,
51
- # roll_reason is an optional symbol description of why the roll was made
52
- # #total and #value are calculated based on roll_reason
73
+ # Adds value from a new roll to the object. GamesDice::DieResult tracks reasons for the roll
74
+ # and makes the correct adjustment to the total so far. Any mapped value is cleared.
75
+ # @param [Integer] roll_result Value result from rolling the die.
76
+ # @param [Symbol] roll_reason Reason for rolling the die.
77
+ # @return [Integer] Total so far
53
78
  def add_roll( roll_result, roll_reason=:basic )
54
79
  unless GamesDice::REROLL_TYPES.has_key?(roll_reason)
55
80
  raise ArgumentError, "Unrecognised reason for roll #{roll_reason}"
@@ -83,14 +108,21 @@ class GamesDice::DieResult
83
108
  @value = @total
84
109
  end
85
110
 
86
- # sets #value arbitrarily intended for use by GamesDice::MapRule objects
111
+ # Sets value arbitrarily, and notes that the value has been mapped. Used by GamesDice::ComplexDie
112
+ # when there are one or more GamesDice::MapRule objects to process for a die.
113
+ # @param [Integer] to_value Replacement value.
114
+ # @param [String] description Description of what the mapped value represents e.g. "Success"
115
+ # @return [nil]
87
116
  def apply_map( to_value, description = '' )
88
117
  @mapped = true
89
118
  @value = to_value
90
119
  @map_description = description
120
+ return
91
121
  end
92
122
 
93
- # returns a string summary of how #value was obtained, showing all contributing rolls
123
+ # Generates a text description of how #value is determined. If #value has been mapped, includes the
124
+ # map description, but does not include the mapped value.
125
+ # @return [String] Explanation of #value.
94
126
  def explain_value
95
127
  text = ''
96
128
  if @rolls.length < 2
@@ -104,39 +136,46 @@ class GamesDice::DieResult
104
136
  return text
105
137
  end
106
138
 
107
- # returns a string summary of the #total, including effect of any maps that been applied
139
+ # @!visibility private
140
+ # This is mis-named, it doesn't explain the total at all! It is used to generate summaries of keeper dice.
108
141
  def explain_total
109
142
  text = @total.to_s
110
143
  text += ' ' + @map_description if @mapped && @map_description && @map_description.length > 0
111
144
  return text
112
145
  end
113
146
 
147
+ # @!visibility private
114
148
  # all coercions simply use #value (i.e. nil or a Fixnum)
115
149
  def coerce(thing)
116
150
  @value.coerce(thing)
117
151
  end
118
152
 
153
+ # @!visibility private
119
154
  # addition uses #value
120
155
  def +(thing)
121
156
  @value + thing
122
157
  end
123
158
 
159
+ # @!visibility private
124
160
  # subtraction uses #value
125
161
  def -(thing)
126
162
  @value - thing
127
163
  end
128
164
 
165
+ # @!visibility private
129
166
  # multiplication uses #value
130
167
  def *(thing)
131
168
  @value * thing
132
169
  end
133
170
 
171
+ # @!visibility private
134
172
  # comparison <=> uses #value
135
173
  def <=>(other)
136
174
  self.value <=> other
137
175
  end
138
176
 
139
- # implements a deep clone (used for recursive probability calculations)
177
+ # This is a deep clone, all attributes are cloned.
178
+ # @return [GamesDice::DieResult]
140
179
  def clone
141
180
  cloned = GamesDice::DieResult.new()
142
181
  cloned.instance_variable_set('@rolls', @rolls.clone)
@@ -48,11 +48,11 @@ class GamesDice::MapRule
48
48
  # @return [Integer,Range,Object] Object that receives (#trigger_op, die_result)
49
49
  attr_reader :trigger_value
50
50
 
51
- # Mapped value.
51
+ # Value that a die will use after the value has been mapped.
52
52
  # @return [Integer]
53
53
  attr_reader :mapped_value
54
54
 
55
- # Name for mapped value.
55
+ # Name for mapped value, used in explanations.
56
56
  # @return [String]
57
57
  attr_reader :mapped_name
58
58
 
@@ -1,6 +1,11 @@
1
1
  require 'parslet'
2
2
 
3
- # converts string dice descriptions to data usable for the GamesDice::Dice constructor
3
+ # Based on the parslet gem, this class defines the dice mini-language used by GamesDice.create
4
+ #
5
+ # An instance of this class is a parser for the language. There are no user-definable instance
6
+ # variables.
7
+ #
8
+
4
9
  class GamesDice::Parser < Parslet::Parser
5
10
 
6
11
  # Parslet rules that define the dice string grammar.
@@ -62,6 +67,10 @@ class GamesDice::Parser < Parslet::Parser
62
67
  rule(:expressions) { dice_expression.repeat.as(:bunches) }
63
68
  root :expressions
64
69
 
70
+ # Parses a string description in the dice mini-language, and returns data for feeding into
71
+ # GamesDice::Dice constructore.
72
+ # @param [String] dice_description Text to parse e.g. '1d6'
73
+ # @return [Hash] Analysis of dice_description
65
74
  def parse dice_description
66
75
  dice_description = dice_description.to_s.strip
67
76
  # Force first item to start '+' for simpler parse rules
@@ -1,49 +1,84 @@
1
- # utility class for calculating with probabilities for reuslts from GamesDice objects
1
+ # This class models probability distributions for dice systems.
2
+ #
3
+ # An object of this class represents a single distribution, which might be the result of a complex
4
+ # combination of dice.
5
+ #
6
+ # @example Distribution for a six-sided die
7
+ # probs = GamesDice::Probabilities.for_fair_die( 6 )
8
+ # probs.min # => 1
9
+ # probs.max # => 6
10
+ # probs.expected # => 3.5
11
+ # probs.p_ge( 4 ) # => 0.5
12
+ #
13
+ # @example Adding two distributions
14
+ # pd6 = GamesDice::Probabilities.for_fair_die( 6 )
15
+ # probs = GamesDice::Probabilities.add_distributions( pd6, pd6 )
16
+ # probs.min # => 2
17
+ # probs.max # => 12
18
+ # probs.expected # => 7.0
19
+ # probs.p_ge( 10 ) # => 0.16666666666666669
20
+ #
2
21
  class GamesDice::Probabilities
3
- # prob_hash is a Hash with each key as an Integer, and the associated value being the probability
4
- # of getting that value. It is not validated. Avoid using the default constructor if
5
- # one of the factory methods or calculation methods already does what you need.
22
+
23
+ # Creates new instance of GamesDice::Probabilities.
24
+ # @param [Hash] prob_hash A hash representation of the distribution, each key is an integer result,
25
+ # and the matching value is probability of getting that result
26
+ # @return [GamesDice::Probabilities]
6
27
  def initialize( prob_hash = { 0 => 1.0 } )
7
- # This should *probably* be validated in future
28
+ # This should *probably* be validated in future, but that would impact performance
8
29
  @ph = prob_hash
9
30
  end
10
31
 
11
- # the Hash representation of probabilities. TODO: Hide this from public interface, but make it available
12
- # to factory methods
32
+ # @!visibility private
33
+ # the Hash representation of probabilities.
13
34
  attr_reader :ph
14
35
 
15
- # a clone of probability data (as provided to constructor), safe to pass to methods that modify in place
36
+ # A hash representation of the distribution. Each key is an integer result,
37
+ # and the matching value is probability of getting that result. A new hash is generated on each
38
+ # call to this method.
39
+ # @return [Hash]
16
40
  def to_h
17
41
  @ph.clone
18
42
  end
19
43
 
44
+ # @!attribute [r] min
45
+ # Minimum result in the distribution
46
+ # @return [Integer]
20
47
  def min
21
48
  (@minmax ||= @ph.keys.minmax )[0]
22
49
  end
23
50
 
51
+ # @!attribute [r] max
52
+ # Maximum result in the distribution
53
+ # @return [Integer]
24
54
  def max
25
55
  (@minmax ||= @ph.keys.minmax )[1]
26
56
  end
27
57
 
28
- # returns mean expected value as a Float
58
+ # @!attribute [r] expected
59
+ # Expected value of distribution.
60
+ # @return [Float]
29
61
  def expected
30
62
  @expected ||= @ph.inject(0.0) { |accumulate,p| accumulate + p[0] * p[1] }
31
63
  end
32
64
 
33
- # returns Float probability fram Range (0.0..1.0) that a value chosen from the distribution will
34
- # be equal to target integer
65
+ # Probability of result equalling specific target
66
+ # @param [Integer] target
67
+ # @return [Float] in range (0.0..1.0)
35
68
  def p_eql target
36
69
  @ph[ Integer(target) ] || 0.0
37
70
  end
38
71
 
39
- # returns Float probability fram Range (0.0..1.0) that a value chosen from the distribution will
40
- # be a number greater than target integer
72
+ # Probability of result being greater than specific target
73
+ # @param [Integer] target
74
+ # @return [Float] in range (0.0..1.0)
41
75
  def p_gt target
42
76
  p_ge( Integer(target) + 1 )
43
77
  end
44
78
 
45
- # returns Float probability fram Range (0.0..1.0) that a value chosen from the distribution will
46
- # be a number greater than or equal to target integer
79
+ # Probability of result being equal to or greater than specific target
80
+ # @param [Integer] target
81
+ # @return [Float] in range (0.0..1.0)
47
82
  def p_ge target
48
83
  target = Integer(target)
49
84
  return @prob_ge[target] if @prob_ge && @prob_ge[target]
@@ -54,7 +89,9 @@ class GamesDice::Probabilities
54
89
  @prob_ge[target] = @ph.select {|k,v| target <= k}.inject(0.0) {|so_far,pv| so_far + pv[1] }
55
90
  end
56
91
 
57
- # returns probability than a roll will produce a number less than or equal to target integer
92
+ # Probability of result being equal to or less than specific target
93
+ # @param [Integer] target
94
+ # @return [Float] in range (0.0..1.0)
58
95
  def p_le target
59
96
  target = Integer(target)
60
97
  return @prob_le[target] if @prob_le && @prob_le[target]
@@ -65,12 +102,16 @@ class GamesDice::Probabilities
65
102
  @prob_le[target] = @ph.select {|k,v| target >= k}.inject(0.0) {|so_far,pv| so_far + pv[1] }
66
103
  end
67
104
 
68
- # returns probability than a roll will produce a number less than target integer
105
+ # Probability of result being less than specific target
106
+ # @param [Integer] target
107
+ # @return [Float] in range (0.0..1.0)
69
108
  def p_lt target
70
109
  p_le( Integer(target) - 1 )
71
110
  end
72
111
 
73
- # constructor returns probability distrubution for a simple fair die
112
+ # Distribution for a die with equal chance of rolling 1..N
113
+ # @param [Integer] sides Number of sides on die
114
+ # @return [GamesDice::Probabilities]
74
115
  def self.for_fair_die sides
75
116
  sides = Integer(sides)
76
117
  raise ArgumentError, "sides must be at least 1" unless sides > 0
@@ -80,8 +121,11 @@ class GamesDice::Probabilities
80
121
  GamesDice::Probabilities.new( h )
81
122
  end
82
123
 
83
- # adding two probability distributions calculates a new distribution, representing what would
84
- # happen if you created a random number using the sum of numbers from both distributions
124
+ # Combines two distributions to create a third, that represents the distribution created when adding
125
+ # results together.
126
+ # @param [GamesDice::Probabilities] pd_a First distribution
127
+ # @param [GamesDice::Probabilities] pd_b Second distribution
128
+ # @return [GamesDice::Probabilities]
85
129
  def self.add_distributions pd_a, pd_b
86
130
  h = {}
87
131
  pd_a.ph.each do |ka,pa|
@@ -94,8 +138,13 @@ class GamesDice::Probabilities
94
138
  GamesDice::Probabilities.new( h )
95
139
  end
96
140
 
97
- # adding two probability distributions calculates a new distribution, representing what would
98
- # happen if you created a random number using the sum of numbers from both distributions
141
+ # Combines two distributions with multipliers to create a third, that represents the distribution
142
+ # created when adding weighted results together.
143
+ # @param [Integer] m_a Weighting for first distribution
144
+ # @param [GamesDice::Probabilities] pd_a First distribution
145
+ # @param [Integer] m_b Weighting for second distribution
146
+ # @param [GamesDice::Probabilities] pd_b Second distribution
147
+ # @return [GamesDice::Probabilities]
99
148
  def self.add_distributions_mult m_a, pd_a, m_b, pd_b
100
149
  h = {}
101
150
  pd_a.ph.each do |ka,pa|
@@ -108,4 +157,4 @@ class GamesDice::Probabilities
108
157
  GamesDice::Probabilities.new( h )
109
158
  end
110
159
 
111
- end # class Dice
160
+ end # class GamesDice::Probabilities
@@ -1,3 +1,3 @@
1
1
  module GamesDice
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.3"
3
3
  end
@@ -1,4 +1,5 @@
1
1
  require 'games_dice'
2
+ require 'helpers'
2
3
 
3
4
  describe GamesDice::Bunch do
4
5
 
@@ -1,4 +1,5 @@
1
1
  require 'games_dice'
2
+ require 'helpers'
2
3
 
3
4
  describe GamesDice::ComplexDie do
4
5
 
@@ -1,4 +1,5 @@
1
1
  require 'games_dice'
2
+ require 'helpers'
2
3
 
3
4
  describe GamesDice::Die do
4
5
 
@@ -1,4 +1,5 @@
1
1
  require 'games_dice'
2
+ require 'helpers'
2
3
 
3
4
  describe GamesDice::Probabilities do
4
5
 
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.2
4
+ version: 0.2.3
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-06-10 00:00:00.000000000 Z
12
+ date: 2013-06-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -144,7 +144,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
144
144
  version: '0'
145
145
  segments:
146
146
  - 0
147
- hash: -2482711703426352461
147
+ hash: 1797445249921181101
148
148
  required_rubygems_version: !ruby/object:Gem::Requirement
149
149
  none: false
150
150
  requirements:
@@ -153,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
153
  version: '0'
154
154
  segments:
155
155
  - 0
156
- hash: -2482711703426352461
156
+ hash: 1797445249921181101
157
157
  requirements: []
158
158
  rubyforge_project:
159
159
  rubygems_version: 1.8.24