nerd_dice 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa0fb8eebb2386727cce81ce7a3c301599322a3531db13f01329aa26225c88b3
4
- data.tar.gz: 205640e5b275ae107e0c2ee0ff1c10b777782f21c7a3c0ae7a5aac98e27f4841
3
+ metadata.gz: 8f64389429514a7d0c2a8410b2e27575e5c11b316b50cee41b37c58d8f8af953
4
+ data.tar.gz: 4d2365e3ce8139d4675695dfe47edf9ef5d52b9cc873beb5c7c314b8955aaa33
5
5
  SHA512:
6
- metadata.gz: 0d275950feb1506a5a7602450690dab697c0ccd35b8d85a0869c4675c2dd714c85d2c744854ac7f69c7f204b1e55ecbf7e80f9209eb672ef4705d15fdbd10fd7
7
- data.tar.gz: fb8d5aa2297ecbf722b9242caf2680272a502d801dbd94fd53c6bd359a21a50e6e0e1096a5c5518a25a9ef842609c6db0af7c6b5273f4a1fd2e4f2f6089d393e
6
+ metadata.gz: d8bf0ea2c85e3aedf952d82d0f511463accedcad5122b14a7d6769a94100902887ca0e52cf85a6d8b69a75a6cd750858852ad71e34980e61624d39666bedff5b
7
+ data.tar.gz: ee2e8117b3071872a3d07353dabd4685c4a20e188591e80cbcb93609ba9f134ab22082ae01bee152c34c9cdd1a81a6a40aec2b1521d3e2a106d10f0c8fa313ae
checksums.yaml.gz.sig CHANGED
Binary file
data/.rubocop.yml CHANGED
@@ -400,3 +400,22 @@ Lint/AmbiguousRange: # new in 1.19
400
400
  Enabled: true
401
401
  Style/RedundantSelfAssignmentBranch: # new in 1.19
402
402
  Enabled: true
403
+
404
+ Lint/AmbiguousOperatorPrecedence: # new in 1.21
405
+ Enabled: true
406
+ Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21
407
+ Enabled: true
408
+ Lint/RequireRelativeSelfPath: # new in 1.22
409
+ Enabled: true
410
+ Security/IoMethods: # new in 1.22
411
+ Enabled: true
412
+ Style/NumberedParameters: # new in 1.22
413
+ Enabled: true
414
+ Style/NumberedParametersLimit: # new in 1.22
415
+ Enabled: true
416
+ Style/SelectByRegexp: # new in 1.22
417
+ Enabled: true
418
+ RSpec/ExcessiveDocstringSpacing: # new in 2.5
419
+ Enabled: true
420
+ RSpec/SubjectDeclaration: # new in 2.5
421
+ Enabled: true
data/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@
5
5
  ### Changed
6
6
  ### Fixed
7
7
 
