games_dice 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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