nerd_dice 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/main.yml +3 -3
- data/.rubocop.yml +181 -14
- data/CHANGELOG.md +66 -4
- data/Gemfile +2 -2
- data/Gemfile.lock +47 -51
- data/README.md +178 -6
- data/bin/generate_checksums +2 -2
- data/bin/nerd_dice_benchmark +112 -16
- data/certs/msducheminjr.pem +24 -25
- data/lib/nerd_dice/class_methods/configure.rb +50 -0
- data/lib/nerd_dice/class_methods/execute_die_roll.rb +47 -0
- data/lib/nerd_dice/class_methods/harvest_totals.rb +35 -0
- data/lib/nerd_dice/class_methods/refresh_seed.rb +83 -0
- data/lib/nerd_dice/class_methods/roll_ability_scores.rb +73 -0
- data/lib/nerd_dice/class_methods/roll_dice.rb +45 -0
- data/lib/nerd_dice/class_methods/total_ability_scores.rb +52 -0
- data/lib/nerd_dice/class_methods/total_dice.rb +44 -0
- data/lib/nerd_dice/class_methods.rb +30 -0
- data/lib/nerd_dice/configuration.rb +46 -2
- data/lib/nerd_dice/dice_set.rb +166 -0
- data/lib/nerd_dice/die.rb +51 -0
- data/lib/nerd_dice/sets_randomization_technique.rb +19 -0
- data/lib/nerd_dice/version.rb +1 -1
- data/lib/nerd_dice.rb +9 -159
- data/nerd_dice.gemspec +5 -6
- data.tar.gz.sig +0 -0
- metadata +57 -47
- metadata.gz.sig +0 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
############################
|
4
|
+
# refresh_seed! class method
|
5
|
+
############################
|
6
|
+
# Usage:
|
7
|
+
# NerdDice.refresh_seed! by default will refresh the seed for the generator
|
8
|
+
# configured in NerdDice.configuration. It can also be used with arguments
|
9
|
+
# to set a particular seed for use with deterministic testing. It sets
|
10
|
+
# the count_since_last_refresh to 0 whenever called.
|
11
|
+
#
|
12
|
+
# It cannot refresh or manipulate the seed for SecureRandom
|
13
|
+
#
|
14
|
+
# <tt>NerdDice.refresh_seed!</tt>
|
15
|
+
#
|
16
|
+
# With options
|
17
|
+
# <tt>
|
18
|
+
# previous_seed_data = NerdDice.refresh_seed!(
|
19
|
+
# randomization_technique: :randomized,
|
20
|
+
# random_rand_seed: 1337,
|
21
|
+
# random_object_seed: 24601
|
22
|
+
# )
|
23
|
+
# </tt>
|
24
|
+
module NerdDice
|
25
|
+
class << self
|
26
|
+
# Options: (none required)
|
27
|
+
# randomization_technique (Symbol) => must be one of the symbols in
|
28
|
+
# RANDOMIZATION_TECHNIQUES if specified
|
29
|
+
# random_rand_seed (Integer) => Seed to set for Random
|
30
|
+
# random_object_seed (Integer) => Seed to set for new Random object
|
31
|
+
# Return (Hash or nil) => Previous values of generator seeds that were refreshed
|
32
|
+
def refresh_seed!(**opts)
|
33
|
+
technique, random_rand_new_seed, random_object_new_seed = parse_refresh_options(opts)
|
34
|
+
@count_since_last_refresh = 0
|
35
|
+
return nil if technique == :securerandom
|
36
|
+
|
37
|
+
reset_appropriate_seeds!(technique, random_rand_new_seed, random_object_new_seed)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def parse_refresh_options(opts)
|
43
|
+
[
|
44
|
+
opts[:randomization_technique] || configuration.randomization_technique,
|
45
|
+
opts[:random_rand_seed],
|
46
|
+
opts[:random_object_seed]
|
47
|
+
]
|
48
|
+
end
|
49
|
+
|
50
|
+
# rubocop:disable Metrics/MethodLength
|
51
|
+
def reset_appropriate_seeds!(technique, random_rand_new_seed, random_object_new_seed)
|
52
|
+
return_hash = {}
|
53
|
+
case technique
|
54
|
+
when :random_rand
|
55
|
+
return_hash[:random_rand_prior_seed] = refresh_random_rand_seed!(random_rand_new_seed)
|
56
|
+
when :random_object
|
57
|
+
return_hash[:random_object_prior_seed] = refresh_random_object_seed!(random_object_new_seed)
|
58
|
+
when :randomized
|
59
|
+
return_hash[:random_rand_prior_seed] = refresh_random_rand_seed!(random_rand_new_seed)
|
60
|
+
return_hash[:random_object_prior_seed] = refresh_random_object_seed!(random_object_new_seed)
|
61
|
+
end
|
62
|
+
return_hash
|
63
|
+
end
|
64
|
+
# rubocop:enable Metrics/MethodLength
|
65
|
+
|
66
|
+
def refresh_random_rand_seed!(new_seed)
|
67
|
+
new_seed ? Random.srand(new_seed) : Random.srand
|
68
|
+
end
|
69
|
+
|
70
|
+
def refresh_random_object_seed!(new_seed)
|
71
|
+
old_seed = @random_object&.seed
|
72
|
+
@random_object = new_seed ? Random.new(new_seed) : Random.new
|
73
|
+
old_seed
|
74
|
+
end
|
75
|
+
|
76
|
+
def increment_and_evalutate_refresh_seed
|
77
|
+
@count_since_last_refresh += 1
|
78
|
+
return unless configuration.refresh_seed_interval
|
79
|
+
|
80
|
+
refresh_seed! if @count_since_last_refresh >= configuration.refresh_seed_interval
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
############################
|
4
|
+
# roll_ability_scores method
|
5
|
+
############################
|
6
|
+
# Usage:
|
7
|
+
# If you wanted to get an array of DiceSet objects with your ability scores and
|
8
|
+
# default configuration you would execute:
|
9
|
+
# <tt>ability_score_array = NerdDice.roll_ability_scores</tt>
|
10
|
+
#
|
11
|
+
# If you wanted to specify configuration for the current operation without
|
12
|
+
# modifying the NerdDice.configuration, you can supply options for both the
|
13
|
+
# ability_score configuration and the properties of the DiceSet objects returned.
|
14
|
+
# Properties are specified in the method comment
|
15
|
+
# <tt>
|
16
|
+
# ability_score_array = NerdDice.roll_ability_scores(
|
17
|
+
# ability_score_array_size: 7,
|
18
|
+
# ability_score_number_of_sides: 8,
|
19
|
+
# ability_score_dice_rolled: 5,
|
20
|
+
# ability_score_dice_kept: 4,
|
21
|
+
# randomization_technique: :randomized,
|
22
|
+
# foreground_color: "#FF0000",
|
23
|
+
# background_color: "#FFFFFF"
|
24
|
+
# )
|
25
|
+
# </tt>
|
26
|
+
module NerdDice
|
27
|
+
class << self
|
28
|
+
# Arguments:
|
29
|
+
# opts (options Hash, DEFAULT: {}) any options you wish to include
|
30
|
+
# ABILITY SCORE OPTIONS
|
31
|
+
# :ability_score_array_size DEFAULT NerdDice.configuration.ability_score_array_size
|
32
|
+
# :ability_score_number_of_sides DEFAULT NerdDice.configuration.ability_score_number_of_sides
|
33
|
+
# :ability_score_dice_rolled DEFAULT NerdDice.configuration.ability_score_dice_rolled
|
34
|
+
# :ability_score_dice_kept DEFAULT NerdDice.configuration.ability_score_dice_kept
|
35
|
+
#
|
36
|
+
# DICE SET OPTIONS
|
37
|
+
# :randomization_technique (Symbol) => must be one of the symbols in
|
38
|
+
# RANDOMIZATION_TECHNIQUES or nil
|
39
|
+
# :foreground_color (String) => should resolve to a valid CSS color (format flexible)
|
40
|
+
# :background_color (String) => should resolve to a valid CSS color (format flexible)
|
41
|
+
#
|
42
|
+
# Return (Array of NerdDice::DiceSet) => One NerdDice::DiceSet element for each ability score
|
43
|
+
# rubocop:disable Metrics/MethodLength
|
44
|
+
def roll_ability_scores(**opts)
|
45
|
+
dice_opts = opts.reject { |key, _value| key.to_s.match?(/\Aability_score_[a-z_]+\z/) }
|
46
|
+
ability_score_options = interpret_ability_score_options(opts)
|
47
|
+
ability_score_array = []
|
48
|
+
ability_score_options[:ability_score_array_size].times do
|
49
|
+
ability_score_array << roll_dice(
|
50
|
+
ability_score_options[:ability_score_number_of_sides],
|
51
|
+
ability_score_options[:ability_score_dice_rolled],
|
52
|
+
**dice_opts
|
53
|
+
).highest(
|
54
|
+
ability_score_options[:ability_score_dice_kept]
|
55
|
+
)
|
56
|
+
end
|
57
|
+
ability_score_array
|
58
|
+
end
|
59
|
+
# rubocop:enable Metrics/MethodLength
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def interpret_ability_score_options(opts)
|
64
|
+
return_hash = {}
|
65
|
+
ABILITY_SCORE_KEYS.each { |key| return_hash[key] = parse_ability_score_option(opts, key) }
|
66
|
+
return_hash
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_ability_score_option(option_hash, option_key)
|
70
|
+
option_hash[option_key] || configuration.send(option_key.to_s)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
############################
|
4
|
+
# roll_dice class method
|
5
|
+
############################
|
6
|
+
# Usage:
|
7
|
+
# If you wanted to roll a single d4, you would execute:
|
8
|
+
# <tt>dice_set = NerdDice.roll_dice(4)</tt>
|
9
|
+
#
|
10
|
+
# If you wanted to roll 3d6, you would execute
|
11
|
+
# <tt>dice_set = NerdDice.roll_dice(6, 3)</tt>
|
12
|
+
#
|
13
|
+
# If you wanted to roll a d20 and add 5 to the value, you would execute
|
14
|
+
# <tt>dice_set = NerdDice.roll_dice(20, 1, bonus: 5)</tt>
|
15
|
+
#
|
16
|
+
# Since this method returns a DiceSet, you can call any of the DiceSet
|
17
|
+
# methods on the result. See the README for more details and options
|
18
|
+
module NerdDice
|
19
|
+
class << self
|
20
|
+
############################
|
21
|
+
# roll_dice class method
|
22
|
+
############################
|
23
|
+
# Arguments:
|
24
|
+
# number_of_sides (Integer) => the number of sides of the dice to roll
|
25
|
+
# number_of_dice (Integer, DEFAULT: 1) => the quantity to roll of the type
|
26
|
+
# of die specified in the number_of_sides argument.
|
27
|
+
# opts (options Hash, DEFAULT: {}) any additional options you wish to include
|
28
|
+
# :bonus (Integer) => The total bonus (positive integer) or penalty
|
29
|
+
# (negative integer) to modify the total by. Is added to the total of
|
30
|
+
# all dice after they are totaled, not to each die rolled
|
31
|
+
# :randomization_technique (Symbol) => must be one of the symbols in
|
32
|
+
# RANDOMIZATION_TECHNIQUES or nil
|
33
|
+
# :foreground_color (String) => should resolve to a valid CSS color (format flexible)
|
34
|
+
# :background_color (String) => should resolve to a valid CSS color (format flexible)
|
35
|
+
# :damage_type: (String) => damage type for the dice set, if applicable
|
36
|
+
#
|
37
|
+
# Return (NerdDice::DiceSet) => Collection object with one or more Die objects
|
38
|
+
#
|
39
|
+
# You can call roll_dice().total to get similar functionality to total_dice
|
40
|
+
# or you can chain methods together roll_dice(6, 4, bonus: 3).with_advantage(3).total
|
41
|
+
def roll_dice(number_of_sides, number_of_dice = 1, **opts)
|
42
|
+
DiceSet.new(number_of_sides, number_of_dice, **opts)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
############################
|
4
|
+
# total_ability_scores method
|
5
|
+
############################
|
6
|
+
# Usage:
|
7
|
+
# If you wanted to get an array of only the values of ability scores and
|
8
|
+
# default configuration you would execute:
|
9
|
+
# <tt>ability_score_array = NerdDice.total_ability_scores</tt>
|
10
|
+
# => [15, 14, 13, 12, 10, 8]
|
11
|
+
#
|
12
|
+
# If you wanted to specify configuration for the current operation without
|
13
|
+
# modifying the NerdDice.configuration, you can supply options for both the
|
14
|
+
# ability_score configuration and the properties of the DiceSet objects returned.
|
15
|
+
#
|
16
|
+
# Many of the properties available in roll_ability_scores will not be relevant
|
17
|
+
# to total_ability_scores but the method delegates all options passed without
|
18
|
+
# filtering.
|
19
|
+
# <tt>
|
20
|
+
# ability_score_array = NerdDice.total_ability_scores(
|
21
|
+
# ability_score_array_size: 7,
|
22
|
+
# ability_score_number_of_sides: 8,
|
23
|
+
# ability_score_dice_rolled: 5,
|
24
|
+
# ability_score_dice_kept: 4,
|
25
|
+
# randomization_technique: :randomized
|
26
|
+
# )
|
27
|
+
# => [27, 22, 13, 23, 20, 24, 23]
|
28
|
+
# </tt>
|
29
|
+
module NerdDice
|
30
|
+
class << self
|
31
|
+
# Arguments:
|
32
|
+
# opts (options Hash, DEFAULT: {}) any options you wish to include
|
33
|
+
# ABILITY SCORE OPTIONS
|
34
|
+
# :ability_score_array_size DEFAULT NerdDice.configuration.ability_score_array_size
|
35
|
+
# :ability_score_number_of_sides DEFAULT NerdDice.configuration.ability_score_number_of_sides
|
36
|
+
# :ability_score_dice_rolled DEFAULT NerdDice.configuration.ability_score_dice_rolled
|
37
|
+
# :ability_score_dice_kept DEFAULT NerdDice.configuration.ability_score_dice_kept
|
38
|
+
#
|
39
|
+
# DICE SET OPTIONS
|
40
|
+
# :randomization_technique (Symbol) => must be one of the symbols in
|
41
|
+
# RANDOMIZATION_TECHNIQUES or nil
|
42
|
+
#
|
43
|
+
# ARGUMENTS PASSED ON THAT DO NOT REALLY MATTER
|
44
|
+
# :foreground_color (String) => should resolve to a valid CSS color (format flexible)
|
45
|
+
# :background_color (String) => should resolve to a valid CSS color (format flexible)
|
46
|
+
#
|
47
|
+
# Return (Array of Integers) => One Integer element for each ability score
|
48
|
+
def total_ability_scores(**opts)
|
49
|
+
harvest_totals(roll_ability_scores(**opts))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
############################
|
4
|
+
# total_dice class method
|
5
|
+
############################
|
6
|
+
# Usage:
|
7
|
+
# If you wanted to roll a single d4, you would execute:
|
8
|
+
# <tt>NerdDice.total_dice(4)</tt>
|
9
|
+
#
|
10
|
+
# If you wanted to roll 3d6, you would execute
|
11
|
+
# <tt>NerdDice.total_dice(6, 3)</tt>
|
12
|
+
#
|
13
|
+
# If you wanted to roll a d20 and add 5 to the value, you would execute
|
14
|
+
# <tt>NerdDice.total_dice(20, 1, bonus: 5)</tt>
|
15
|
+
#
|
16
|
+
# The bonus in the options hash must be an Integer duck type or nil
|
17
|
+
module NerdDice
|
18
|
+
class << self
|
19
|
+
# Arguments:
|
20
|
+
# number_of_sides (Integer) => the number of sides of the dice to roll
|
21
|
+
# number_of_dice (Integer, DEFAULT: 1) => the quantity to roll of the type
|
22
|
+
# of die specified in the number_of_sides argument.
|
23
|
+
# opts (options Hash, DEFAULT: {}) any additional options you wish to include
|
24
|
+
# :bonus (Integer) => The total bonus (positive integer) or penalty
|
25
|
+
# (negative integer) to modify the total by. Is added to the total of
|
26
|
+
# all dice after they are totaled, not to each die rolled
|
27
|
+
# :randomization_technique (Symbol) => must be one of the symbols in
|
28
|
+
# RANDOMIZATION_TECHNIQUES or nil
|
29
|
+
#
|
30
|
+
# Return (Integer) => Total of the dice rolled, plus modifier if applicable
|
31
|
+
def total_dice(number_of_sides, number_of_dice = 1, **opts)
|
32
|
+
total = 0
|
33
|
+
number_of_dice.times do
|
34
|
+
total += execute_die_roll(number_of_sides, opts[:randomization_technique])
|
35
|
+
end
|
36
|
+
begin
|
37
|
+
total += opts[:bonus].to_i
|
38
|
+
rescue NoMethodError
|
39
|
+
raise ArgumentError, "Bonus must be a value that responds to :to_i"
|
40
|
+
end
|
41
|
+
total
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nerd_dice/class_methods/configure"
|
4
|
+
require "nerd_dice/class_methods/refresh_seed"
|
5
|
+
require "nerd_dice/class_methods/execute_die_roll"
|
6
|
+
require "nerd_dice/class_methods/total_dice"
|
7
|
+
require "nerd_dice/class_methods/roll_dice"
|
8
|
+
require "nerd_dice/class_methods/roll_ability_scores"
|
9
|
+
require "nerd_dice/class_methods/harvest_totals"
|
10
|
+
require "nerd_dice/class_methods/total_ability_scores"
|
11
|
+
|
12
|
+
############################
|
13
|
+
# NerdDice class methods
|
14
|
+
# This file contains module-level attribute readers and writers and includes the other
|
15
|
+
# files that provide the class method functionality.
|
16
|
+
#
|
17
|
+
# PUBLIC METHODS
|
18
|
+
# NerdDice.configure => ./class_methods/configure.rb
|
19
|
+
# NerdDice.configuration => ./class_methods/configure.rb
|
20
|
+
# NerdDice.refresh_seed => ./class_methods/refresh_seed.rb
|
21
|
+
# NerdDice.execute_die_roll => ./class_methods/execute_die_roll.rb
|
22
|
+
# NerdDice.total_dice => ./class_methods/total_dice.rb
|
23
|
+
# NerdDice.roll_dice => ./class_methods/roll_dice.rb
|
24
|
+
# NerdDice.roll_ability_scores => ./class_methods/roll_ability_scores.rb
|
25
|
+
############################
|
26
|
+
module NerdDice
|
27
|
+
class << self
|
28
|
+
attr_reader :count_since_last_refresh
|
29
|
+
end
|
30
|
+
end
|
@@ -18,8 +18,12 @@ module NerdDice
|
|
18
18
|
# You can also set a particular property without a block using inline assignment
|
19
19
|
# <tt>NerdDice.configuration.randomization_technique = :random_new_once</tt>
|
20
20
|
class Configuration
|
21
|
-
attr_reader :randomization_technique, :refresh_seed_interval
|
22
|
-
|
21
|
+
attr_reader :randomization_technique, :refresh_seed_interval, :ability_score_array_size,
|
22
|
+
:ability_score_dice_rolled, :ability_score_number_of_sides, :ability_score_dice_kept
|
23
|
+
attr_accessor :die_background_color, :die_foreground_color
|
24
|
+
|
25
|
+
DEFAULT_BACKGROUND_COLOR = "#0000DD"
|
26
|
+
DEFAULT_FOREGROUND_COLOR = "#DDDDDD"
|
23
27
|
|
24
28
|
def randomization_technique=(value)
|
25
29
|
unless RANDOMIZATION_TECHNIQUES.include?(value)
|
@@ -37,11 +41,51 @@ module NerdDice
|
|
37
41
|
@refresh_seed_interval = value
|
38
42
|
end
|
39
43
|
|
44
|
+
def ability_score_array_size=(value)
|
45
|
+
@ability_score_array_size = ensure_positive_integer!("ability_score_array_size", value)
|
46
|
+
end
|
47
|
+
|
48
|
+
def ability_score_number_of_sides=(value)
|
49
|
+
@ability_score_number_of_sides = ensure_positive_integer!("ability_score_number_of_sides", value)
|
50
|
+
end
|
51
|
+
|
52
|
+
def ability_score_dice_rolled=(value)
|
53
|
+
@ability_score_dice_rolled = ensure_positive_integer!("ability_score_dice_rolled", value)
|
54
|
+
return unless ability_score_dice_kept > @ability_score_dice_rolled
|
55
|
+
|
56
|
+
warn "WARNING: ability_score_dice_rolled set to lower value than ability_score_dice_kept. " \
|
57
|
+
"Reducing ability_score_dice_kept from #{ability_score_dice_kept} to #{@ability_score_dice_rolled}"
|
58
|
+
self.ability_score_dice_kept = @ability_score_dice_rolled
|
59
|
+
end
|
60
|
+
|
61
|
+
def ability_score_dice_kept=(value)
|
62
|
+
compare_error_message = "cannot set ability_score_dice_kept greater than ability_score_dice_rolled"
|
63
|
+
new_value = ensure_positive_integer!("ability_score_dice_kept", value)
|
64
|
+
raise NerdDice::Error, compare_error_message if new_value > @ability_score_dice_rolled
|
65
|
+
|
66
|
+
@ability_score_dice_kept = new_value
|
67
|
+
end
|
68
|
+
|
40
69
|
private
|
41
70
|
|
42
71
|
def initialize
|
43
72
|
@ability_score_array_size = 6
|
73
|
+
@ability_score_number_of_sides = 6
|
74
|
+
@ability_score_dice_rolled = 4
|
75
|
+
@ability_score_dice_kept = 3
|
44
76
|
@randomization_technique = :random_object
|
77
|
+
@die_background_color = DEFAULT_BACKGROUND_COLOR
|
78
|
+
@die_foreground_color = DEFAULT_FOREGROUND_COLOR
|
79
|
+
end
|
80
|
+
|
81
|
+
def ensure_positive_integer!(attribute_name, argument_value)
|
82
|
+
error_message = "#{attribute_name} must be must be a positive value that responds to :to_i"
|
83
|
+
new_value = argument_value.to_i
|
84
|
+
raise ArgumentError, error_message unless new_value.positive?
|
85
|
+
|
86
|
+
new_value
|
87
|
+
rescue NoMethodError
|
88
|
+
raise ArgumentError, error_message
|
45
89
|
end
|
46
90
|
end
|
47
91
|
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NerdDice
|
4
|
+
# The NerdDice::DiceSet class allows you to instantiate and roll a set of dice by specifying
|
5
|
+
# the number of sides, the number of dice (with a default of 1) and other options. As part of
|
6
|
+
# initialization the DiceSet will initialize and roll the Die objects specified by the
|
7
|
+
# DiceSet.new method. The parameters align with the NerdDice.total_dice method and
|
8
|
+
# NerdDice::DiceSet.new(6, 3, bonus: 5).total would be equivalent to
|
9
|
+
# NerdDice.total_dice(6, 3, bonus: 5)
|
10
|
+
#
|
11
|
+
# Usage:
|
12
|
+
# Instantiate a d20:
|
13
|
+
# <tt>dice = NerdDice::DiceSet.new(20)
|
14
|
+
# dice.total # between 1 and 20
|
15
|
+
# </tt>
|
16
|
+
#
|
17
|
+
# Instantiate 3d6 + 5:
|
18
|
+
# <tt>dice = NerdDice::DiceSet.new(6, 3, bonus: 5)
|
19
|
+
# dice.total # between 8 and 23
|
20
|
+
# </tt>
|
21
|
+
#
|
22
|
+
# You can also specify options that will cascade to the member dice when instantiating
|
23
|
+
# <tt>NerdDice::DiceSet.new(6, 3, randomization_technique: :randomized,
|
24
|
+
# foreground_color: "#FF0000",
|
25
|
+
# background_color: "#FFFFFF"
|
26
|
+
# damage_type: "necrotic"))
|
27
|
+
# </tt>
|
28
|
+
class DiceSet
|
29
|
+
include Enumerable
|
30
|
+
include SetsRandomizationTechnique
|
31
|
+
|
32
|
+
attr_reader :number_of_sides, :number_of_dice, :dice, :bonus
|
33
|
+
attr_accessor :background_color, :foreground_color, :damage_type
|
34
|
+
|
35
|
+
# required to implement Enumerable uses the @dice collection
|
36
|
+
def each(&block)
|
37
|
+
@dice.each(&block)
|
38
|
+
end
|
39
|
+
|
40
|
+
# not included by default in Enumerable: allows [] directly on the DiceSet object
|
41
|
+
def [](index)
|
42
|
+
@dice[index]
|
43
|
+
end
|
44
|
+
|
45
|
+
# not included by default in Enumerable: adds length property directly to the DiceSet object
|
46
|
+
def length
|
47
|
+
@dice.length
|
48
|
+
end
|
49
|
+
|
50
|
+
# sorts the @dice collection in place
|
51
|
+
def sort!
|
52
|
+
@dice.sort!
|
53
|
+
end
|
54
|
+
|
55
|
+
# reverses the @dice collection in place
|
56
|
+
def reverse!
|
57
|
+
@dice.reverse!
|
58
|
+
end
|
59
|
+
|
60
|
+
# re-rolls each Die in the collection and sets its is_included_in_total property back to true
|
61
|
+
def reroll_all!
|
62
|
+
@dice.map do |die|
|
63
|
+
die.roll
|
64
|
+
die.is_included_in_total = true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# resets is_included_in_total property back to true for each Die in the collection
|
69
|
+
def include_all_dice!
|
70
|
+
@dice.map { |die| die.is_included_in_total = true }
|
71
|
+
end
|
72
|
+
|
73
|
+
###################################
|
74
|
+
# highest instance method
|
75
|
+
# (aliased as with_advantage)
|
76
|
+
###################################
|
77
|
+
# Arguments:
|
78
|
+
# number_to_take (Integer default: nil) => the number dice to take
|
79
|
+
#
|
80
|
+
# Notes:
|
81
|
+
# * If the method is called with a nil value it will take all but one of the dice
|
82
|
+
# * If the method is called on a DiceSet with one Die, the lone Die will remain included
|
83
|
+
# * The method will raise an ArgumentError if you try to take more dice than the DiceSet contains
|
84
|
+
# * Even though this method doesn't have a bang at the end, it does call other bang methods
|
85
|
+
#
|
86
|
+
# Return (NerdDice::DiceSet) => Returns the instance the method was called on with
|
87
|
+
# die objects that have is_included_in_total property modified as true for the highest(n)
|
88
|
+
# dice and false for the remaining dice.
|
89
|
+
def highest(number_to_take = nil)
|
90
|
+
include_all_dice!
|
91
|
+
number_to_take = check_low_high_argument!(number_to_take)
|
92
|
+
get_default_to_take if number_to_take.nil?
|
93
|
+
@dice.sort.reverse.each_with_index do |die, index|
|
94
|
+
die.is_included_in_total = false if index >= number_to_take
|
95
|
+
end
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
alias with_advantage highest
|
100
|
+
|
101
|
+
###################################
|
102
|
+
# lowest instance method
|
103
|
+
# (aliased as with_disadvantage)
|
104
|
+
###################################
|
105
|
+
# Arguments and Notes are the same as for the highest method documented above
|
106
|
+
#
|
107
|
+
# Return (NerdDice::DiceSet) => Returns the instance the method was called on with
|
108
|
+
# die objects that have is_included_in_total property modified as true for the lowest(n)
|
109
|
+
# dice and false for the remaining dice.
|
110
|
+
def lowest(number_to_take = nil)
|
111
|
+
include_all_dice!
|
112
|
+
number_to_take = check_low_high_argument!(number_to_take)
|
113
|
+
get_default_to_take if number_to_take.nil?
|
114
|
+
@dice.sort.each_with_index do |die, index|
|
115
|
+
die.is_included_in_total = false if index >= number_to_take
|
116
|
+
end
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
alias with_disadvantage lowest
|
121
|
+
|
122
|
+
# custom attribute writer that ensures the argument is an Integer duck-type and calls to_i
|
123
|
+
def bonus=(new_value)
|
124
|
+
@bonus = new_value.to_i
|
125
|
+
rescue NoMethodError
|
126
|
+
raise ArgumentError, "Bonus must be a value that responds to :to_i"
|
127
|
+
end
|
128
|
+
|
129
|
+
###################################
|
130
|
+
# total method
|
131
|
+
###################################
|
132
|
+
# Return (Integer) => Returns the sum of the values on the Die objects in the collection
|
133
|
+
# where is_included_in_total is set to true and then adds the value of the bonus
|
134
|
+
# attribute (which may be negative)
|
135
|
+
def total
|
136
|
+
@dice.select(&:included_in_total?).sum(&:value) + @bonus
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def initialize(number_of_sides, number_of_dice = 1, **opts)
|
142
|
+
@number_of_sides = number_of_sides
|
143
|
+
@number_of_dice = number_of_dice
|
144
|
+
parse_options(opts)
|
145
|
+
@dice = []
|
146
|
+
@number_of_dice.times { @dice << Die.new(@number_of_sides, **opts) }
|
147
|
+
end
|
148
|
+
|
149
|
+
def parse_options(opts)
|
150
|
+
self.randomization_technique = opts[:randomization_technique]
|
151
|
+
@background_color = opts[:background_color] || NerdDice.configuration.die_background_color
|
152
|
+
@foreground_color = opts[:foreground_color] || NerdDice.configuration.die_foreground_color
|
153
|
+
@damage_type = opts[:damage_type]
|
154
|
+
self.bonus = opts[:bonus]
|
155
|
+
end
|
156
|
+
|
157
|
+
# validates the argument input to the highest and lowest methods
|
158
|
+
# sets a default value if number_to_take is nil
|
159
|
+
def check_low_high_argument!(number_to_take)
|
160
|
+
number_to_take ||= number_of_dice == 1 ? 1 : number_of_dice - 1
|
161
|
+
raise ArgumentError, "Argument cannot exceed number of dice" if number_to_take > number_of_dice
|
162
|
+
|
163
|
+
number_to_take
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NerdDice
|
4
|
+
# The NerdDice::Die class allows you to instantiate and roll a die by specifying
|
5
|
+
# the number of sides with other options. As part of initialization the die will
|
6
|
+
# be rolled and have a value
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# Instantiate a d20:
|
10
|
+
# <tt>die = NerdDice::Die.new(20)
|
11
|
+
# die.value # between 1 and 20
|
12
|
+
# </tt>
|
13
|
+
#
|
14
|
+
# You can also specify options when instantiating
|
15
|
+
# <tt>NerdDice::Die.new(12, randomization_technique: :randomized,
|
16
|
+
# foreground_color: "#FF0000",
|
17
|
+
# background_color: "#FFFFFF",
|
18
|
+
# damage_type: "necrotic"))
|
19
|
+
# </tt>
|
20
|
+
class Die
|
21
|
+
include Comparable
|
22
|
+
include SetsRandomizationTechnique
|
23
|
+
|
24
|
+
attr_reader :number_of_sides, :value
|
25
|
+
attr_accessor :background_color, :foreground_color, :damage_type, :is_included_in_total
|
26
|
+
|
27
|
+
# comparison operator override using value: required to implement Comparable
|
28
|
+
def <=>(other)
|
29
|
+
value <=> other.value
|
30
|
+
end
|
31
|
+
|
32
|
+
# rolls the die, setting the value to the new roll and returning that value
|
33
|
+
def roll
|
34
|
+
@value = NerdDice.execute_die_roll(@number_of_sides, @randomization_technique)
|
35
|
+
end
|
36
|
+
|
37
|
+
alias included_in_total? is_included_in_total
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def initialize(number_of_sides, **opts)
|
42
|
+
@number_of_sides = number_of_sides
|
43
|
+
self.randomization_technique = opts[:randomization_technique]
|
44
|
+
@background_color = opts[:background_color] || NerdDice.configuration.die_background_color
|
45
|
+
@foreground_color = opts[:foreground_color] || NerdDice.configuration.die_foreground_color
|
46
|
+
@damage_type = opts[:damage_type]
|
47
|
+
@is_included_in_total = true
|
48
|
+
roll
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NerdDice
|
4
|
+
# The NerdDice::SetsRandomizationTechnique is a module mixin that can be included
|
5
|
+
# in classes. It provides an attribute reader and writer for randomization_technique
|
6
|
+
# and checks against the NerdDice::RANDOMIZATION_TECHNIQUES constant to make sure the
|
7
|
+
# input provided is valid
|
8
|
+
module SetsRandomizationTechnique
|
9
|
+
attr_reader :randomization_technique
|
10
|
+
|
11
|
+
def randomization_technique=(new_value)
|
12
|
+
unless RANDOMIZATION_TECHNIQUES.include?(new_value) || new_value.nil?
|
13
|
+
raise NerdDice::Error, "randomization_technique must be one of #{NerdDice::RANDOMIZATION_TECHNIQUES.join(', ')}"
|
14
|
+
end
|
15
|
+
|
16
|
+
@randomization_technique = new_value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/nerd_dice/version.rb
CHANGED