8
+ ## 0.4.0 \(2021-10-23\)
9
+ ### Added
10
+ * Add `NerdDice::ConvenienceMethods` method_missing mixin module that allows for dynamic invocation of patterns in the method name that get converted into calls to `NerdDice.roll_dice` or `NerdDice.total_dice` along with allowing the advantage/disadvantage mechanic or bonuses to be parsed from the method name. Full documentation of the module can be found in the [Convenience Methods Mixin](README.md#convenience-methods-mixin) section of the README.
11
+ * Add exensive specs to support the ConvenienceMethods module
12
+ ### Changed
13
+ * Replace `Benchmark.bmbm` with `Benchmark.bm` in the nerd_dice_benchmark
14
+ * Add convenience_methods to nerd_dice_benchmark
15
+ * Extend `NerdDice::ConvenienceMethods` into top-level module as class methods
16
+ ### Fixed
17
+ * Fix typos and horizontal scrolling in README
18
+ * Fix CodeClimate Code Smell on harvest_totals
19
+
8
20
  ## 0.3.0 \(2021-09-11\)
9
21
  ### Added
10
22
  * Add new options to `NerdDice::Configuration`
data/Gemfile CHANGED
@@ -5,5 +5,5 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in nerd_dice.gemspec
6
6
  gemspec
7
7
 
8
- gem "rake", "~> 12.0"
9
- gem "rspec", "~> 3.0"
8
+ gem "rake", "~> 13.0"
9
+ gem "rspec", "~> 3.10"
data/Gemfile.lock CHANGED
@@ -1,25 +1,25 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nerd_dice (0.3.0)
5
- securerandom (~> 0.1, >= 0.1.0)
4
+ nerd_dice (0.4.0)
5
+ securerandom (~> 0.1, >= 0.1.1)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  ast (2.4.2)
11
- coveralls_reborn (0.22.0)
11
+ coveralls_reborn (0.23.0)
12
12
  simplecov (>= 0.18.1, < 0.22.0)
13
13
  term-ansicolor (~> 1.6)
14
14
  thor (>= 0.20.3, < 2.0)
15
15
  tins (~> 1.16)
16
16
  diff-lcs (1.4.4)
17
17
  docile (1.4.0)
18
- parallel (1.20.1)
18
+ parallel (1.21.0)
19
19
  parser (3.0.2.0)
20
20
  ast (~> 2.4.1)
21
21
  rainbow (3.0.0)
22
- rake (12.3.3)
22
+ rake (13.0.6)
23
23
  regexp_parser (2.1.1)
24
24
  rexml (3.2.5)
25
25
  rspec (3.10.0)
@@ -35,27 +35,26 @@ GEM
35
35
  diff-lcs (>= 1.2.0, < 2.0)
36
36
  rspec-support (~> 3.10.0)
37
37
  rspec-support (3.10.2)
38
- rubocop (1.20.0)
38
+ rubocop (1.22.2)
39
39
  parallel (~> 1.10)
40
40
  parser (>= 3.0.0.0)
41
41
  rainbow (>= 2.2.2, < 4.0)
42
42
  regexp_parser (>= 1.8, < 3.0)
43
43
  rexml
44
- rubocop-ast (>= 1.9.1, < 2.0)
44
+ rubocop-ast (>= 1.12.0, < 2.0)
45
45
  ruby-progressbar (~> 1.7)
46
46
  unicode-display_width (>= 1.4.0, < 3.0)
47
- rubocop-ast (1.11.0)
47
+ rubocop-ast (1.12.0)
48
48
  parser (>= 3.0.1.1)
49
49
  rubocop-performance (1.11.5)
50
50
  rubocop (>= 1.7.0, < 2.0)
51
51
  rubocop-ast (>= 0.4.0)
52
52
  rubocop-rake (0.6.0)
53
53
  rubocop (~> 1.0)
54
- rubocop-rspec (2.4.0)
55
- rubocop (~> 1.0)
56
- rubocop-ast (>= 1.1.0)
54
+ rubocop-rspec (2.5.0)
55
+ rubocop (~> 1.19)
57
56
  ruby-progressbar (1.11.0)
58
- securerandom (0.1.0)
57
+ securerandom (0.1.1)
59
58
  simplecov (0.21.2)
60
59
  docile (~> 1.1)
61
60
  simplecov-html (~> 0.11)
@@ -69,20 +68,20 @@ GEM
69
68
  thor (1.1.0)
70
69
  tins (1.29.1)
71
70
  sync
72
- unicode-display_width (2.0.0)
71
+ unicode-display_width (2.1.0)
73
72
 
74
73
  PLATFORMS
75
74
  ruby
76
75
 
77
76
  DEPENDENCIES
78
- coveralls_reborn (~> 0.22.0)
77
+ coveralls_reborn (~> 0.23.0)
79
78
  nerd_dice!
80
- rake (~> 12.0)
81
- rspec (~> 3.0)
82
- rubocop (~> 1.20, >= 1.20.0)
79
+ rake (~> 13.0)
80
+ rspec (~> 3.10)
81
+ rubocop (~> 1.22, >= 1.22.2)
83
82
  rubocop-performance (~> 1.11, >= 1.11.5)
84
83
  rubocop-rake (~> 0.6, >= 0.6.0)
85
- rubocop-rspec (~> 2.4, >= 2.4.0)
84
+ rubocop-rspec (~> 2.5, >= 2.5.0)
86
85
  simplecov-lcov (~> 0.8.0)
87
86
 
88
87
  BUNDLED WITH
data/README.md CHANGED
@@ -24,6 +24,15 @@ Or install it yourself as:
24
24
  $ gem install nerd_dice
25
25
 
26
26
  ## Usage
27
+ After the gem is installed, you can require it as you would any other gem.
28
+
29
+ ```ruby
30
+ require 'nerd_dice'
31
+ ```
32
+
33
+ ### Module methods or a dynamic method_missing DSL
34
+ There are two main patterns for using NerdDice in your project. You can invoke the module-level methods like `NerdDice.total_dice` or you can include the `NerdDice::ConvenienceMethods` module to your class \(or IRB \). Once mixed in, you can dynamically invoke methods like `roll_d20_with_advantage` or `total_3d8_plus5`. See the [Convenience Methods Mixin](#convenience-methods-mixin) section for usage details.
35
+
27
36
  ### Configuration
28
37
  You can customize the behavior of NerdDice via a configuration block as below or by assigning an individual property via the ```NerdDice.configuration.property = value``` syntax \(where ```property``` is the config property and ```value``` is the value you want to assign\)\. The available configuration options as well as their defaults, if applicable, are listed in the example configuration block below:
29
38
 
@@ -39,7 +48,9 @@ NerdDice.configure do | config|
39
48
  # total number of dice rolled for each ability score
40
49
  config.ability_score_dice_rolled = 4 # must duck-type to positive Integer
41
50
 
42
- # highest(n) dice from the total number of dice rolled that are included in the ability score total
51
+ # highest(n) dice from the total number of dice rolled
52
+ # that are included in the ability scoretotal
53
+ #
43
54
  # CANNOT EXCEED ability_score_dice_rolled see Note below
44
55
  config.ability_score_dice_kept = 3 # must duck-type to positive Integer
45
56
 
@@ -49,14 +60,16 @@ NerdDice.configure do | config|
49
60
  # (Seed is shared with other processes. Too predictable)
50
61
  # :random_object => Uses Random.new() and calls rand()
51
62
  # Medium entropy, fastest speed. (Performs the best under speed benchmark)
52
- # :randomized => Uses a random choice of the :securerandom, :rand, and :random_new_interval options above
63
+ # :randomized =>
64
+ # Uses a random choice of the :securerandom, :rand, and :random_new_interval options above
53
65
  config.randomization_technique = :random_object # fast with independent seed
54
66
 
55
67
  # Number of iterations to use on a generator before refreshing the seed
56
68
  # 1 very slow and heavy pressure on processor and memory but very high entropy
57
69
  # 1000 would refresh the object every 1000 times you call rand()
58
70
  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
71
+ # Background and foreground die colors are string values.
72
+ # By default these correspond to the constants in the class
60
73
  # Defaults: DEFAULT_BACKGROUND_COLOR = "#0000DD" DEFAULT_FOREGROUND_COLOR = "#DDDDDD"
61
74
  # It is recommended but not enforced that these should be valid CSS color property attributes
62
75
  config.die_background_color = "red"
@@ -70,25 +83,26 @@ You can use two different methods to roll dice. The `total_dice` method returns
70
83
 
71
84
  ```ruby
72
85
  # roll a single d4
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
86
+ NerdDice.total_dice(4) # => Integer: between 1-4
87
+ NerdDice.roll_dice(4) # => DiceSet: with one 4-sided Die with a value between 1-4
88
+ NerdDice.roll_dice(4).total # => Integer: between 1-4
76
89
 
77
90
  # roll 3d6
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
91
+ NerdDice.total_dice(6, 3) # => Integer: total of three 6-sided dice
92
+ NerdDice.roll_dice(6, 3) # => DiceSet: three 6-sided Die objects, each with values between 1-6
93
+ NerdDice.roll_dice(6, 3).total # => Integer: total of three 6-sided dice
81
94
 
82
95
  # roll a d20 and add 5 to the value
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
96
+ NerdDice.total_dice(20, bonus: 5) # => Integer: roll a d20 and add the bonus to the total
97
+ NerdDice.roll_dice(20, bonus: 5) # => DiceSet: one 20-sided Die and bonus of 5
98
+ NerdDice.roll_dice(20, bonus: 5).total # => Integer: roll a d20 and add the bonus to the total
86
99
 
87
100
  # without changing the config at the module level
88
101
  # roll a d20 and overide the configured randomization_technique one time
89
102
  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
103
+ # roll a d20 and overide the configured randomization_technique for the DiceSet
104
+ # object will persist on the DiceSet object for subsequent rerolls
105
+ NerdDice.roll_dice(20, randomization_technique: :randomized) # => DiceSet with :randomized
92
106
  ```
93
107
  __NOTE:__ If provided, the bonus must respond to `:to_i` or an `ArgumentError` will be raised
94
108
 
@@ -96,26 +110,29 @@ __NOTE:__ If provided, the bonus must respond to `:to_i` or an `ArgumentError` w
96
110
  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
111
 
98
112
  ```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
113
+ # These are equivalent. Both return a NerdDice::DiceSet
114
+ dice_set = NerdDice.roll_dice(6, 3, bonus: 2, randomization_technique: :randomized,
115
+ damage_type: 'psychic', foreground_color: '#FFF', background_color: '#0FF')
116
+
117
+ dice_set = NerdDice::DiceSet.new(6, 3, bonus: 2, randomization_technique: :randomized,
118
+ damage_type: 'psychic', foreground_color: '#FFF', background_color: '#0FF')
119
+
104
120
  ```
105
121
  #### Available options for NerdDice::DiceSet objects
106
122
  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
123
  * `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_
124
+ * `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 to modify_
125
+ * `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 to modify_
126
+ * `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 to modify_
127
+ * `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 to modify_
112
128
 
113
129
  #### Properties of individual Die objects
114
130
  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
131
 
116
132
  ```ruby
117
133
  # six sided die
118
- die = NerdDice::Die.new(6, randomization_technique: :randomized, damage_type: 'psychic', foreground_color: '#FFF', background_color: '#0FF')
134
+ die = NerdDice::Die.new(6, randomization_technique: :randomized, damage_type: 'psychic',
135
+ foreground_color: '#FFF', background_color: '#0FF')
119
136
  die.is_included_in_total # => true
120
137
  die.included_in_total? # => true
121
138
  die.is_included_in_total = false
@@ -123,9 +140,11 @@ die.included_in_total? # => false
123
140
 
124
141
  # value property
125
142
  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
143
+
144
+ # Rolls/rerolls the Die, sets value to the result of the roll, and returns the new value
145
+ die.roll # => Integer.
127
146
  ```
128
- #### Iterating through dices in a DiceSet
147
+ #### Iterating through dice in a DiceSet
129
148
  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
149
 
131
150
  ```ruby
@@ -155,23 +174,31 @@ Since the DiceSet is an object, you can call methods that operate on the result
155
174
  # assuming 4d6 with values of [1, 3, 4, 6]
156
175
  ##############################################
157
176
  dice_set = NerdDice.roll_dice(6, 4)
177
+
158
178
  # 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
179
+ # Returns the existing DiceSet object with the changes made to dice inclusion
180
+ dice_set.highest(3) # => DiceSet
181
+ dice_set.with_advantage(3) # => DiceSet (Alias of highest method)
182
+
161
183
  # calling total after highest/with_advantage for this DiceSet
162
184
  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
185
+
186
+ # same DiceSet using lowest.
187
+ # The 1, 3, and 4 will have is_included_in_total true while the 6 has it false
188
+ dice_set.lowest(3) # => DiceSet
189
+ dice_set.with_disadvantage(3) # => DiceSet (Alias of lowest method)
190
+
166
191
  # calling total after lowest/with_disadvantage for this DiceSet
167
192
  dice_set.total # => 8
193
+
168
194
  # you can chain these methods (assumes the same seed as the above examples)
169
195
  NerdDice.roll_dice(6, 4).with_advantage(3).total # => 13
170
196
  NerdDice.roll_dice(6, 4).lowest(3).total # => 8
171
197
 
172
198
  # reroll_all! method
173
199
  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
200
+ # rerolls each of the Die objects in the collection and re-includes them in the total
201
+ dice_set.reroll_all!
175
202
 
176
203
  # include_all_dice! method
177
204
  dice_set.include_all_dice! # resets is_included_in_total to true for all Die objects
@@ -232,7 +259,7 @@ NerdDice.refresh_seed!(randomization_technique: :randomized,
232
259
  random_rand_seed: 1337,
233
260
  random_object_seed: 24601)
234
261
  ```
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.
262
+ __NOTE:__ Ability to specify a seed is primarily provided for testing purposes. This makes all random numbers generated _transparently deterministic_ and should not be used if you want behavior approximating randomness.
236
263
 
237
264
  ### Utility Methods
238
265
 
@@ -250,6 +277,126 @@ totals_array = NerdDice.harvest_totals(totals_array)
250
277
  # => [15, 14, 13, 12, 10, 8]
251
278
  # yes, it just happened to be the standard array by amazing coincidence
252
279
  ```
280
+ <a name="convenience-methods-mixin"></a>
281
+ ### Convenience Methods Mixin
282
+ NerdDice provides an optional mixin `NerdDice::ConvenienceMethods` that uses Ruby\'s `method_missing` metaprogramming pattern to allow you to roll any number of dice with bonuses and/or the advantage/disadvantage mechanic by dynamically responding to methods that you type that match the `roll_` or `total_` pattern.
283
+
284
+ #### Considerations for ConvenienceMethods
285
+ Before mixing in this method with a class, be aware of other `method_missing` gems that you are also mixing into your project and be sure to write robust tests. We have sought to use `method_missing` in a responsible manner that delegates back to the default implementation using `super` if the method does not match the `ConvenienceMethods` pattern, but there is no guarantee that other gems included in your project are doing the same. If you run into problems with the `ConvenienceMethods` module interacting with other `method_missing` gems, everything that the `ConvenienceMethods` module does can be replicated using the module\-level methods described above or by calling the convenience method on `NerdDice`.
286
+
287
+ Once a particular method has been called, it will define that method so that the next time it will invoke the method directly instead of traversing up the call stack for `method_missing`, which improves performance. The method will remain defined for the duration of the Ruby interpreter process.
288
+
289
+ #### Calling ConvenienceMethods as NerdDice class methods
290
+ NerdDice extends the `ConvenienceMethods` module into the top-level module as class methods, so you can call the methods on the NerdDice module without needing to worry about the implications of extending it into your own class.
291
+ ```ruby
292
+ require 'nerd_dice'
293
+ # works with all the examples and patterns below
294
+ NerdDice.roll_3d6_lowest2_minus1
295
+ NerdDice.total_d20_with_advantage_p6
296
+ ```
297
+
298
+ #### Mixing in the ConvenienceMethods module
299
+ To mix the NerdDice DSL into your class, make sure the gem is required if not already and then use `include NerdDice::ConvenienceMethods` as shown below:
300
+ ```ruby
301
+ # example of a class that mixes in NerdDice::ConvenienceMethods
302
+ require 'nerd_dice'
303
+ class Monster
304
+ include NerdDice::ConvenienceMethods
305
+
306
+ # hard-coding probably not the best solution
307
+ # but gives you an idea how to mix in to a simple class
308
+ def hits_the_monster
309
+ # using the ConvenienceMethods version
310
+ total_d20_plus5 >= @armor_class ? "hit" : "miss"
311
+ end
312
+
313
+ def initialize(armor_class=16)
314
+ @armor_class = armor_class
315
+ end
316
+ end
317
+ ```
318
+ To mix in the module as class methods, you can use `extend NerdDice::ConvenienceMethods`
319
+ ```ruby
320
+ # example of a class that mixes in NerdDice::ConvenienceMethods
321
+ require 'nerd_dice'
322
+ class OtherClass
323
+ extend NerdDice::ConvenienceMethods
324
+ end
325
+ OtherClass.roll_3d6_lowest2_minus1 # returns NerdDice::DiceSet
326
+ ```
327
+
328
+ #### ConvenienceMethods usage examples
329
+ Any invocation of `NerdDice.roll_dice` and `NerdDice.total_dice` can be duplicated using the `NerdDice::ConvenienceMethods` mixin. Here are some examples of what you can do with the return types and equivalent methods in the comments:
330
+
331
+ * `roll_dNN` and `total_dNN` roll one die
332
+ ```ruby
333
+ roll_d20 # => DiceSet: NerdDice.roll_dice(20)
334
+ roll_d8 # => DiceSet: NerdDice.roll_dice(8)
335
+ roll_d1000 # => DiceSet: NerdDice.roll_dice(1000)
336
+ total_d20 # => Integer NerdDice.total_dice(20)
337
+ total_d8 # => Integer NerdDice.total_dice(8)
338
+ total_d1000 # => Integer NerdDice.total_dice(1000)
339
+ ```
340
+ * `roll_NNdNN` and `total_NNdNN` roll specified quantity of dice
341
+ ```ruby
342
+ roll_2d20 # => DiceSet: NerdDice.roll_dice(20, 2)
343
+ roll_3d8 # => DiceSet: NerdDice.roll_dice(8, 3)
344
+ roll_22d1000 # => DiceSet: NerdDice.roll_dice(1000, 22)
345
+ total_2d20 # => Integer NerdDice.total_dice(20, 2)
346
+ total_3d8 # => Integer NerdDice.total_dice(8, 3)
347
+ total_22d1000 # => Integer NerdDice.total_dice(1000, 22)
348
+ ```
349
+ * Keyword arguments are passed on to `roll_dice`/`total_dice` method
350
+ ```ruby
351
+ roll_2d20 foreground_color: "blue" # => DiceSet: NerdDice.roll_dice(20, 2, foreground_color: "blue")
352
+
353
+ total_d12 randomization_technique: :randomized
354
+ # => Integer NerdDice.total_dice(12, randomization_technique: :randomized)
355
+ total_22d1000 randomization_technique: :random_rand
356
+ # => Integer NerdDice.total_dice(1000, 22, randomization_technique: :random_rand)
357
+
358
+ roll_4d6_with_advantage3 background_color: 'blue'
359
+ # => DiceSet: NerdDice.roll_dice(4, 3, background_color: 'blue').highest(3)
360
+ total_4d6_with_advantage3 randomization_technique: :random_rand
361
+ # => Integer: NerdDice.roll_dice(4, 3, randomization_technique: :random_rand).highest(3).total
362
+ ```
363
+ * Positive and negative bonuses can be used with `plus` (alias `p`) or `minus` (alias `m`)
364
+ ```ruby
365
+ roll_d20_plus6 # => DiceSet: NerdDice.roll_dice(20, bonus: 6)
366
+ total_3d8_p2 # => Integer: NerdDice.total_dice(8, 3, bonus: 2)
367
+ total_d20_minus5 # => Integer: NerdDice.total_dice(20, bonus: -6)
368
+ roll_3d8_m3 # => DiceSet: NerdDice.roll_dice(8, 3, bonus: -3)
369
+ ```
370
+ * `_with_advantageN` or `highestN` roll with advantage
371
+ * `_with_disadvantageN` or `lowestN` roll with disadvantage
372
+ * Calling `roll_dNN_with_advantage` \(and variants\) rolls 2 dice and keeps one
373
+ ```ruby
374
+ # equivalent
375
+ roll_3d8_with_advantage1
376
+ roll_3d8_highest1
377
+ # => DiceSet: NerdDice.roll_dice(8, 3).with_advantage(1)
378
+
379
+ # calls roll_dice and total to return an integer
380
+ total_3d8_with_advantage1
381
+ total_3d8_highest1
382
+ # => Integer: NerdDice.roll_dice(8, 3).with_advantage(1).total
383
+
384
+ # rolls two dice in this case
385
+ # equal to roll_2d20_with_advantage but more natural
386
+ roll_d20_with_advantage # => DiceSet: NerdDice.roll_dice(20, 2).with_advantage(1)
387
+ # equal to total_2d20_with_advantage but more natural
388
+ total_d20_with_advantage # => Integer: NerdDice.roll_dice(20, 2).with_advantage(1).total
389
+ ```
390
+ #### ConvenienceMethods error handling
391
+ * If you try to call with a plus and a minus, an Exception is raised
392
+ * If you call with a bonus and a keyword argument and they don't match, an Exception is raised
393
+ * Any combination not expressly allowed or matched will call `super` on `method_missing`
394
+ ```ruby
395
+ roll_3d8_plus3_m2 # will raise NameError using super method_missing
396
+ roll_3d8_plus3 bonus: 1 # will raise NerdDice::Error with message about "Bonus integrity failure"
397
+ roll_d20_with_advantage_lowest # will raise NameError using super method_missing
398
+ total_4d6_lowest3_highest2 # will raise NameError using super method_missing
399
+ ```
253
400
 
254
401
  ## Development
255
402
 
@@ -264,4 +411,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/statel
264
411
 
265
412
  ## Unlicense, License, and Copyright
266
413
 
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\.
414
+ The project 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\.
@@ -16,16 +16,28 @@ RATIOS = {
16
16
  total_dice_random_rand_3d6: 30.0,
17
17
  total_dice_random_object_3d6: 25.5,
18
18
  total_dice_randomized_3d6: 15.5,
19
+ total_magic_securerandom: 6.04,
20
+ total_magic_random_rand: 85.9,
21
+ total_magic_random_object: 89.42,
22
+ total_magic_randomized: 20.27,
19
23
  roll_dice_securerandom: 4.0,
20
24
  roll_dice_random_rand: 42.0,
21
25
  roll_dice_random_object: 44.0,
22
26
  roll_dice_randomized: 14.5,
27
+ roll_magic_securerandom: 6.04,
28
+ roll_magic_random_rand: 85.9,
29
+ roll_magic_random_object: 89.42,
30
+ roll_magic_randomized: 20.27,
23
31
  roll_dice_securerandom_3d6: 13.0,
24
32
  roll_dice_random_rand_3d6: 79.0,
25
33
  roll_dice_random_object_3d6: 86.0,
26
34
  roll_dice_randomized_3d6: 26.5,
27
- roll_ability_scores_randomized: 26.5,
28
- total_ability_scores_randomized: 26.5
35
+ roll_magic_securerandom_3d6: 18.38,
36
+ roll_magic_random_rand_3d6: 108.54,
37
+ roll_magic_random_object_3d6: 118.36,
38
+ roll_magic_randomized_3d6: 44.39,
39
+ roll_ability_scores_randomized: 30.5,
40
+ total_ability_scores_randomized: 30.5
29
41
  }.freeze
30
42
 
31
43
  def check_against_baseline!(baseline_value, test_value)
@@ -36,7 +48,7 @@ def check_against_baseline!(baseline_value, test_value)
36
48
  end
37
49
 
38
50
  puts "Set baseline"
39
- baselines = Benchmark.bmbm do |x|
51
+ baselines = Benchmark.bm do |x|
40
52
  # Random.rand()
41
53
  x.report("Random.rand") do # standard rand()
42
54
  n.times { Random.rand(1000) }
@@ -52,7 +64,9 @@ random_rand_baseline = baselines[0].real
52
64
  securerandom_baseline = baselines[1].real
53
65
 
54
66
  puts "Roll d1000s"
55
- total_dice_d1000_results = Benchmark.bmbm do |x|
67
+
68
+ puts "total_dice"
69
+ total_dice_d1000_results = Benchmark.bm do |x|
56
70
  # NerdDice.total_dice securerandom
57
71
  x.report("total_dice_securerandom") do
58
72
  NerdDice.configuration.randomization_technique = :securerandom
@@ -84,7 +98,43 @@ check_against_baseline! random_rand_baseline, total_dice_random_object
84
98
  total_dice_randomized = total_dice_d1000_results[3]
85
99
  check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), total_dice_randomized
86
100
 
87
- roll_dice_d1000_results = Benchmark.bmbm do |x|
101
+ puts "total_ d1000s ConvenienceMethods"
102
+
103
+ # NOTE: Due to method_missing overhead, using roll_ ratios for ConvenienceMethods total_dice
104
+ total_d1000_results = Benchmark.bm do |x|
105
+ # NerdDice.total_dice securerandom
106
+ x.report("total_magic_securerandom") do
107
+ NerdDice.configuration.randomization_technique = :securerandom
108
+ n.times { NerdDice.total_d1000 }
109
+ end
110
+
111
+ x.report("total_magic_random_rand") do
112
+ NerdDice.configuration.randomization_technique = :random_rand
113
+ n.times { NerdDice.total_d1000 }
114
+ end
115
+
116
+ x.report("total_magic_random_object") do
117
+ NerdDice.configuration.randomization_technique = :random_object
118
+ n.times { NerdDice.total_d1000 }
119
+ end
120
+
121
+ x.report("total_magic_randomized") do
122
+ NerdDice.configuration.randomization_technique = :randomized
123
+ n.times { NerdDice.total_d1000 }
124
+ end
125
+ end
126
+
127
+ total_magic_securerandom = total_d1000_results[0]
128
+ check_against_baseline! securerandom_baseline, total_magic_securerandom
129
+ total_magic_random_rand = total_d1000_results[1]
130
+ check_against_baseline! random_rand_baseline, total_magic_random_rand
131
+ total_magic_random_object = total_d1000_results[2]
132
+ check_against_baseline! random_rand_baseline, total_magic_random_object
133
+ total_magic_randomized = total_d1000_results[3]
134
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), total_magic_randomized
135
+
136
+ puts "roll_dice"
137
+ roll_dice_d1000_results = Benchmark.bm do |x|
88
138
  # NerdDice.roll_dice securerandom
