module NerdDice

Nerd dice allows you to roll polyhedral dice and add bonuses as you would in a tabletop roleplaying game. You can choose to roll multiple dice and keep a specified number of dice such as rolling 4d6 and dropping the lowest for ability scores or rolling with advantage and disadvantage if those mechanics exist in your game.

This module is broken down into multiple source files:

The class_methods file has all of the module_level methods called by NerdDice.method_name

See the README for overall usage for the module

NerdDice class methods This file contains module-level attribute readers and writers and includes the other files that provide the class method functionality.

PUBLIC METHODS NerdDice.configure => ./class_methods/configure.rb NerdDice.configuration => ./class_methods/configure.rb NerdDice.refresh_seed => ./class_methods/refresh_seed.rb NerdDice.execute_die_roll => ./class_methods/execute_die_roll.rb NerdDice.total_dice => ./class_methods/total_dice.rb NerdDice.roll_dice => ./class_methods/roll_dice.rb NerdDice.roll_ability_scores => ./class_methods/roll_ability_scores.rb

Configuration class methods

Usage:

NerdDice.configure takes a block and yields the NerdDice::Configuration
properties:
If you wanted to configure several properties in a block:
<tt>
  NerdDice.configure do |config|
    config.randomization_technique = :randomized
    config.die_background_color = "#FF0000"
  end
</tt>

NerdDice.configuration returns the NerdDice::Configuration object and lets you
set properties on the NerdDice::Configuration object without using a block:
<tt>
  config = NerdDice.configuration
  config.randomization_technique = :randomized
  config.die_background_color = "#FF0000"
</tt>

execute_die_roll class method

Usage:

If you wanted to execute a single d4 die roll without a Die object, you would execute:
<tt>NerdDice.execute_die_roll(4)</tt>

If you wanted to execute a die roll with a different randomization technique
than the one in NerdDice.configuration, you can supply an optional second argument
<tt>NerdDice.execute_die_roll(4, :randomized)</tt>

harvest_totals method

Usage:

This method will take any collection of objects where each element responds to
:total and return an array of the results of the total method.

Example
<tt>
  ability_score_array = NerdDice.roll_ability_scores
  => Array of 6 DiceSet objects
  totals_array = NerdDice.harvest_totals(totals_array)
  => [15, 14, 13, 12, 10, 8]
  # yes, it just happened to be the standard array by amazing coincidence
</tt>

refresh_seed! class method

Usage:

NerdDice.refresh_seed! by default will refresh the seed for the generator
configured in NerdDice.configuration. It can also be used with arguments
to set a particular seed for use with deterministic testing. It sets
the count_since_last_refresh to 0 whenever called.

It cannot refresh or manipulate the seed for SecureRandom

<tt>NerdDice.refresh_seed!</tt>

With options
<tt>
  previous_seed_data = NerdDice.refresh_seed!(
    randomization_technique: :randomized,
    random_rand_seed: 1337,
    random_object_seed: 24601
  )
</tt>

roll_ability_scores method

Usage:

If you wanted to get an array of DiceSet objects with your ability scores and
default configuration you would execute:
<tt>ability_score_array = NerdDice.roll_ability_scores</tt>

If you wanted to specify configuration for the current operation without
modifying the NerdDice.configuration, you can supply options for both the
ability_score configuration and the properties of the DiceSet objects returned.
Properties are specified in the method comment
<tt>
  ability_score_array = NerdDice.roll_ability_scores(
    ability_score_array_size: 7,
    ability_score_number_of_sides: 8,
    ability_score_dice_rolled: 5,
    ability_score_dice_kept: 4,
    randomization_technique: :randomized,
    foreground_color: "#FF0000",
    background_color: "#FFFFFF"
  )
</tt>

roll_dice class method

Usage:

If you wanted to roll a single d4, you would execute:
<tt>dice_set = NerdDice.roll_dice(4)</tt>

If you wanted to roll 3d6, you would execute
<tt>dice_set = NerdDice.roll_dice(6, 3)</tt>

If you wanted to roll a d20 and add 5 to the value, you would execute
<tt>dice_set = NerdDice.roll_dice(20, 1, bonus: 5)</tt>

Since this method returns a DiceSet, you can call any of the DiceSet
methods on the result. See the README for more details and options

total_ability_scores method

Usage:

If you wanted to get an array of only the values of ability scores and
default configuration you would execute:
<tt>ability_score_array = NerdDice.total_ability_scores</tt>
  => [15, 14, 13, 12, 10, 8]

If you wanted to specify configuration for the current operation without
modifying the NerdDice.configuration, you can supply options for both the
ability_score configuration and the properties of the DiceSet objects returned.

Many of the properties available in roll_ability_scores will not be relevant
to total_ability_scores but the method delegates all options passed without
filtering.
<tt>
  ability_score_array = NerdDice.total_ability_scores(
    ability_score_array_size: 7,
    ability_score_number_of_sides: 8,
    ability_score_dice_rolled: 5,
    ability_score_dice_kept: 4,
    randomization_technique: :randomized
  )
  => [27, 22, 13, 23, 20, 24, 23]
</tt>

total_dice class method

Usage:

If you wanted to roll a single d4, you would execute:
<tt>NerdDice.total_dice(4)</tt>

If you wanted to roll 3d6, you would execute
<tt>NerdDice.total_dice(6, 3)</tt>

If you wanted to roll a d20 and add 5 to the value, you would execute
<tt>NerdDice.total_dice(20, 1, bonus: 5)</tt>

The bonus in the options hash must be an Integer duck type or nil

Constants

ABILITY_SCORE_KEYS
RANDOMIZATION_TECHNIQUES
VERSION

Attributes

count_since_last_refresh[R]

Public Class Methods

configuration() click to toggle source

configuration class method

Arguments: None Provides the lazy-loaded class instance variable @configuration Return (NerdDice::Configuration) the Configuration object tied to the

@configuration class instance variable
# File lib/nerd_dice/class_methods/configure.rb, line 46
def configuration
  @configuration ||= Configuration.new
end
configure() { |configuration| ... } click to toggle source

configure class method

Arguments: None Expects and yields to a block where configuration is specified. See README and NerdDice::Configuration class for config options Return (NerdDice::Configuration) the Configuration object tied to the

@configuration class instance variable
# File lib/nerd_dice/class_methods/configure.rb, line 34
def configure
  yield configuration
  configuration
end
execute_die_roll(number_of_sides, using_generator = nil) click to toggle source

Arguments:

number_of_sides (Integer) =>  the number of sides of the die to roll
using_generator (Symbol) => must be one of the symbols in
  RANDOMIZATION_TECHNIQUES or nil

Return (Integer) => Value of the single die rolled

# File lib/nerd_dice/class_methods/execute_die_roll.rb, line 21
def execute_die_roll(number_of_sides, using_generator = nil)
  @count_since_last_refresh ||= 0
  gen = get_number_generator(using_generator)
  result = gen.rand(number_of_sides) + 1
  increment_and_evalutate_refresh_seed
  result
end
harvest_totals(collection) click to toggle source

Arguments:

collection (Enumerable) a collection where each element responds to total

Return (Array) => Data type of each element will be whatever is returned by total method

# File lib/nerd_dice/class_methods/harvest_totals.rb, line 24
def harvest_totals(collection)
  collection.map(&:total)
rescue NoMethodError => e
  specific_message =
    case e.message
    when /`total'/ then "Each element must respond to :total."
    when /`map'/ then "Argument must respond to :map."
    end
  specific_message ? raise(ArgumentError, "You must provide a valid collection. #{specific_message}") : raise
end
refresh_seed!(**opts) click to toggle source

Options: (none required)

randomization_technique (Symbol) => must be one of the symbols in
  RANDOMIZATION_TECHNIQUES if specified
random_rand_seed (Integer) => Seed to set for Random
random_object_seed (Integer) => Seed to set for new Random object

Return (Hash or nil) => Previous values of generator seeds that were refreshed

# File lib/nerd_dice/class_methods/refresh_seed.rb, line 32
def refresh_seed!(**opts)
  technique, random_rand_new_seed, random_object_new_seed = parse_refresh_options(opts)
  @count_since_last_refresh = 0
  return nil if technique == :securerandom

  reset_appropriate_seeds!(technique, random_rand_new_seed, random_object_new_seed)
end
roll_ability_scores(**opts) click to toggle source

Arguments:

opts (options Hash, DEFAULT: {}) any options you wish to include
  ABILITY SCORE OPTIONS
  :ability_score_array_size DEFAULT NerdDice.configuration.ability_score_array_size
  :ability_score_number_of_sides DEFAULT NerdDice.configuration.ability_score_number_of_sides
  :ability_score_dice_rolled DEFAULT NerdDice.configuration.ability_score_dice_rolled
  :ability_score_dice_kept DEFAULT NerdDice.configuration.ability_score_dice_kept

  DICE SET OPTIONS
  :randomization_technique (Symbol) => must be one of the symbols in
    RANDOMIZATION_TECHNIQUES or nil
  :foreground_color (String) => should resolve to a valid CSS color (format flexible)
  :background_color (String) => should resolve to a valid CSS color (format flexible)

Return (Array of NerdDice::DiceSet) => One NerdDice::DiceSet element for each ability score rubocop:disable Metrics/MethodLength

# File lib/nerd_dice/class_methods/roll_ability_scores.rb, line 44
def roll_ability_scores(**opts)
  dice_opts = opts.reject { |key, _value| key.to_s.match?(/\Aability_score_[a-z_]+\z/) }
  ability_score_options = interpret_ability_score_options(opts)
  ability_score_array = []
  ability_score_options[:ability_score_array_size].times do
    ability_score_array << roll_dice(
      ability_score_options[:ability_score_number_of_sides],
      ability_score_options[:ability_score_dice_rolled],
      **dice_opts
    ).highest(
      ability_score_options[:ability_score_dice_kept]
    )
  end
  ability_score_array
end
roll_dice(number_of_sides, number_of_dice = 1, **opts) click to toggle source

roll_dice class method

Arguments:

number_of_sides (Integer) =>  the number of sides of the dice to roll
number_of_dice (Integer, DEFAULT: 1) => the quantity to roll of the type
  of die specified in the number_of_sides argument.
opts (options Hash, DEFAULT: {}) any additional options you wish to include
  :bonus (Integer) => The total bonus (positive integer) or penalty
    (negative integer) to modify the total by. Is added to the total of
    all dice after they are totaled, not to each die rolled
  :randomization_technique (Symbol) => must be one of the symbols in
    RANDOMIZATION_TECHNIQUES or nil
  :foreground_color (String) => should resolve to a valid CSS color (format flexible)
  :background_color (String) => should resolve to a valid CSS color (format flexible)
  :damage_type: (String) => damage type for the dice set, if applicable

Return (NerdDice::DiceSet) => Collection object with one or more Die objects

You can call roll_dice().total to get similar functionality to total_dice or you can chain methods together roll_dice(6, 4, bonus: 3).with_advantage(3).total

# File lib/nerd_dice/class_methods/roll_dice.rb, line 41
def roll_dice(number_of_sides, number_of_dice = 1, **opts)
  DiceSet.new(number_of_sides, number_of_dice, **opts)
end
total_ability_scores(**opts) click to toggle source

Arguments:

opts (options Hash, DEFAULT: {}) any options you wish to include
  ABILITY SCORE OPTIONS
  :ability_score_array_size DEFAULT NerdDice.configuration.ability_score_array_size
  :ability_score_number_of_sides DEFAULT NerdDice.configuration.ability_score_number_of_sides
  :ability_score_dice_rolled DEFAULT NerdDice.configuration.ability_score_dice_rolled
  :ability_score_dice_kept DEFAULT NerdDice.configuration.ability_score_dice_kept

  DICE SET OPTIONS
  :randomization_technique (Symbol) => must be one of the symbols in
    RANDOMIZATION_TECHNIQUES or nil

  ARGUMENTS PASSED ON THAT DO NOT REALLY MATTER
  :foreground_color (String) => should resolve to a valid CSS color (format flexible)
  :background_color (String) => should resolve to a valid CSS color (format flexible)

Return (Array of Integers) => One Integer element for each ability score

# File lib/nerd_dice/class_methods/total_ability_scores.rb, line 48
def total_ability_scores(**opts)
  harvest_totals(roll_ability_scores(**opts))
end
total_dice(number_of_sides, number_of_dice = 1, **opts) click to toggle source

Arguments:

number_of_sides (Integer) =>  the number of sides of the dice to roll
number_of_dice (Integer, DEFAULT: 1) => the quantity to roll of the type
  of die specified in the number_of_sides argument.
