nerd_dice 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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