89
139
  x.report("roll_dice_securerandom") do
90
140
  NerdDice.configuration.randomization_technique = :securerandom
@@ -116,8 +166,42 @@ check_against_baseline! random_rand_baseline, roll_dice_random_object
116
166
  roll_dice_randomized = roll_dice_d1000_results[3]
117
167
  check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), roll_dice_randomized
118
168
 
169
+ puts "roll_ d1000s ConvenienceMethods"
170
+ roll_d1000_results = Benchmark.bm do |x|
171
+ # NerdDice.roll_dice securerandom
172
+ x.report("roll_magic_securerandom") do
173
+ NerdDice.configuration.randomization_technique = :securerandom
174
+ n.times { NerdDice.roll_d1000 }
175
+ end
176
+
177
+ x.report("roll_magic_random_rand") do
178
+ NerdDice.configuration.randomization_technique = :random_rand
179
+ n.times { NerdDice.roll_d1000 }
180
+ end
181
+
182
+ x.report("roll_magic_random_object") do
183
+ NerdDice.configuration.randomization_technique = :random_object
184
+ n.times { NerdDice.roll_d1000 }
185
+ end
186
+
187
+ x.report("roll_magic_randomized") do
188
+ NerdDice.configuration.randomization_technique = :randomized
189
+ n.times { NerdDice.roll_d1000 }
190
+ end
191
+ end
192
+
193
+ roll_magic_securerandom = roll_d1000_results[0]
194
+ check_against_baseline! securerandom_baseline, roll_magic_securerandom
195
+ roll_magic_random_rand = roll_d1000_results[1]
196
+ check_against_baseline! random_rand_baseline, roll_magic_random_rand
197
+ roll_magic_random_object = roll_d1000_results[2]
198
+ check_against_baseline! random_rand_baseline, roll_magic_random_object
199
+ roll_magic_randomized = roll_d1000_results[3]
200
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), roll_magic_randomized
201
+
119
202
  puts "Roll 3d6"
