nerd_dice 0.2.1 → 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.
data/README.md CHANGED
@@ -4,6 +4,9 @@
4
4
  # NerdDice
5
5
  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.
6
6
 
7
+ ## Educational Videos By Stateless Code
8
+ The end-to-end process of developing this gem has been captured as [instructional videos](https://www.youtube.com/playlist?list=PL9kkbu1kLUeOnUtMpAnJOCtHdThx1Efkt). The videos are in a one-take style so that the mistakes along the way have troubleshooting and the concepts used to develop the gem are explained as they are covered.
9
+
7
10
  ## Installation
8
11
 
9
12
  Add this line to your application's Gemfile:
@@ -28,7 +31,17 @@ You can customize the behavior of NerdDice via a configuration block as below or
28
31
  NerdDice.configure do | config|
29
32
 
30
33
  # number of ability scores to place in an ability score array
31
- config.ability_score_array_size = 6
34
+ config.ability_score_array_size = 6 # must duck-type to positive Integer
35
+
36
+ # number of sides for each ability score Die
37
+ config.ability_score_number_of_sides = 6 # must duck-type to positive Integer
38
+
39
+ # total number of dice rolled for each ability score
40
+ config.ability_score_dice_rolled = 4 # must duck-type to positive Integer
41
+
42
+ # highest(n) dice from the total number of dice rolled that are included in the ability score total
43
+ # CANNOT EXCEED ability_score_dice_rolled see Note below
44
+ config.ability_score_dice_kept = 3 # must duck-type to positive Integer
32
45
 
33
46
  # randomization technique options are:
34
47
  # :securerandom => Uses SecureRandom.rand(). Good entropy, medium speed.
@@ -43,26 +56,167 @@ NerdDice.configure do | config|
43
56
  # 1 very slow and heavy pressure on processor and memory but very high entropy
44
57
  # 1000 would refresh the object every 1000 times you call rand()
45
58
  config.refresh_seed_interval = nil # don't refresh the seed
59
+ # Background and foreground die colors are string values. By default these correspond to the constants in the class
60
+ # Defaults: DEFAULT_BACKGROUND_COLOR = "#0000DD" DEFAULT_FOREGROUND_COLOR = "#DDDDDD"
61
+ # It is recommended but not enforced that these should be valid CSS color property attributes
62
+ config.die_background_color = "red"
63
+ config.die_foreground_color = "#000"
46
64
  end
47
65
  ```
66
+ **Note:** You cannot set `ability_score_dice_kept` greater than `ability_score_dice_rolled`. If you try to set `ability_score_dice_kept` higher than `ability_score_dice_rolled`, an error will be raised. If you set `ability_score_dice_rolled` _lower_ than the existing value of `ability_score_dice_kept`, no error will be thrown, but `ability_score_dice_kept` will be _**modified**_ to match `ability_score_dice_rolled` and a warning will be printed.
48
67
 
49
68
  ### Rolling a number of dice and adding a bonus
69
+ You can use two different methods to roll dice. The `total_dice` method returns an `Integer` representing the total of the dice plus any applicable bonuses. The `total_dice` method does not support chaining additional methods like `highest`, `lowest`, `with_advantage`, `with_disadvantage`. The `roll_dice` method returns a `DiceSet` collection object, and allows for chaining the methods mentioned above and iterating over the individual `Die` objects. `NerdDice.roll_dice.total` and `NerdDice.total_dice` are roughly equivalent.
70
+
50
71
  ```ruby
51
72
  # roll a single d4
52
73
  NerdDice.total_dice(4) # => return random Integer between 1-4
74
+ NerdDice.roll_dice(4) # => return a DiceSet with one 4-sided Die with a value between 1-4
75
+ NerdDice.roll_dice(4).total # => return random Integer between 1-4
53
76
 
54
77
  # roll 3d6
55
78
  NerdDice.total_dice(6, 3) # => return Integer total of three 6-sided dice
79
+ NerdDice.roll_dice(6, 3) # => return a DiceSet with three 6-sided Die objects, each with values between 1-6
80
+ NerdDice.roll_dice(6, 3).total # => return Integer total of three 6-sided dice
56
81
 
57
82
  # roll a d20 and add 5 to the value
58
- NerdDice.total_dice(20, bonus: 5)
83
+ NerdDice.total_dice(20, bonus: 5) # rolls a d20 and adds the bonus to the total => Integer
84
+ NerdDice.roll_dice(20, bonus: 5) # return a DiceSet with one 20-sided Die with a value between 1-20 and a bonus attribute of 5
85
+ NerdDice.roll_dice(20, bonus: 5).total # rolls a d20 and adds the bonus to the total => Integer
59
86
 
87
+ # without changing the config at the module level
60
88
  # roll a d20 and overide the configured randomization_technique one time
61
- # without changing the config
62
- NerdDice.total_dice(20, randomization_technique: :randomized)
89
+ NerdDice.total_dice(20, randomization_technique: :randomized) # => Integer
90
+ # roll a d20 and overide the configured randomization_technique for the DiceSet object will persist on the DiceSet object for subsequent rerolls
91
+ NerdDice.roll_dice(20, randomization_technique: :randomized) # => DiceSet with randomization_technique: :randomized
63
92
  ```
64
93
  __NOTE:__ If provided, the bonus must respond to `:to_i` or an `ArgumentError` will be raised
65
94
 
95
+ ### Taking actions on the dice as objects using the DiceSet object
96
+ The `NerdDice.roll_dice` method or the `NerdDice::DiceSet.new` methods return a collection object with an array of one or more `Die` objects. There are properties on both the `DiceSet` object and the `Die` object. Applicable properties are cascaded from the `DiceSet` to the `Die` objects in the collection by default.
97
+
98
+ ```ruby
99
+ # These are equivalent
100
+ dice_set = NerdDice.roll_dice(6, 3, bonus: 2, randomization_technique: :randomized, damage_type: 'psychic', foreground_color: '#FFF', background_color: '#0FF')
101
+ # => NerdDice::DiceSet
102
+ dice_set = NerdDice::DiceSet.new(6, 3, bonus: 2, randomization_technique: :randomized, damage_type: 'psychic', foreground_color: '#FFF', background_color: '#0FF')
103
+ # => NerdDice::DiceSet
104
+ ```
105
+ #### Available options for NerdDice::DiceSet objects
106
+ There are a number of options that can be provided when initializing a `NerdDice::DiceSet` object after specifying the mandatory number of sides and the optional number of dice \(default: 1\). The list below provides the options and indicates whether they are cascaded to the Die objects in the collection.
107
+ * `bonus` \(Duck-type Integer, _default: 0_\): Bonus or penalty to apply to the total after all dice are rolled. _**Not applied** to Die objects_
108
+ * `randomization_technique` \(Symbol, _default: nil_\): Randomization technique override to use for the `DiceSet`. If `nil` it will use the value in `NerdDice.configuration`. _**Applied** to Die objects by default with ability modify_
109
+ * `damage_type` \(String, _default: nil_\): Optional string indicating the damage type associated with the dice for systems where it is relevant. _**Applied** to Die objects by default with ability modify_
110
+ * `foreground_color` \(String, _default: `NerdDice.configuration.die_foreground_color`_\): Intended foreground color to apply to the dice in the `DiceSet`. Should be a valid CSS color but is not validated or enforced and doesn\'t currently have any real functionality associated with it. _**Applied** to Die objects by default with ability modify_
111
+ * `background_color` \(String, _default: `NerdDice.configuration.die_background_color`_\): Intended background color to apply to the dice in the `DiceSet`. Should be a valid CSS color but is not validated or enforced and doesn\'t currently have any real functionality associated with it. _**Applied** to Die objects by default with ability modify_
112
+
113
+ #### Properties of individual Die objects
114
+ When initialized from a `DiceSet` object most of the properties of the `Die` object are inherited from the `DiceSet` object. In addition, there is an `is_included_in_total` public attribute that can be set to indicate whether the value of that particular die should be included in the total for its parent `DiceSet`. This property always starts out as true when the `Die` is initialized, but can be set to false.
115
+
116
+ ```ruby
117
+ # six sided die
118
+ die = NerdDice::Die.new(6, randomization_technique: :randomized, damage_type: 'psychic', foreground_color: '#FFF', background_color: '#0FF')
119
+ die.is_included_in_total # => true
120
+ die.included_in_total? # => true
121
+ die.is_included_in_total = false
122
+ die.included_in_total? # => false
123
+
124
+ # value property
125
+ die.value # => Integer between 1 and number_of_sides
126
+ die.roll # => Integer. Rolls/rerolls the Die and sets value to the result of the roll. Returns the new value
127
+ ```
128
+ #### Iterating through dices in a DiceSet
129
+ The `DiceSet` class mixes in the `Enumerable` module and the `Die` object mixes in the `Comparable` module. This allows you to iterate over the dice in the collection. The `sort` method on the dice will return the die objects in ascending value from lowest to highest.
130
+
131
+ ```ruby
132
+ dice_set = NerdDice.roll_dice(6, 3) # => NerdDice::DiceSet
133
+ dice_set.dice => Array of Die objects
134
+ dice_set.length # => 3. (dice_set.dice.length)
135
+ dice_set[0] # => NerdDice::Die (first element of dice array)
136
+ # take actions on each die
137
+ dice_set.each do |die|
138
+ # print the current value
139
+ puts "Die value before reroll is #{die.value}"
140
+ # set the foreground_color of the die
141
+ die.foreground_color = ["gray", "#FF0000#", "#d9d9d9", "green"].shuffle.first
142
+ # reroll the die
143
+ die.roll
144
+ # print the new value
145
+ puts "Die value after reroll is #{die.value}"
146
+ # do other things
147
+ end
148
+ ```
149
+ #### Methods and method chaining on the DiceSet
150
+ Since the DiceSet is an object, you can call methods that operate on the result returned and allow for things like the 5e advantage/disadvantage mechanic, the ability to re-roll all of the dice in the `DiceSet`, or to mark them all as included in the total.
151
+
152
+ ```ruby
153
+ ##############################################
154
+ # highest/with_advantage and lowest/with_disadvantage methods
155
+ # assuming 4d6 with values of [1, 3, 4, 6]
156
+ ##############################################
157
+ dice_set = NerdDice.roll_dice(6, 4)
158
+ # the 6, 4, and 3 will have is_included_in_total true while the 1 has it false
159
+ dice_set.highest(3) # => Returns the existing DiceSet object with the changes made to dice inclusion
160
+ dice_set.with_advantage(3) # => Alias of highest method
161
+ # calling total after highest/with_advantage for this DiceSet
162
+ dice_set.total # => 13
163
+ # same DiceSet using lowest. The 1, 3, and 4 will have is_included_in_total true while the 6 has it false
164
+ dice_set.lowest(3) # => Returns the existing DiceSet object with the changes made to dice inclusion
165
+ dice_set.with_disadvantage(3) # => Alias of lowest method
166
+ # calling total after lowest/with_disadvantage for this DiceSet
167
+ dice_set.total # => 8
168
+ # you can chain these methods (assumes the same seed as the above examples)
169
+ NerdDice.roll_dice(6, 4).with_advantage(3).total # => 13
170
+ NerdDice.roll_dice(6, 4).lowest(3).total # => 8
171
+
172
+ # reroll_all! method
173
+ dice_set = NerdDice.roll_dice(6, 4)
174
+ dice_set.reroll_all! # rerolls each of the Die objects in the collection and re-includes them in the total
175
+
176
+ # include_all_dice! method
177
+ dice_set.include_all_dice! # resets is_included_in_total to true for all Die objects
178
+ ```
179
+
180
+ ### Rolling Ability Scores
181
+ You can call `roll_ability_scores` or `total_ability_scores` to get back an array of `DiceSet` objects or `Integer` objects, respectively. The `total_ability_scores` method calls `total` on each `DiceSet` and returns those numbers with one value per ability score. The `Configuration` object defaults to 6 ability scores using a methodology of __4d6 drop the lowest__ by default.
182
+
183
+ ```ruby
184
+ # return an array of DiceSet objects including info about the discarded dice
185
+ #
186
+ NerdDice.roll_ability_scores
187
+ #=> [DiceSet0, DiceSet1, ...]
188
+ # => DiceSet0 hash representation { total: 12, dice: [
189
+ # {value: 2, is_included_in_total: true},
190
+ # {value: 6, is_included_in_total: true},
191
+ # {value: 4, is_included_in_total: true},
192
+ # {value: 1, is_included_in_total: false}
193
+ # ]}
194
+ # if you want to get back DiceSet objects that you can interact with
195
+
196
+ # just return an array of totaled ability scores
197
+ NerdDice.total_ability_scores
198
+ #=> [12, 14, 13, 15, 10, 8]
199
+ ```
200
+
201
+ Both methods can be called without arguments to use the values specified in `NerdDice.configuration` or passed a set of options.
202
+ ```ruby
203
+
204
+ # total_dice and roll_dice take the same set of options
205
+ NerdDice.roll_ability_scores(
206
+ ability_score_array_size: 7,
207
+ ability_score_number_of_sides: 8,
208
+ ability_score_dice_rolled: 5,
209
+ ability_score_dice_kept: 4,
210
+ randomization_technique: :randomized,
211
+ foreground_color: "#FF0000",
212
+ background_color: "#FFFFFF"
213
+ )
214
+ # => [DiceSet0, DiceSet1, ...] with 7 ability scores that each roll 5d8 dropping the lowest
215
+ # or if called with total_ability_scores
216
+ # => [27, 17, 21, 17, 23, 13, 27]
217
+ ```
218
+ **Note:** If you try to call this method with `ability_score_dice_kept` greater than `ability_score_dice_rolled` an error will be raised.
219
+
66
220
  ### Manually setting or refreshing the random generator seed
67
221
  For randomization techniques other than `:securerandom` you can manually set or refresh the generator's seed by calling the `refresh_seed!` method. This is automatically called at the interval specified in `NerdDice.configuration.refresh_seed_interval` if it is not nil.
68
222
 
@@ -80,6 +234,23 @@ NerdDice.refresh_seed!(randomization_technique: :randomized,
80
234
  ```
81
235
  __NOTE:__ Ability to specify a seed it primarily provided for testing purposes. This makes all random numbers generated _transparently deterministic_ and should not be used if you want behavior approximating randomness.
82
236
 
237
+ ### Utility Methods
238
+
239
+ #### Harvesting Totals from DiceSets
240
+ The `harvest_totals` method take any collection of objects where each element responds to `total` and return an array of the results of the total method.
241
+ ```ruby
242
+ ability_score_array = NerdDice.roll_ability_scores
243
+ # => Array of 6 DiceSet objects
244
+
245
+ # Arguments:
246
+ # collection (Enumerable) a collection where each element responds to total
247
+ #
248
+ # Return (Array) => Data type of each element will be whatever is returned by total method
249
+ totals_array = NerdDice.harvest_totals(totals_array)
250
+ # => [15, 14, 13, 12, 10, 8]
251
+ # yes, it just happened to be the standard array by amazing coincidence
252
+ ```
253
+
83
254
  ## Development
84
255
 
85
256
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -88,8 +259,9 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
88
259
 
89
260
  ## Contributing
90
261
 
91
- Bug reports and pull requests are welcome on GitHub at https://github.com/statelesscode/nerd_dice/issues. We welcome and encourage your participation in this open-source project. We welcome those of all backgrounds and abilities, but we refuse to adopt the Contributor Covenant for reasons outlined in [BURN_THE_CONTRIBUTOR_COVENANT_WITH_FIRE.md](BURN_THE_CONTRIBUTOR_COVENANT_WITH_FIRE.md)
262
+ Bug reports and pull requests are welcome on GitHub at https://github.com/statelesscode/nerd_dice/issues. We welcome and encourage your participation in this open-source project. We welcome those of all backgrounds and abilities, but we refuse to adopt the Contributor Covenant for reasons outlined in [BURN_THE_CONTRIBUTOR_COVENANT_WITH_FIRE.md](https://github.com/statelesscode/nerd_dice/blob/master/BURN_THE_CONTRIBUTOR_COVENANT_WITH_FIRE.md)
263
+
92
264
 
93
265
  ## Unlicense, License, and Copyright
94
266
 
95
- The document is dual-licensed under the [MIT](https://opensource.org/licenses/MIT) license and the [UNLICENSE](https://unlicense.org/) \(with strong preference toward the UNLICENSE\)\. The content is released under [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/) \(no rights reserved\). You are free to include it in its original form or modified with or without modification in your own project\.
267
+ The document is dual-licensed under the [MIT](https://opensource.org/licenses/MIT) license and the [UNLICENSE](https://unlicense.org/) \(with strong preference toward the UNLICENSE\)\. The content is released under [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/) \(no rights reserved\). You are free to include it in its original form or modified with or without additional modification in your own project\.
@@ -7,7 +7,7 @@ version = ARGV[0]
7
7
  built_gem_path = "pkg/nerd_dice-#{version}.gem"
8
8
  checksum = Digest::SHA512.new.hexdigest(File.read(built_gem_path))
9
9
  checksum_path = "checksum/nerd_dice-#{version}.gem.sha512"
10
- File.write(checksum_path, checksum)
10
+ File.open(checksum_path, "w") { |f| f.write(checksum) }
11
11
  sha256 = Digest::SHA256.new.hexdigest(File.read(built_gem_path))
12
12
  checksum_256_path = "checksum/nerd_dice-#{version}.gem.sha256"
13
- File.write(checksum_256_path, sha256)
13
+ File.open(checksum_256_path, "w") { |f| f.write(sha256) }
@@ -8,14 +8,24 @@ require "nerd_dice"
8
8
  n = 50_000
9
9
 
10
10
  RATIOS = {
11
- nerd_dice_securerandom: 1.7,
12
- nerd_dice_random_rand: 11.1,
13
- nerd_dice_random_object: 13.0,
14
- nerd_dice_randomized: 5.5,
15
- nerd_dice_securerandom_3d6: 5.5,
16
- nerd_dice_random_rand_3d6: 25.0,
17
- nerd_dice_random_object_3d6: 25.5,
18
- nerd_dice_randomized_3d6: 14.5
11
+ total_dice_securerandom: 2.1,
12
+ total_dice_random_rand: 11.1,
13
+ total_dice_random_object: 13.0,
14
+ total_dice_randomized: 5.5,
15
+ total_dice_securerandom_3d6: 5.5,
16
+ total_dice_random_rand_3d6: 30.0,
17
+ total_dice_random_object_3d6: 25.5,
18
+ total_dice_randomized_3d6: 15.5,
19
+ roll_dice_securerandom: 4.0,
20
+ roll_dice_random_rand: 42.0,
21
+ roll_dice_random_object: 44.0,
22
+ roll_dice_randomized: 14.5,
23
+ roll_dice_securerandom_3d6: 13.0,
24
+ roll_dice_random_rand_3d6: 79.0,
25
+ roll_dice_random_object_3d6: 86.0,
26
+ roll_dice_randomized_3d6: 26.5,
27
+ roll_ability_scores_randomized: 26.5,
28
+ total_ability_scores_randomized: 26.5
19
29
  }.freeze
20
30
 
21
31
  def check_against_baseline!(baseline_value, test_value)
@@ -44,22 +54,22 @@ securerandom_baseline = baselines[1].real
44
54
  puts "Roll d1000s"
45
55
  total_dice_d1000_results = Benchmark.bmbm do |x|
46
56
  # NerdDice.total_dice securerandom
47
- x.report("nerd_dice_securerandom") do
57
+ x.report("total_dice_securerandom") do
48
58
  NerdDice.configuration.randomization_technique = :securerandom
49
59
  n.times { NerdDice.total_dice(1000) }
50
60
  end
51
61
 
52
- x.report("nerd_dice_random_rand") do
62
+ x.report("total_dice_random_rand") do
53
63
  NerdDice.configuration.randomization_technique = :random_rand
54
64
  n.times { NerdDice.total_dice(1000) }
55
65
  end
56
66
 
57
- x.report("nerd_dice_random_object") do
67
+ x.report("total_dice_random_object") do
58
68
  NerdDice.configuration.randomization_technique = :random_object
59
69
  n.times { NerdDice.total_dice(1000) }
60
70
  end
61
71
 
62
- x.report("nerd_dice_randomized") do
72
+ x.report("total_dice_randomized") do
63
73
  NerdDice.configuration.randomization_technique = :randomized
64
74
  n.times { NerdDice.total_dice(1000) }
65
75
  end
@@ -74,25 +84,57 @@ check_against_baseline! random_rand_baseline, total_dice_random_object
74
84
  total_dice_randomized = total_dice_d1000_results[3]
75
85
  check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), total_dice_randomized
76
86
 
87
+ roll_dice_d1000_results = Benchmark.bmbm do |x|
88
+ # NerdDice.roll_dice securerandom
89
+ x.report("roll_dice_securerandom") do
90
+ NerdDice.configuration.randomization_technique = :securerandom
91
+ n.times { NerdDice.roll_dice(1000) }
92
+ end
93
+
94
+ x.report("roll_dice_random_rand") do
95
+ NerdDice.configuration.randomization_technique = :random_rand
96
+ n.times { NerdDice.roll_dice(1000) }
97
+ end
98
+
99
+ x.report("roll_dice_random_object") do
100
+ NerdDice.configuration.randomization_technique = :random_object
101
+ n.times { NerdDice.roll_dice(1000) }
102
+ end
103
+
104
+ x.report("roll_dice_randomized") do
105
+ NerdDice.configuration.randomization_technique = :randomized
106
+ n.times { NerdDice.roll_dice(1000) }
107
+ end
108
+ end
109
+
110
+ roll_dice_securerandom = roll_dice_d1000_results[0]
111
+ check_against_baseline! securerandom_baseline, roll_dice_securerandom
112
+ roll_dice_random_rand = roll_dice_d1000_results[1]
113
+ check_against_baseline! random_rand_baseline, roll_dice_random_rand
114
+ roll_dice_random_object = roll_dice_d1000_results[2]
115
+ check_against_baseline! random_rand_baseline, roll_dice_random_object
116
+ roll_dice_randomized = roll_dice_d1000_results[3]
117
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), roll_dice_randomized
118
+
77
119
  puts "Roll 3d6"
78
120
  total_dice_3d6_results = Benchmark.bmbm do |x|
79
121
  # NerdDice.total_dice securerandom
80
- x.report("nerd_dice_securerandom_3d6") do
122
+ x.report("total_dice_securerandom_3d6") do
81
123
  NerdDice.configuration.randomization_technique = :securerandom
82
124
  n.times { NerdDice.total_dice(6, 3) }
83
125
  end
84
126
 
85
- x.report("nerd_dice_random_rand_3d6") do
127
+ x.report("total_dice_random_rand_3d6") do
86
128
  NerdDice.configuration.randomization_technique = :random_rand
87
129
  n.times { NerdDice.total_dice(6, 3) }
88
130
  end
89
131
 
90
- x.report("nerd_dice_random_object_3d6") do
132
+ x.report("total_dice_random_object_3d6") do
91
133
  NerdDice.configuration.randomization_technique = :random_object
92
134
  n.times { NerdDice.total_dice(6, 3) }
93
135
  end
94
136
 
95
- x.report("nerd_dice_randomized_3d6") do
137
+ x.report("total_dice_randomized_3d6") do
96
138
  NerdDice.configuration.randomization_technique = :randomized
97
139
  n.times { NerdDice.total_dice(6, 3) }
98
140
  end
@@ -106,3 +148,57 @@ total_dice_3d6_random_object = total_dice_3d6_results[2]
106
148
  check_against_baseline! random_rand_baseline, total_dice_3d6_random_object
107
149
  total_dice_3d6_randomized = total_dice_3d6_results[3]
108
150
  check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), total_dice_3d6_randomized
