Class: GamesDice::Bunch

Inherits:
Object
  • Object
show all
Defined in:
lib/games_dice/bunch.rb

Overview

This class models a number of identical dice, which may be either GamesDice::Die or GamesDice::ComplexDie objects.

An object of this class represents a fixed number of indentical dice that may be rolled and their values summed to make a total for the bunch.

Examples:

The ubiquitous '3d6'

d = GamesDice::Bunch.new( :ndice => 3, :sides => 6 )
d.roll # => 14
d.result # => 14
d.explain_result # => "2 + 6 + 6 = 14"
d.max # => 18

Roll 5d10, and keep the best 2

d = GamesDice::Bunch.new( :ndice => 5, :sides => 10 , :keep_mode => :keep_best, :keep_number => 2 )
d.roll # => 18
d.result # => 18
d.explain_result # => "4, 9, 2, 9, 1. Keep: 9 + 9 = 18"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ GamesDice::Bunch

The constructor accepts parameters that are suitable for either GamesDice::Die or GamesDice::ComplexDie and decides which of those classes to instantiate.

Options Hash (options):

  • :ndice (Integer)

    Number of dice in the bunch, mandatory

  • :sides (Integer)

    Number of sides on a single die in the bunch, mandatory

  • :name (String)

    Optional name for the bunch

  • :rerolls (Array<GamesDice::RerollRule,Array>)

    Optional rules that cause the die to roll again

  • :maps (Array<GamesDice::MapRule,Array>)

    Optional rules to convert a value into a final result for the die

  • :prng (#rand)

    Optional alternative source of randomness to Ruby's built-in #rand, passed to GamesDice::Die's constructor

  • :keep_mode (Symbol)

    Optional, either :keep_best or :keep_worst

  • :keep_number (Integer)

    Optional number of dice to keep when :keep_mode is not nil


34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/games_dice/bunch.rb', line 34

def initialize( options )
  name_number_sides_from_hash( options )
  keep_mode_from_hash( options )

  if options[:prng]
    raise ":prng does not support the rand() method" if ! options[:prng].respond_to?(:rand)
  end

  if options[:rerolls] || options[:maps]
    @single_die = GamesDice::ComplexDie.new( @sides, complex_die_params_from_hash( options ) )
  else
    @single_die = GamesDice::Die.new( @sides, options[:prng] )
  end
end

Instance Attribute Details

#explain_resultString? (readonly)

Explanation of result, or nil if no call to #roll yet.


166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/games_dice/bunch.rb', line 166

def explain_result
  return nil unless @result

  explanation = ''

  # With #keep_mode, we may need to show unused and used dice separately
  used_dice = result_details
  unused_dice = []

  # Pick highest numbers and their associated details
  if @keep_mode && @keep_number < @ndice
    full_dice = result_details.sort_by { |die_result| die_result.total }
    case @keep_mode
    when :keep_best then
      used_dice = full_dice[-@keep_number..-1]
      unused_dice = full_dice[0..full_dice.length-1-@keep_number]
    when :keep_worst then
      used_dice = full_dice[0..(@keep_number-1)]
      unused_dice = full_dice[@keep_number..(full_dice.length-1)]
    end
  end

  # Show unused dice (if any)
  if @keep_mode || @single_die.maps
    explanation += result_details.map do |die_result|
      die_result.explain_value
    end.join(', ')
    if @keep_mode
      separator = @single_die.maps ? ', ' : ' + '
      explanation += ". Keep: " + used_dice.map do |die_result|
        die_result.explain_total
      end.join( separator )
    end
    if @single_die.maps
      explanation += ". Successes: #{@result}"
    end
    explanation += " = #{@result}" if @keep_mode && ! @single_die.maps && @keep_number > 1
  else
    explanation += used_dice.map do |die_result|
      die_result.explain_value
    end.join(' + ')
    explanation += " = #{@result}" if @ndice > 1
  end

  explanation
end

#keep_modeSymbol? (readonly)

Can be nil, :keep_best or :keep_worst


63
64
65
# File 'lib/games_dice/bunch.rb', line 63

def keep_mode
  @keep_mode
end

#keep_numberInteger? (readonly)

Number of “best” or “worst” results to select when #keep_mode is not nil.


67
68
69
# File 'lib/games_dice/bunch.rb', line 67

def keep_number
  @keep_number
end

#labelString (readonly)

Description that will be used in explanations with more than one bunch


76
77
78
79
# File 'lib/games_dice/bunch.rb', line 76

def label
  return @name if @name != ''
  return @ndice.to_s + 'd' + @sides.to_s
end

#mapsArray<GamesDice::MapRule>? (readonly)

Sequence of map rules, or nil if mapping is not required.


91
92
93
# File 'lib/games_dice/bunch.rb', line 91

def maps
  @single_die.maps
end

#maxInteger (readonly)

Maximum possible result from a call to #roll


115
116
117
118
# File 'lib/games_dice/bunch.rb', line 115

def max
  n = @keep_mode ? [@keep_number,@ndice].min : @ndice
  return n * @single_die.max
end

#minInteger (readonly)

Minimum possible result from a call to #roll


107
108
109
110
# File 'lib/games_dice/bunch.rb', line 107

def min
  n = @keep_mode ? [@keep_number,@ndice].min : @ndice
  return n * @single_die.min
end

#nameString (readonly)

Name to help identify bunch


51
52
53
# File 'lib/games_dice/bunch.rb', line 51

def name
  @name
end

#ndiceInteger (readonly)

Number of dice to roll


55
56
57
# File 'lib/games_dice/bunch.rb', line 55

def ndice
  @ndice
end

#rerollsArray<GamesDice::RerollRule>? (readonly)

Sequence of re-roll rules, or nil if re-rolls are not required.


84
85
86
# File 'lib/games_dice/bunch.rb', line 84

def rerolls
  @single_die.rerolls
end

#resultInteger? (readonly)

Result of most-recent roll, or nil if no roll made yet.


71
72
73
# File 'lib/games_dice/bunch.rb', line 71

def result
  @result
end

#result_detailsArray<GamesDice::DieResult>? (readonly)

After calling #roll, this is an array of GamesDice::DieResult objects. There is one from each #single_die rolled, allowing inspection of how the result was obtained.


99
100
101
102
# File 'lib/games_dice/bunch.rb', line 99

def result_details
  return nil unless @raw_result_details
  @raw_result_details.map { |r| r.is_a?(Integer) ? GamesDice::DieResult.new(r) : r }
end

#single_dieGamesDice::Die, GamesDice::ComplexDie (readonly)

Individual die from the bunch


59
60
61
# File 'lib/games_dice/bunch.rb', line 59

def single_die
  @single_die
end

Instance Method Details

#probabilitiesGamesDice::Probabilities

Calculates the probability distribution for the bunch. When the bunch is composed of dice with open-ended re-roll rules, there are some arbitrary limits imposed to prevent large amounts of recursion.


124
125
126
127
128
129
130
131
132
133
134
# File 'lib/games_dice/bunch.rb', line 124

def probabilities
  return @probabilities if @probabilities

  if @keep_mode && @ndice > @keep_number
    @probabilities = @single_die.probabilities.repeat_n_sum_k( @ndice, @keep_number, @keep_mode )
  else
    @probabilities = @single_die.probabilities.repeat_sum( @ndice )
  end

  return @probabilities
end

#rollInteger

Simulates rolling the bunch of identical dice


138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/games_dice/bunch.rb', line 138

def roll
  @result = 0
  @raw_result_details = []

  @ndice.times do
    @result += @single_die.roll
    @raw_result_details << @single_die.result
  end

  if ! @keep_mode
    return @result
  end

  use_dice = if @keep_mode && @keep_number < @ndice
    case @keep_mode
    when :keep_best then @raw_result_details.sort[-@keep_number..-1]
    when :keep_worst then @raw_result_details.sort[0..(@keep_number-1)]
    end
  else
    @raw_result_details
  end

  @result = use_dice.inject(0) { |so_far, die_result| so_far + die_result }
end