120
- total_dice_3d6_results = Benchmark.bmbm do |x|
203
+ puts "total_dice 3d6"
204
+ total_dice_3d6_results = Benchmark.bm do |x|
121
205
  # NerdDice.total_dice securerandom
122
206
  x.report("total_dice_securerandom_3d6") do
123
207
  NerdDice.configuration.randomization_technique = :securerandom
@@ -149,7 +233,8 @@ check_against_baseline! random_rand_baseline, total_dice_3d6_random_object
149
233
  total_dice_3d6_randomized = total_dice_3d6_results[3]
150
234
  check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), total_dice_3d6_randomized
151
235
 
152
- roll_dice_3d6_results = Benchmark.bmbm do |x|
236
+ puts "roll_dice 3d6"
237
+ roll_dice_3d6_results = Benchmark.bm do |x|
153
238
  # NerdDice.roll_dice securerandom
154
239
  x.report("roll_dice_securerandom_3d6") do
155
240
  NerdDice.configuration.randomization_technique = :securerandom
@@ -181,11 +266,44 @@ check_against_baseline! random_rand_baseline, roll_dice_3d6_random_object
181
266
  roll_dice_3d6_randomized = roll_dice_3d6_results[3]
182
267
  check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), roll_dice_3d6_randomized
183
268
 