opts (options Hash, DEFAULT: {}) any additional options you wish to include
  :bonus (Integer) => The total bonus (positive integer) or penalty
    (negative integer) to modify the total by. Is added to the total of
    all dice after they are totaled, not to each die rolled
  :randomization_technique (Symbol) => must be one of the symbols in
    RANDOMIZATION_TECHNIQUES or nil

Return (Integer) => Total of the dice rolled, plus modifier if applicable

# File lib/nerd_dice/class_methods/total_dice.rb, line 31
def total_dice(number_of_sides, number_of_dice = 1, **opts)
  total = 0
  number_of_dice.times do
    total += execute_die_roll(number_of_sides, opts[:randomization_technique])
  end
  begin
    total += opts[:bonus].to_i
  rescue NoMethodError
    raise ArgumentError, "Bonus must be a value that responds to :to_i"
  end
  total
end

Private Class Methods

get_number_generator(using_generator = nil) click to toggle source
# File lib/nerd_dice/class_methods/execute_die_roll.rb, line 31
def get_number_generator(using_generator = nil)
  using_generator ||= configuration.randomization_technique
  case using_generator
  when :securerandom then SecureRandom
  when :random_rand then Random
  when :random_object then @random_object ||= Random.new
  when :randomized then random_generator
  else raise ArgumentError, "Unrecognized generator. Must be one of #{RANDOMIZATION_TECHNIQUES.join(', ')}"
  end
end
increment_and_evalutate_refresh_seed() click to toggle source
# File lib/nerd_dice/class_methods/refresh_seed.rb, line 76
def increment_and_evalutate_refresh_seed
  @count_since_last_refresh += 1
  return unless configuration.refresh_seed_interval

  refresh_seed! if @count_since_last_refresh >= configuration.refresh_seed_interval
end
interpret_ability_score_options(opts) click to toggle source

rubocop:enable Metrics/MethodLength

# File lib/nerd_dice/class_methods/roll_ability_scores.rb, line 63
def interpret_ability_score_options(opts)
  return_hash = {}
  ABILITY_SCORE_KEYS.each { |key| return_hash[key] = parse_ability_score_option(opts, key) }
  return_hash
end
parse_ability_score_option(option_hash, option_key) click to toggle source
# File lib/nerd_dice/class_methods/roll_ability_scores.rb, line 69
def parse_ability_score_option(option_hash, option_key)
  option_hash[option_key] || configuration.send(option_key.to_s)
end
parse_refresh_options(opts) click to toggle source
# File lib/nerd_dice/class_methods/refresh_seed.rb, line 42
def parse_refresh_options(opts)
  [
    opts[:randomization_technique] || configuration.randomization_technique,
    opts[:random_rand_seed],
    opts[:random_object_seed]
  ]
end
random_generator() click to toggle source
# File lib/nerd_dice/class_methods/execute_die_roll.rb, line 42
def random_generator
  gen = RANDOMIZATION_TECHNIQUES.reject { |el| el == :randomized }.sample
  get_number_generator(gen)
end
refresh_random_object_seed!(new_seed) click to toggle source
# File lib/nerd_dice/class_methods/refresh_seed.rb, line 70
def refresh_random_object_seed!(new_seed)
  old_seed = @random_object&.seed
  @random_object = new_seed ? Random.new(new_seed) : Random.new
  old_seed
end
refresh_random_rand_seed!(new_seed) click to toggle source

rubocop:enable Metrics/MethodLength

# File lib/nerd_dice/class_methods/refresh_seed.rb, line 66
def refresh_random_rand_seed!(new_seed)
  new_seed ? Random.srand(new_seed) : Random.srand
end
reset_appropriate_seeds!(technique, random_rand_new_seed, random_object_new_seed) click to toggle source

rubocop:disable Metrics/MethodLength

# File lib/nerd_dice/class_methods/refresh_seed.rb, line 51
def reset_appropriate_seeds!(technique, random_rand_new_seed, random_object_new_seed)
  return_hash = {}
  case technique
  when :random_rand
    return_hash[:random_rand_prior_seed] = refresh_random_rand_seed!(random_rand_new_seed)
  when :random_object
    return_hash[:random_object_prior_seed] = refresh_random_object_seed!(random_object_new_seed)
  when :randomized
    return_hash[:random_rand_prior_seed] = refresh_random_rand_seed!(random_rand_new_seed)
    return_hash[:random_object_prior_seed] = refresh_random_object_seed!(random_object_new_seed)
  end
  return_hash
end