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.
@@ -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
- attr_accessor :ability_score_array_size
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NerdDice
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.0"
5
5
  end