nerd_dice 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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�͖=