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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.rubocop.yml +19 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +17 -18
- data/README.md +181 -34
- data/bin/nerd_dice_benchmark +126 -8
- data/checksum/nerd_dice-0.3.0.gem.sha256 +1 -0
- data/checksum/nerd_dice-0.3.0.gem.sha512 +1 -0
- data/lib/nerd_dice/class_methods/harvest_totals.rb +9 -4
- data/lib/nerd_dice/convenience_methods.rb +279 -0
- data/lib/nerd_dice/version.rb +1 -1
- data/lib/nerd_dice.rb +3 -0
- data/nerd_dice.gemspec +4 -4
- data.tar.gz.sig +0 -0
- metadata +17 -14
- metadata.gz.sig +2 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f64389429514a7d0c2a8410b2e27575e5c11b316b50cee41b37c58d8f8af953
|
4
|
+
data.tar.gz: 4d2365e3ce8139d4675695dfe47edf9ef5d52b9cc873beb5c7c314b8955aaa33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/Gemfile.lock
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
nerd_dice (0.
|
5
|
-
securerandom (~> 0.1, >= 0.1.
|
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.
|
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.
|
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 (
|
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.
|
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.
|
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.
|
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.
|
55
|
-
rubocop (~> 1.
|
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.
|
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.
|
71
|
+
unicode-display_width (2.1.0)
|
73
72
|
|
74
73
|
PLATFORMS
|
75
74
|
ruby
|
76
75
|
|
77
76
|
DEPENDENCIES
|
78
|
-
coveralls_reborn (~> 0.
|
77
|
+
coveralls_reborn (~> 0.23.0)
|
79
78
|
nerd_dice!
|
80
|
-
rake (~>
|
81
|
-
rspec (~> 3.
|
82
|
-
rubocop (~> 1.
|
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.
|
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
|
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 =>
|
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.
|
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) # =>
|
74
|
-
NerdDice.roll_dice(4) # =>
|
75
|
-
NerdDice.roll_dice(4).total # =>
|
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) # =>
|
79
|
-
NerdDice.roll_dice(6, 3) # =>
|
80
|
-
NerdDice.roll_dice(6, 3).total # =>
|
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) #
|
84
|
-
NerdDice.roll_dice(20, bonus: 5) #
|
85
|
-
NerdDice.roll_dice(20, bonus: 5).total #
|
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
|
91
|
-
|
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,
|
101
|
-
#
|
102
|
-
|
103
|
-
|
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',
|
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
|
-
|
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
|
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
|
-
|
160
|
-
dice_set.
|
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
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
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
|
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
|
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\.
|
data/bin/nerd_dice_benchmark
CHANGED
@@ -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
|
-
|
28
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
data/lib/nerd_dice/version.rb
CHANGED
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.
|
46
|
+
spec.add_dependency "securerandom", "~> 0.1", ">= 0.1.1"
|
47
47
|
|
48
48
|
# Development Dependencies
|
49
|
-
spec.add_development_dependency "coveralls_reborn", "~> 0.
|
50
|
-
spec.add_development_dependency "rubocop", "~> 1.
|
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.
|
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.
|
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-
|
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.
|
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.
|
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.
|
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.
|
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.
|
80
|
+
version: '1.22'
|
81
81
|
- - ">="
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: 1.
|
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.
|
90
|
+
version: '1.22'
|
91
91
|
- - ">="
|
92
92
|
- !ruby/object:Gem::Version
|
93
|
-
version: 1.
|
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.
|
140
|
+
version: '2.5'
|
141
141
|
- - ">="
|
142
142
|
- !ruby/object:Gem::Version
|
143
|
-
version: 2.
|
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.
|
150
|
+
version: '2.5'
|
151
151
|
- - ">="
|
152
152
|
- !ruby/object:Gem::Version
|
153
|
-
version: 2.
|
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
|
-
|
2
|
-
|
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��.������a�gr=0�a�͖=�
|