269
+ puts "roll_3d6 ConvenienceMethods"
270
+ roll_magic_3d6_results = Benchmark.bm do |x|
271
+ # NerdDice.roll_magic securerandom
272
+ x.report("roll_magic_securerandom_3d6") do
273
+ NerdDice.configuration.randomization_technique = :securerandom
274
+ n.times { NerdDice.roll_3d6 }
275
+ end
276
+
277
+ x.report("roll_magic_random_rand_3d6") do
278
+ NerdDice.configuration.randomization_technique = :random_rand
279
+ n.times { NerdDice.roll_3d6 }
280
+ end
281
+
282
+ x.report("roll_magic_random_object_3d6") do
283
+ NerdDice.configuration.randomization_technique = :random_object
284
+ n.times { NerdDice.roll_3d6 }
285
+ end
286
+
287
+ x.report("roll_magic_randomized_3d6") do
288
+ NerdDice.configuration.randomization_technique = :randomized
289
+ n.times { NerdDice.roll_3d6 }
290
+ end
291
+ end
292
+
293
+ roll_magic_3d6_securerandom = roll_magic_3d6_results[0]
294
+ check_against_baseline! securerandom_baseline, roll_magic_3d6_securerandom
295
+ roll_magic_3d6_random_rand = roll_magic_3d6_results[1]
296
+ check_against_baseline! random_rand_baseline, roll_magic_3d6_random_rand
297
+ roll_magic_3d6_random_object = roll_magic_3d6_results[2]
298
+ check_against_baseline! random_rand_baseline, roll_magic_3d6_random_object
299
+ roll_magic_3d6_randomized = roll_magic_3d6_results[3]
300
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), roll_magic_3d6_randomized
301
+
184
302
  puts "Setting n down to 5,000 due to more intensive methods"
185
303
  n = 5_000
186
304
 
187
305
  puts "Roll and total ability scores"
188
- roll_ability_scores_results = Benchmark.bmbm do |x|
306
+ roll_ability_scores_results = Benchmark.bm do |x|
189
307
  x.report("roll_ability_scores_randomized") do
190
308
  NerdDice.configuration.randomization_technique = :randomized
191
309
  n.times { NerdDice.roll_ability_scores }
@@ -0,0 +1 @@
1
+ 30f9d3809362099e81e414bf987b711c8dad59703c68579809bb36ce9b199d58
@@ -0,0 +1 @@
1
+ 1a8913b8f2878c1471f32dadaddf9965fd61c7ef2e9a255961babdb9586f2c18b7eca1cd0f1021061fb69c96a330151a97df9a102b2871e1e1c5e5675f34a511
@@ -24,12 +24,17 @@ module NerdDice
24
24
  def harvest_totals(collection)
25
25
  collection.map(&:total)
26
26
  rescue NoMethodError => e
27
- specific_message =
28
- case e.message
27
+ specific_message = get_harvest_totals_error_message(e)
28
+ specific_message ? raise(ArgumentError, "You must provide a valid collection. #{specific_message}") : raise
29
+ end
30
+
31
+ private
32
+
33
+ def get_harvest_totals_error_message(rescued_error)
34
+ case rescued_error.message
29
35
  when /`total'/ then "Each element must respond to :total."
30
36
  when /`map'/ then "Argument must respond to :map."
31
37
  end
32
- specific_message ? raise(ArgumentError, "You must provide a valid collection. #{specific_message}") : raise
33
- end
38
+ end
34
39
  end