151
+
152
+ roll_dice_3d6_results = Benchmark.bmbm do |x|
153
+ # NerdDice.roll_dice securerandom
154
+ x.report("roll_dice_securerandom_3d6") do
155
+ NerdDice.configuration.randomization_technique = :securerandom
156
+ n.times { NerdDice.roll_dice(6, 3) }
157
+ end
158
+
159
+ x.report("roll_dice_random_rand_3d6") do
160
+ NerdDice.configuration.randomization_technique = :random_rand
161
+ n.times { NerdDice.roll_dice(6, 3) }
162
+ end
163
+
164
+ x.report("roll_dice_random_object_3d6") do
165
+ NerdDice.configuration.randomization_technique = :random_object
166
+ n.times { NerdDice.roll_dice(6, 3) }
167
+ end
168
+
169
+ x.report("roll_dice_randomized_3d6") do
170
+ NerdDice.configuration.randomization_technique = :randomized
171
+ n.times { NerdDice.roll_dice(6, 3) }
172
+ end
173
+ end
174
+
175
+ roll_dice_3d6_securerandom = roll_dice_3d6_results[0]
176
+ check_against_baseline! securerandom_baseline, roll_dice_3d6_securerandom
177
+ roll_dice_3d6_random_rand = roll_dice_3d6_results[1]
178
+ check_against_baseline! random_rand_baseline, roll_dice_3d6_random_rand
179
+ roll_dice_3d6_random_object = roll_dice_3d6_results[2]
180
+ check_against_baseline! random_rand_baseline, roll_dice_3d6_random_object
181
+ roll_dice_3d6_randomized = roll_dice_3d6_results[3]
182
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), roll_dice_3d6_randomized
183
+
184
+ puts "Setting n down to 5,000 due to more intensive methods"
185
+ n = 5_000
186
+
187
+ puts "Roll and total ability scores"
188
+ roll_ability_scores_results = Benchmark.bmbm do |x|
189
+ x.report("roll_ability_scores_randomized") do
190
+ NerdDice.configuration.randomization_technique = :randomized
191
+ n.times { NerdDice.roll_ability_scores }
192
+ end
193
+
194
+ x.report("total_ability_scores_randomized") do
195
+ NerdDice.configuration.randomization_technique = :randomized
196
+ n.times { NerdDice.total_ability_scores }
197
+ end
198
+ end
199
+
200
+ roll_ability_scores_randomized = roll_ability_scores_results[0]
201
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), roll_ability_scores_randomized
202
+ total_ability_scores_randomized = roll_ability_scores_results[1]
203
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)),
204
+ total_ability_scores_randomized
@@ -1,27 +1,26 @@
1
1
  -----BEGIN CERTIFICATE-----