35
40
  end
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NerdDice
4
+ # The NerdDice::ConvenienceMethods module overrides the default behavior of method_missing to
5
+ # provide the ability to dynamically roll dice with methods names that match specific patterns.
6
+ #
7
+ # Examples:
8
+ # `roll_dNN` and `total_dNN` roll one die
9
+ # <tt>
10
+ # roll_d20 # => DiceSet: NerdDice.roll_dice(20)
11
+ # roll_d8 # => DiceSet: NerdDice.roll_dice(8)
12
+ # roll_d1000 # => DiceSet: NerdDice.roll_dice(1000)
13
+ # total_d20 # => Integer NerdDice.total_dice(20)
14
+ # total_d8 # => Integer NerdDice.total_dice(8)
15
+ # total_d1000 # => Integer NerdDice.total_dice(1000)
16
+ # </tt>
17
+ #
18
+ # `roll_NNdNN` and `total_NNdNN` roll specified quantity of dice
19
+ # <tt>
20
+ # roll_2d20 # => DiceSet: NerdDice.roll_dice(20, 2)
21
+ # roll_3d8 # => DiceSet: NerdDice.roll_dice(8, 3)
22
+ # roll_22d1000 # => DiceSet: NerdDice.roll_dice(1000, 22)
23
+ # total_2d20 # => Integer NerdDice.total_dice(20, 2)
24
+ # total_3d8 # => Integer NerdDice.total_dice(8, 3)
25
+ # total_22d1000 # => Integer NerdDice.total_dice(1000, 22)
26
+ # </tt>
27
+ #
28
+ # Keyword arguments are passed on to `roll_dice`/`total_dice` method
29
+ # <tt>
30
+ # roll_2d20 foreground_color: 'blue' # => DiceSet: NerdDice.roll_dice(20, 2, foreground_color: 'blue')
31
+ # roll_2d20 foreground_color: 'blue' # => DiceSet: NerdDice.roll_dice(20, 2, foreground_color: 'blue')
32
+ # total_d12 randomization_technique: :randomized
33
+ # # => Integer NerdDice.total_dice(12, randomization_technique: :randomized)
34
+ # total_22d1000 randomization_technique: :random_rand
35
+ # # => Integer NerdDice.total_dice(1000, 22, randomization_technique: :random_rand)
36
+ # roll_4d6_with_advantage3 foreground_color: 'blue'
37
+ # # => DiceSet: NerdDice.roll_dice(4, 3, foreground_color: 'blue').highest(3)
38
+ # total_4d6_with_advantage3 randomization_technique: :random_rand
39
+ # # => Integer: NerdDice.roll_dice(4, 3, randomization_technique: :random_rand).highest(3).total
40
+ # </tt>
41
+ #
42
+ # Positive and negative bonuses can be used with `plus` (alias `p`) or `minus` (alias `m`) in DSL
43
+ # <tt>
44
+ # roll_d20_plus6 # => DiceSet: NerdDice.roll_dice(20, bonus: 6)
45
+ # total_3d8_p2 # => Integer: NerdDice.total_dice(8, 3, bonus: 2)
46
+ # total_d20_minus5 # => Integer: NerdDice.total_dice(20, bonus: -6)
47
+ # roll_3d8_m3 # => DiceSet: NerdDice.roll_dice(8, 3, bonus: -3)
48
+ # </tt>
49
+ #
50
+ # Advantage and disadvantage
51
+ # * `_with_advantageN` or `highestN` roll with advantage
52
+ # * `_with_disadvantageN` or `lowestN` roll with disadvantage
53
+ # * Calling `roll_dNN_with_advantage` \(and variants\) rolls 2 dice and keeps one
54
+ # <tt>
55
+ # # equivalent
56
+ # roll_3d8_with_advantage1
57
+ # roll_3d8_highest1
58
+ # # => DiceSet: NerdDice.roll_dice(8, 3).with_advantage(1)
59
+ # # calls roll_dice and total to return an integer
60
+ # total_3d8_with_advantage1
61
+ # total_3d8_highest1
62
+ # # => Integer: NerdDice.roll_dice(8, 3).with_advantage(1).total
63
+ # # rolls two dice in this case
64
+ # # equal to roll_2d20_with_advantage but more natural
65
+ # roll_d20_with_advantage # => DiceSet: NerdDice.roll_dice(20, 2).with_advantage(1)
66
+ # # equal to total_2d20_with_advantage but more natural
67
+ # total_d20_with_advantage # => Integer: NerdDice.roll_dice(20, 2).with_advantage(1).total
68
+ # </tt>
69
+ #
70
+ # Error Handling
71
+ # * If you try to call with a plus and a minus, an Exception is raised
72
+ # * If you call with a bonus and a keyword argument and they don't match, an Exception is raised
73
+ # * Any combination not expressly allowed or matched will call `super`
74
+ # <tt>
75
+ # roll_3d8_plus3_m2 # raise NerdDice::Error
76
+ # roll_3d8_plus3 bonus: 1 # raise NerdDice::Error
77
+ # roll_d20_with_advantage_lowest # will raise NameError using super method_missing
78
+ # total_4d6_lowest3_highest2 # will raise NameError using super method_missing
79
+ module ConvenienceMethods
80
+ DIS = /_(with_disadvantage|lowest)/.freeze
81
+ ADV = /_(with_advantage|highest)/.freeze
82
+ MOD = /(_p(lus)?\d+|_m(inus)?\d+)/.freeze
83
+ OVERALL_REGEXP = /\A(roll|total)_\d*d\d+((#{ADV}|#{DIS})\d*)?#{MOD}?\z/.freeze
84
+
85
+ # Override of method_missing
86
+ # * Attempts to match pattern to the regular expression matching the methods
87
+ # we want to intercept
88
+ # * If the method matches the pattern, it is defined and executed with send
89
+ # * Subsequent calls to the same method name will not hit method_missing
90
+ # * If the method name does not match the regular expression pattern, the default
91
+ # implementation of method_missing is called by invoking super
92
+ def method_missing(method_name, *args, **kwargs, &block)
93
+ # returns false if no match
94
+ if match_pattern_and_delegate(method_name, *args, **kwargs, &block)
95
+ # send the method after defining it
96
+ send(method_name, *args, **kwargs, &block)
97
+ else
98
+ super
99
+ end
100
+ end
101
+
102
+ # Override :respond_to_missing? so that :respond_to? works as expected when the
103
+ # module is included.
104
+ def respond_to_missing?(symbol, include_all)
105
+ symbol.to_s.match?(OVERALL_REGEXP) || super
106
+ end
107
+
108
+ private
109
+
110
+ # Compares the method name to the regular expression patterns for the module
111
+ # * If the pattern matches the convenience method is defined and a truthy value is returned
112
+ # * If the pattern does not match then the method returns false and method_missing invokes `super`
113
+ def match_pattern_and_delegate(method_name, *args, **kwargs, &block)
114
+ case method_name.to_s
115
+ when /\Aroll_\d*d\d+((#{ADV}|#{DIS})\d*)?#{MOD}?\z/o then define_roll_nndnn(method_name, *args, **kwargs,
116
+ &block)
117
+ when /\Atotal_\d*d\d+((#{ADV}|#{DIS})\d*)?#{MOD}?\z/o then define_total_nndnn(method_name, *args, **kwargs,
118
+ &block)
119
+ else
120
+ false
121
+ end
122
+ end
123
+
124
+ # * Evaluates the method name and then uses class_eval and define_method to define the method
125
+ # * Subsequent calls to the method will bypass method_missing
126
+ #
127
+ # Defined method will return a NerdDice::DiceSet
128
+ def define_roll_nndnn(method_name, *_args, **_kwargs)
129
+ sides, number_of_dice, number_to_keep = parse_from_method_name(method_name)
130
+ # defines the method on the class mixing in the module
131
+ (class << self; self; end).class_eval do
132
+ define_method method_name do |*_args, **kwargs|
133
+ modifier = get_modifier_from_method_name!(method_name, kwargs)
134
+ kwargs[:bonus] = modifier if modifier
135
+ # NerdDice::DiceSet object
136
+ dice = NerdDice.roll_dice(sides, number_of_dice, **kwargs)
137
+ # invoke highest or lowest on the DiceSet if applicable
138
+ parse_number_to_keep(dice, method_name, number_to_keep)
139
+ end
140
+ end
141
+ end
142
+
143
+ # The implementation of this is different than the roll_ version because NerdDice.total_dice
144
+ # cannot deal with highest/lowest calls
145
+ # * Evaluates the method name and then uses class_eval and define_method to define the method
146
+ # * Calls :determine_total_method to allow for handling of highest/lowest mechanic
147
+ # * Subsequent calls to the method will bypass method_missing
148
+ #
149
+ # Defined method will return an Integer
150
+ def define_total_nndnn(method_name, *_args, **_kwargs)
151
+ (class << self; self; end).class_eval do
152
+ define_method method_name do |*_args, **kwargs|
153
+ # parse out bonus before calling determine_total_method
154
+ modifier = get_modifier_from_method_name!(method_name, kwargs)
155
+ kwargs[:bonus] = modifier if modifier
156
+ # parse the method and take different actions based on method_name pattern
157
+ determine_total_method(method_name, kwargs)
158
+ end
159
+ end
160
+ end
161
+
162
+ # Determines whether to call NerdDice.total_dice or NerdDice.roll_dice.total based on RegEx pattern
163
+ # * If the method matches the ADV or DIS regular expressions, the method will call :roll_dice and :total
164
+ # * If the method does not match ADV or DIS, the method will call :total_dice (which is faster)
165
+ #
166
+ # Returns Integer irrespective of which methodology is used
167
+ def determine_total_method(method_name, kwargs)
168
+ sides, number_of_dice, number_to_keep = parse_from_method_name(method_name)
169
+ if number_to_keep
170
+ # NerdDice::DiceSet
171
+ dice = NerdDice.roll_dice(sides, number_of_dice, **kwargs)
172
+ # invoke the highest or lowest method to define dice included and then return the total
173
+ parse_number_to_keep(dice, method_name, number_to_keep).total
174
+ else
175
+ NerdDice.total_dice(sides, number_of_dice, **kwargs)
176
+ end
177
+ end
178
+
179
+ # calls a series of parse methods and then returns them as an array so the define_ methods above can
180
+ # use a one-liner to assign the variables
181
+ def parse_from_method_name(method_name)
182
+ sides = get_sides_from_method_name(method_name)
183
+ number_of_dice = get_number_of_dice_from_method_name(method_name)
184
+ number_to_keep = get_number_to_keep_from_method_name(method_name, number_of_dice)
185
+ [sides, number_of_dice, number_to_keep]
186
+ end
187
+
188
+ # parses out the Integer value of number of sides from the method name
189
+ # will only ever get called if the pattern matches so no need for guard against nil
190
+ def get_sides_from_method_name(method_name)
191
+ method_name.to_s.match(/d\d+/).to_s[1..].to_i
192
+ end
193
+
194
+ # parses the number of dice from the method name and returns the applicable Integer value
195
+ # * If number of dice are specified in the method name, that value is used
196
+ # * If number of dice are not specified and ADV or DIS match, the number is set to 2
197
+ # * If number of dice are not specified and no ADV or DIS match, the number is set to 1
198
+ def get_number_of_dice_from_method_name(method_name)
199
+ match_data = method_name.to_s.match(/_\d+d/)
200
+ default = method_name.to_s.match?(/_d\d+((#{ADV}|#{DIS})\d*)/o) ? 2 : 1
201
+ match_data ? match_data.to_s[1...-1].to_i : default
202
+ end
203
+
204
+ # parses out the modifier from the method name
205
+ # * Input must be a valid MatchData object matching the MOD regular expression
206
+ # * Does not handle nil
207
+ # * Only called from get_modifier_from_method_name!
208
+ def get_modifier_from_match_data(match_data)
209
+ if match_data.to_s.match?(/_p(lus)?\d+/)
210
+ match_data.to_s.match(/_p(lus)?\d+/).to_s.match(/\d+/).to_s.to_i
211
+ else
212
+ match_data.to_s.match(/_m(inus)?\d+/).to_s.match(/\d+/).to_s.to_i * -1
213
+ end
214
+ end
215
+
216
+ # Parses the method name to determine if the modifier pattern is present
217
+ # * Returns nil if method name does not match pattern
218
+ # * Calls get_modifier_from_match_data to get the Integer value of the modifier
219
+ # * Calls check_bonus_integrity! to ensure that the parsed modifier matches the
220
+ # keyword argument if present (which will raise an error if they don't match)
221
+ # * Returns the Integer value of the modifier
222
+ def get_modifier_from_method_name!(method_name, kwargs)
223
+ match_data = method_name.to_s.match(MOD)
224
+
225
+ return nil unless match_data
226
+
227
+ modifier = get_modifier_from_match_data(match_data)
228
+
229
+ # will raise error if mismatch
230
+ check_bonus_integrity!(kwargs, modifier)
231
+ modifier
232
+ end
233
+
234
+ # Determines whether the highest/lowest/with_advantage/with_disadvantage pattern is present in the
235
+ # method name and takes appropriate action
236
+ # * Returns nil if no match
237
+ # * Returns Integer value of number to keep otherwise (irrespective of highest/lowest)
238
+ # * Takes the number to keep from the method name if specified
239
+ # * Returns 1 if number to keep not specified and number of dice equals 1
240
+ # * Returns number_of_dice -1 if number to keep not specified and more than one die
241
+ def get_number_to_keep_from_method_name(method_name, number_of_dice)
242
+ return nil unless method_name.to_s.match?(/(#{ADV}|#{DIS})/o)
243
+
244
+ specified_number = method_name.to_s.match(/(#{ADV}|#{DIS})\d+/o)
245
+
246
+ # set the default to 1 if only one die or number minus 1 if multiple
247
+ default = number_of_dice == 1 ? 1 : number_of_dice - 1
248
+
249
+ # return pattern match if one exists or number of dice -1 if no pattern match
250
+ specified_number ? specified_number.to_s.match(/\d+/).to_s.to_i : default
251
+ end
252
+
253
+ # Checks that there is not a mismatch between the modifier specified in the method name and the
254
+ # modifier specified in the keyword arguments if one is specified
255
+ # * Raises a NerdDice::Error if there is a mismatch
256
+ # * Returns true if no keyword argument bonus
257
+ # * Returns true if keyword argument bonus and method name modifier are consistent
258
+ def check_bonus_integrity!(kwargs, bonus)
259
+ bonus_error_message = "Bonus integrity failure: "
260
+ bonus_error_message += "Modifier specified in keyword arguments was #{kwargs[:bonus]}. "
261
+ bonus_error_message += "Modifier specified in method_name was #{bonus}. "
262
+ raise NerdDice::Error, bonus_error_message if kwargs && kwargs[:bonus] && kwargs[:bonus].to_i != bonus
263
+
264
+ true
265
+ end
266
+
267
+ # Parses number to keep on a NerdDice::DiceSet
268
+ # * If number_to_keep falsey, just return the DiceSet object
269
+ # * If number_to_keep matches ADV pattern return DiceSet with highest called
270
+ # * If number_to_keep matches DIS pattern return DiceSet with lowest called
271
+ def parse_number_to_keep(dice, method_name, number_to_keep)
272
+ return dice unless number_to_keep
273
+
274
+ # use match against ADV to determine truth value of ternary expression
275
+ match_data = method_name.to_s.match(ADV)
276
+ match_data ? dice.highest(number_to_keep) : dice.lowest(number_to_keep)
277
+ end
278
+ end
279
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NerdDice
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/nerd_dice.rb CHANGED
@@ -7,6 +7,7 @@ require "nerd_dice/die"
7
7
  require "nerd_dice/dice_set"
8
8
  require "nerd_dice/class_methods"
9
9
  require "securerandom"
10
+ require "nerd_dice/convenience_methods"
10
11
  # Nerd dice allows you to roll polyhedral dice and add bonuses as you would in
11
12
  # a tabletop roleplaying game. You can choose to roll multiple dice and keep a
12
13
  # specified number of dice such as rolling 4d6 and dropping the lowest for
@@ -23,4 +24,6 @@ module NerdDice
23
24
  RANDOMIZATION_TECHNIQUES = %i[securerandom random_rand random_object randomized].freeze
24
25
  ABILITY_SCORE_KEYS = %i[ability_score_array_size ability_score_number_of_sides ability_score_dice_rolled
25
26
  ability_score_dice_kept].freeze
27
+
28
+ extend ConvenienceMethods
26
29
  end
data/nerd_dice.gemspec CHANGED
@@ -43,13 +43,13 @@ Gem::Specification.new do |spec|
43
43
  spec.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $PROGRAM_NAME.end_with?("gem")
44
44
 
45
45
  # Dependencies
46
- spec.add_dependency "securerandom", "~> 0.1", ">= 0.1.0"
46
+ spec.add_dependency "securerandom", "~> 0.1", ">= 0.1.1"
47
47
 
48
48
  # Development Dependencies
49
- spec.add_development_dependency "coveralls_reborn", "~> 0.22.0"
50
- spec.add_development_dependency "rubocop", "~> 1.20", ">= 1.20.0"
49
+ spec.add_development_dependency "coveralls_reborn", "~> 0.23.0"
50
+ spec.add_development_dependency "rubocop", "~> 1.22", ">= 1.22.2"
51
51
  spec.add_development_dependency "rubocop-performance", "~> 1.11", ">= 1.11.5"
52
52
  spec.add_development_dependency "rubocop-rake", "~> 0.6", ">= 0.6.0"
53
- spec.add_development_dependency "rubocop-rspec", "~> 2.4", ">= 2.4.0"
53
+ spec.add_development_dependency "rubocop-rspec", "~> 2.5", ">= 2.5.0"
54
54
  spec.add_development_dependency "simplecov-lcov", "~> 0.8.0"
55
55
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nerd_dice
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Duchemin
@@ -35,7 +35,7 @@ cert_chain:
35
35
  WQ4faXJSevxT+x9TgyUNJINPkz/KqreClzdL83cwxPzFFQto7zF6zMCsj0slqJjW
36
36
  EQ==
37
37
  -----END CERTIFICATE-----
38
- date: 2021-09-11 00:00:00.000000000 Z
38
+ date: 2021-10-23 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: securerandom
@@ -46,7 +46,7 @@ dependencies:
46
46
  version: '0.1'
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: 0.1.0
49
+ version: 0.1.1
50
50
  type: :runtime
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
@@ -56,41 +56,41 @@ dependencies:
56
56
  version: '0.1'
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
- version: 0.1.0
59
+ version: 0.1.1
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: coveralls_reborn
62
62
  requirement: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
- version: 0.22.0
66
+ version: 0.23.0
67
67
  type: :development
68
68
  prerelease: false
69
69
  version_requirements: !ruby/object:Gem::Requirement
70
70
  requirements:
71
71
  - - "~>"
72
72
  - !ruby/object:Gem::Version
73
- version: 0.22.0
73
+ version: 0.23.0
74
74
  - !ruby/object:Gem::Dependency
75
75
  name: rubocop
76
76
  requirement: !ruby/object:Gem::Requirement
77
77
  requirements:
78
78
  - - "~>"
79
79
  - !ruby/object:Gem::Version
80
- version: '1.20'
80
+ version: '1.22'
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
- version: 1.20.0
83
+ version: 1.22.2
84
84
  type: :development
85
85
  prerelease: false
86
86
  version_requirements: !ruby/object:Gem::Requirement
87
87
  requirements:
88
88
  - - "~>"
89
89
  - !ruby/object:Gem::Version
90
- version: '1.20'
90
+ version: '1.22'
91
91
  - - ">="
92
92
  - !ruby/object:Gem::Version
93
- version: 1.20.0
93
+ version: 1.22.2
94
94
  - !ruby/object:Gem::Dependency
95
95
  name: rubocop-performance
96
96
  requirement: !ruby/object:Gem::Requirement
@@ -137,20 +137,20 @@ dependencies:
137
137
  requirements:
138
138
  - - "~>"
139
139
  - !ruby/object:Gem::Version
140
- version: '2.4'
140
+ version: '2.5'
141
141
  - - ">="
142
142
  - !ruby/object:Gem::Version
143
- version: 2.4.0
143
+ version: 2.5.0
144
144
  type: :development
145
145
  prerelease: false
146
146
  version_requirements: !ruby/object:Gem::Requirement
147
147
  requirements:
148
148
  - - "~>"
149
149
  - !ruby/object:Gem::Version
150
- version: '2.4'
150
+ version: '2.5'
151
151
  - - ">="
152
152
  - !ruby/object:Gem::Version
153
- version: 2.4.0
153
+ version: 2.5.0
154
154
  - !ruby/object:Gem::Dependency
155
155
  name: simplecov-lcov
156
156
  requirement: !ruby/object:Gem::Requirement
@@ -204,6 +204,8 @@ files:
204
204
  - checksum/nerd_dice-0.1.1.gem.sha512
205
205
  - checksum/nerd_dice-0.2.0.gem.sha256
206
206
  - checksum/nerd_dice-0.2.0.gem.sha512
207
+ - checksum/nerd_dice-0.3.0.gem.sha256
208
+ - checksum/nerd_dice-0.3.0.gem.sha512
207
209
  - lib/nerd_dice.rb
208
210
  - lib/nerd_dice/class_methods.rb
209
211
  - lib/nerd_dice/class_methods/configure.rb
@@ -215,6 +217,7 @@ files:
215
217
  - lib/nerd_dice/class_methods/total_ability_scores.rb
216
218
  - lib/nerd_dice/class_methods/total_dice.rb
217
219
  - lib/nerd_dice/configuration.rb
220
+ - lib/nerd_dice/convenience_methods.rb
218
221
  - lib/nerd_dice/dice_set.rb
219
222
  - lib/nerd_dice/die.rb
220
223
  - lib/nerd_dice/sets_randomization_technique.rb
metadata.gz.sig CHANGED
@@ -1,3 +1,2 @@
1
- C����w
2
- �g4���]T۞~����s�����ul�!��o��D���#����`t��r��r̋8l��������M]�Tu���M���X�Ʌe/���ϧ9��.�>�G��B��'��R��� �۽/r"wa�����P��p�0���pd޼�v�,�N_㼨�����krd��PT������͋)V�c�"#RG�З{��|�nP 9�X�s�p�i���>�?^�6LXQ�.4 �O�ء6怳�`ݣ�D.����ڠ|�S��:a�P��j���"��1ȩF�*V7��
3
- �-�w�r���[ �5�7��,�4���_��}�SJ:
1
+ ����vܕu&��rXsϴ����j]4_�|F���k��Ū�eQl��?n�WC.,E(��;�B�/�(%َWۊ��#sՉ� �~ګj0U6H��� �M9��b/�ơ�H���|
2
+ N&Y{2p�M��4Mյ��V��M!a�Ӛ��9($���!y^bTq�ה��GZ�;������c-�a)����e4��.������agr=0�a�͖=