2
- MIIEhTCCAu2gAwIBAgIBATANBgkqhkiG9w0BAQsFADBEMRYwFAYDVQQDDA1zdGF0
3
- ZWxlc3Njb2RlMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
4
- FgNjb20wHhcNMjMwMjIzMjMyMTQwWhcNMjQwMjIzMjMyMTQwWjBEMRYwFAYDVQQD
5
- DA1zdGF0ZWxlc3Njb2RlMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJ
6
- k/IsZAEZFgNjb20wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCxVzHS
7
- gYszan//tjTSO0z59UO1rUiT5G0//iyhiaYIiuwxhYFbfD+wOCou4M18pQtEb4qx
8
- o67tPfGimBAVak6fGfo8fo1ByHiKCvx3jgOjxNifT9pRFlBSr6ZvyXeu7zA0ddLr
9
- slw92DNqeRlZXqB0mxDtpKWONGc1XhAqEjEP3VL7g7x0xPQShcpXg/OyRPR5vyv8
10
- 66pXdFrXYZGrySfIB6ZOWFV6wGBj603rPdXOeYVeks6hKvw3wb4G1s7tvwTA5MWI
11
- otw6Mp9TaMdms9zTc5A3N58pueKfBJfwkICkdAGJDWC6sIXECoaTDRqVK96RSH/1
12
- 8tEPDoFYpJDOa5byX1j7srwO0B6WOtPxix7gW1wBbEp7eWSQf1k3k9XEh32SRsPq
13
- NJObRfhkzoa9p1tPkVP3nasDTK5gtisolwhb7Vimeup54yKfT/THv2iNEnGTvK1P
14
- sV4vC8nch88lBI1mIecmSh/mwED4Mb1dNtcyuB/+XnSI8vIzXJKAAKaT0eMCAwEA
15
- AaOBgTB/MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBRP42hSa0JV
16
- QGDSobiGTYyfM570hjAiBgNVHREEGzAZgRdzdGF0ZWxlc3Njb2RlQGdtYWlsLmNv
17
- bTAiBgNVHRIEGzAZgRdzdGF0ZWxlc3Njb2RlQGdtYWlsLmNvbTANBgkqhkiG9w0B
18
- AQsFAAOCAYEANtaR6OV7p1IJOsvVgGQzVg88NIOeXrfOaEDUPb6eg4JMOSL0Cvvl
19
- 2F7lB/ykbQcO4Oe7NucuavC7ClyG3c/V5eQ5TtPNWkMbVN9ESVR8wk5SjhiI8L35
20
- MBxJ6YU27eyDmazQJ7eCYcRJkuyWt3KcqgsEh7JyNnKcJ/3rgf1QW0IyJiGsXM1I
21
- SssQ/t7Ia2tVMrVMsvs834v9FRpVbO3dHdCO4t7zQBIADVcj4NqCDV10D6aji/Aa
22
- 35YJHwlkhuZH6AYC45QHt9dW0/OLmbFwoJqW7syrso2PParyMr4YcJwucXViRiL7
23
- l5aVpYdz/RTqdB92Mmud5Hj5zkuEE4CHBh8L8AJC5kZu/YUXXDtuECSMVhg5O84h
24
- QsdcuygyVASmw2aliMAFXfIBDYelduG0XwjdOREN3q4SDTKP+pfBxx6OdD1RfsYF
25
- /9HhtVbKLq34iQftF4oIH66bYDEyG5y4CLKQ8Nq0WDWq50OcaP9KpDiS21BC43SW
26
- t+NX7PDOWx4k
2
+ MIIETTCCArWgAwIBAgIBATANBgkqhkiG9w0BAQsFADAoMSYwJAYDVQQDDB1zdGF0
3
+ ZWxlc3Njb2RlL0RDPWdtYWlsL0RDPWNvbTAeFw0yMDEyMDYyMzQ1NTZaFw0yMTEy
4
+ MDYyMzQ1NTZaMCgxJjAkBgNVBAMMHXN0YXRlbGVzc2NvZGUvREM9Z21haWwvREM9
5
+ Y29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyHIMJi0o3bwBZsx5
6
+ TQ35XByFsdDRsro3T+0NY7EAILtOiU04o9C2NPOp/RQE7BXQgMjGebwp6bT6QvzN
7
+ 6noV4jPL7Fi5pWw08QygG7f+73YUBb+8d8o+3xGrC+UO5h1PZEtVcZwUWUG18QBE
8
+ fbDinQT6P4IDQoZwhfrPCB+aBfUyQp4Ok7oD7MEWqsq9SjrSxqxfk4+oZdXUySe7
9
+ Vi5vnzVQ5uFf56NHwWnNKCzJzmH84mBO5MzHaQpHNzKGJPoUmzLU5RBlCH6YXqBG
10
+ KhXTMUDBWKJmJ3RDry/FpGgJLKu4wzFRYjXla6IjeKozWGuPNNJ+2mesXKhsX7bo
11
+ vVCzRxPEupbEg/0FkJiWpiGlSPOdd6oJiwX8E6rlEeV605xrbOQewkbovHkYTMtG
12
+ +NH+u08x0z4Oj71kmDLwuj812uS0mtrCg2VhiYO0ZCQ4XrwBsBfK+/MtMlR+o6sG
13
+ /zvz/vHVJKaLTQxRp5oGo4QH6HfbOnwzTkXdZnt5AlN31ErJAgMBAAGjgYEwfzAJ
14
+ BgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUC7seYydsGO6O1qT4nVVD
15
+ G/LkiHYwIgYDVR0RBBswGYEXc3RhdGVsZXNzY29kZUBnbWFpbC5jb20wIgYDVR0S
16
+ BBswGYEXc3RhdGVsZXNzY29kZUBnbWFpbC5jb20wDQYJKoZIhvcNAQELBQADggGB
17
+ ADPRFRB1cjqdcE2O0jtqiDRmrR62uEYBiUbkRPVhyoEp/cK0TVhAs9mGWAyCWu0M
18
+ LewUeqNTUvQ9MgvagcKcnxa2RTjdrP3nGnwpStMr9bm3ArNJEzvWEs0Eusk9y73x
19
+ fjy0qH2pw5WPfWcKYlDehMXqOP+a4udYsz0YSNiI8qEfkDCSqTJN11d5kSjVjwGB
20
+ xkauxDT68j1JZRjPmQl3dl+DCgxkoziWX2mFTPLfGg5vZ0t6gmhdUtLvJtNIo0IX
21
+ 477E5UjmE1+rULQp/fsH6n5+H+t2eCED41ST+gkKbaQBUfIuUaCmdHz9sJaIIBw2
22
+ 6ordFa1nrLV4w5Uf6qYFnWVhIWX4GToyZSPO2s0DPYp3PWFJ4VtzKa2vp1TR5ZEA
23
+ dkij2eQ9M8bzWWmW+A7RNaI0CzLl967bKGBSaMVCsZGBarggWD8UwJnBhTuOPZGR
24
+ WQ4faXJSevxT+x9TgyUNJINPkz/KqreClzdL83cwxPzFFQto7zF6zMCsj0slqJjW
25
+ EQ==
27
26
  -----END CERTIFICATE-----
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ #############################
4
+ # Configuration class methods
5
+ #############################
6
+ # Usage:
7
+ # NerdDice.configure takes a block and yields the NerdDice::Configuration
8
+ # properties:
9
+ # If you wanted to configure several properties in a block:
10
+ # <tt>
11
+ # NerdDice.configure do |config|
12
+ # config.randomization_technique = :randomized
13
+ # config.die_background_color = "#FF0000"
14
+ # end
15
+ # </tt>
16
+ #
17
+ # NerdDice.configuration returns the NerdDice::Configuration object and lets you
18
+ # set properties on the NerdDice::Configuration object without using a block:
19
+ # <tt>
20
+ # config = NerdDice.configuration
21
+ # config.randomization_technique = :randomized
22
+ # config.die_background_color = "#FF0000"
23
+ # </tt>
24
+ module NerdDice
25
+ class << self
26
+ ############################
27
+ # configure class method
28
+ ############################
29
+ # Arguments: None
30
+ # Expects and yields to a block where configuration is specified.
31
+ # See README and NerdDice::Configuration class for config options
32
+ # Return (NerdDice::Configuration) the Configuration object tied to the
33
+ # @configuration class instance variable
34
+ def configure
35
+ yield configuration
36
+ configuration
37
+ end
38
+
39
+ ############################
40
+ # configuration class method
41
+ ############################
42
+ # Arguments: None
43
+ # Provides the lazy-loaded class instance variable @configuration
44
+ # Return (NerdDice::Configuration) the Configuration object tied to the
45
+ # @configuration class instance variable
46
+ def configuration
47
+ @configuration ||= Configuration.new
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ ###############################
4
+ # execute_die_roll class method
5
+ ###############################
6
+ # Usage:
7
+ # If you wanted to execute a single d4 die roll without a Die object, you would execute:
8
+ # <tt>NerdDice.execute_die_roll(4)</tt>
9
+ #
10
+ # If you wanted to execute a die roll with a different randomization technique
11
+ # than the one in NerdDice.configuration, you can supply an optional second argument
12
+ # <tt>NerdDice.execute_die_roll(4, :randomized)</tt>
13
+ module NerdDice
14
+ class << self
15
+ # Arguments:
16
+ # number_of_sides (Integer) => the number of sides of the die to roll
17
+ # using_generator (Symbol) => must be one of the symbols in
18
+ # RANDOMIZATION_TECHNIQUES or nil
19
+ #
20
+ # Return (Integer) => Value of the single die rolled
21
+ def execute_die_roll(number_of_sides, using_generator = nil)
22
+ @count_since_last_refresh ||= 0
23
+ gen = get_number_generator(using_generator)
24
+ result = gen.rand(number_of_sides) + 1
25
+ increment_and_evalutate_refresh_seed
26
+ result
27
+ end
28
+
29
+ private
30
+
31
+ def get_number_generator(using_generator = nil)
32
+ using_generator ||= configuration.randomization_technique
33
+ case using_generator
34
+ when :securerandom then SecureRandom
35
+ when :random_rand then Random
36
+ when :random_object then @random_object ||= Random.new
37
+ when :randomized then random_generator
38
+ else raise ArgumentError, "Unrecognized generator. Must be one of #{RANDOMIZATION_TECHNIQUES.join(', ')}"
39
+ end
40
+ end
41
+
42
+ def random_generator
43
+ gen = RANDOMIZATION_TECHNIQUES.reject { |el| el == :randomized }.sample
44
+ get_number_generator(gen)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ ############################
4
+ # harvest_totals method
5
+ ############################
6
+ # Usage:
7
+ # This method will take any collection of objects where each element responds to
8
+ # :total and return an array of the results of the total method.
9
+ #
10
+ # Example
11
+ # <tt>
12
+ # ability_score_array = NerdDice.roll_ability_scores
13
+ # => Array of 6 DiceSet objects
14
+ # totals_array = NerdDice.harvest_totals(totals_array)
15
+ # => [15, 14, 13, 12, 10, 8]
16
+ # # yes, it just happened to be the standard array by amazing coincidence
17
+ # </tt>
18
+ module NerdDice
19
+ class << self
20
+ # Arguments:
21
+ # collection (Enumerable) a collection where each element responds to total
22
+ #
23
+ # Return (Array) => Data type of each element will be whatever is returned by total method
24
+ def harvest_totals(collection)
25
+ collection.map(&:total)
26
+ rescue NoMethodError => e
27
+ specific_message =
28
+ case e.message
29
+ when /`total'/ then "Each element must respond to :total."
30
+ when /`map'/ then "Argument must respond to :map."
31
+ end
32
+ specific_message ? raise(ArgumentError, "You must provide a valid collection. #{specific_message}") : raise
33
+ end
34
+ end
35
+ end