nerd_dice 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ version = ARGV[0]
7
7
  built_gem_path = "pkg/nerd_dice-#{version}.gem"
8
8
  checksum = Digest::SHA512.new.hexdigest(File.read(built_gem_path))
9
9
  checksum_path = "checksum/nerd_dice-#{version}.gem.sha512"
10
- File.write(checksum_path, checksum)
10
+ File.open(checksum_path, "w") { |f| f.write(checksum) }
11
11
  sha256 = Digest::SHA256.new.hexdigest(File.read(built_gem_path))
12
12
  checksum_256_path = "checksum/nerd_dice-#{version}.gem.sha256"
13
- File.write(checksum_256_path, sha256)
13
+ File.open(checksum_256_path, "w") { |f| f.write(sha256) }
@@ -16,14 +16,26 @@ 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,
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,
27
39
  roll_ability_scores_randomized: 30.5,
28
40
  total_ability_scores_randomized: 30.5
29
41
  }.freeze
@@ -52,6 +64,8 @@ random_rand_baseline = baselines[0].real
52
64
  securerandom_baseline = baselines[1].real
53
65
 
54
66
  puts "Roll d1000s"
67
+
68
+ puts "total_dice"
55
69
  total_dice_d1000_results = Benchmark.bm do |x|
56
70
  # NerdDice.total_dice securerandom
57
71
  x.report("total_dice_securerandom") do
@@ -84,6 +98,42 @@ 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
 
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"
87
137
  roll_dice_d1000_results = Benchmark.bm do |x|
88
138
  # NerdDice.roll_dice securerandom
89
139
  x.report("roll_dice_securerandom") do
@@ -116,7 +166,41 @@ 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"
203
+ puts "total_dice 3d6"
120
204
  total_dice_3d6_results = Benchmark.bm do |x|
121
205
  # NerdDice.total_dice securerandom
122
206
  x.report("total_dice_securerandom_3d6") do
@@ -149,6 +233,7 @@ 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
 
236
+ puts "roll_dice 3d6"
152
237
  roll_dice_3d6_results = Benchmark.bm do |x|
153
238
  # NerdDice.roll_dice securerandom
154
239
  x.report("roll_dice_securerandom_3d6") do
@@ -181,6 +266,39 @@ 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
 
@@ -1,27 +1,26 @@
1
1
  -----BEGIN CERTIFICATE-----
2
- MIIEhTCCAu2gAwIBAgIBATANBgkqhkiG9w0BAQsFADBEMRYwFAYDVQQDDA1zdGF0
3
- ZWxlc3Njb2RlMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
4
- FgNjb20wHhcNMjMwMjIzMjMyMTQwWhcNMjQwMjIzMjMyMTQwWjBEMRYwFAYDVQQD
5
- DA1zdGF0ZWxlc3Njb2RlMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJ
6
- k/IsZAEZFgNjb20wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCxVzHS
7
- gYszan//tjTSO0z59UO1rUiT5G0//iyhiaYIiuwxhYFbfD+wOCou4M18pQtEb4qx
8
- o67tPfGimBAVak6fGfo8fo1ByHiKCvx3jgOjxNifT9pRFlBSr6ZvyXeu7zA0ddLr
9
- slw92DNqeRlZXqB0mxDtpKWONGc1XhAqEjEP3VL7g7x0xPQShcpXg/OyRPR5vyv8
10
- 66pXdFrXYZGrySfIB6ZOWFV6wGBj603rPdXOeYVeks6hKvw3wb4G1s7tvwTA5MWI
11
- otw6Mp9TaMdms9zTc5A3N58pueKfBJfwkICkdAGJDWC6sIXECoaTDRqVK96RSH/1
12
- 8tEPDoFYpJDOa5byX1j7srwO0B6WOtPxix7gW1wBbEp7eWSQf1k3k9XEh32SRsPq
13
- NJObRfhkzoa9p1tPkVP3nasDTK5gtisolwhb7Vimeup54yKfT/THv2iNEnGTvK1P
14
- sV4vC8nch88lBI1mIecmSh/mwED4Mb1dNtcyuB/+XnSI8vIzXJKAAKaT0eMCAwEA
15
- AaOBgTB/MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBRP42hSa0JV
16
- QGDSobiGTYyfM570hjAiBgNVHREEGzAZgRdzdGF0ZWxlc3Njb2RlQGdtYWlsLmNv
17
- bTAiBgNVHRIEGzAZgRdzdGF0ZWxlc3Njb2RlQGdtYWlsLmNvbTANBgkqhkiG9w0B
18
- AQsFAAOCAYEANtaR6OV7p1IJOsvVgGQzVg88NIOeXrfOaEDUPb6eg4JMOSL0Cvvl
19
- 2F7lB/ykbQcO4Oe7NucuavC7ClyG3c/V5eQ5TtPNWkMbVN9ESVR8wk5SjhiI8L35
20
- MBxJ6YU27eyDmazQJ7eCYcRJkuyWt3KcqgsEh7JyNnKcJ/3rgf1QW0IyJiGsXM1I
21
- SssQ/t7Ia2tVMrVMsvs834v9FRpVbO3dHdCO4t7zQBIADVcj4NqCDV10D6aji/Aa
22
- 35YJHwlkhuZH6AYC45QHt9dW0/OLmbFwoJqW7syrso2PParyMr4YcJwucXViRiL7
23
- l5aVpYdz/RTqdB92Mmud5Hj5zkuEE4CHBh8L8AJC5kZu/YUXXDtuECSMVhg5O84h
24
- QsdcuygyVASmw2aliMAFXfIBDYelduG0XwjdOREN3q4SDTKP+pfBxx6OdD1RfsYF
25
- /9HhtVbKLq34iQftF4oIH66bYDEyG5y4CLKQ8Nq0WDWq50OcaP9KpDiS21BC43SW
26
- t+NX7PDOWx4k
2
+ MIIETTCCArWgAwIBAgIBATANBgkqhkiG9w0BAQsFADAoMSYwJAYDVQQDDB1zdGF0
3
+ ZWxlc3Njb2RlL0RDPWdtYWlsL0RDPWNvbTAeFw0yMDEyMDYyMzQ1NTZaFw0yMTEy
4
+ MDYyMzQ1NTZaMCgxJjAkBgNVBAMMHXN0YXRlbGVzc2NvZGUvREM9Z21haWwvREM9
5
+ Y29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyHIMJi0o3bwBZsx5
6
+ TQ35XByFsdDRsro3T+0NY7EAILtOiU04o9C2NPOp/RQE7BXQgMjGebwp6bT6QvzN
7
+ 6noV4jPL7Fi5pWw08QygG7f+73YUBb+8d8o+3xGrC+UO5h1PZEtVcZwUWUG18QBE
8
+ fbDinQT6P4IDQoZwhfrPCB+aBfUyQp4Ok7oD7MEWqsq9SjrSxqxfk4+oZdXUySe7
9
+ Vi5vnzVQ5uFf56NHwWnNKCzJzmH84mBO5MzHaQpHNzKGJPoUmzLU5RBlCH6YXqBG
10
+ KhXTMUDBWKJmJ3RDry/FpGgJLKu4wzFRYjXla6IjeKozWGuPNNJ+2mesXKhsX7bo
11
+ vVCzRxPEupbEg/0FkJiWpiGlSPOdd6oJiwX8E6rlEeV605xrbOQewkbovHkYTMtG
12
+ +NH+u08x0z4Oj71kmDLwuj812uS0mtrCg2VhiYO0ZCQ4XrwBsBfK+/MtMlR+o6sG
13
+ /zvz/vHVJKaLTQxRp5oGo4QH6HfbOnwzTkXdZnt5AlN31ErJAgMBAAGjgYEwfzAJ
14
+ BgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUC7seYydsGO6O1qT4nVVD
15
+ G/LkiHYwIgYDVR0RBBswGYEXc3RhdGVsZXNzY29kZUBnbWFpbC5jb20wIgYDVR0S
16
+ BBswGYEXc3RhdGVsZXNzY29kZUBnbWFpbC5jb20wDQYJKoZIhvcNAQELBQADggGB
17
+ ADPRFRB1cjqdcE2O0jtqiDRmrR62uEYBiUbkRPVhyoEp/cK0TVhAs9mGWAyCWu0M
18
+ LewUeqNTUvQ9MgvagcKcnxa2RTjdrP3nGnwpStMr9bm3ArNJEzvWEs0Eusk9y73x
19
+ fjy0qH2pw5WPfWcKYlDehMXqOP+a4udYsz0YSNiI8qEfkDCSqTJN11d5kSjVjwGB
20
+ xkauxDT68j1JZRjPmQl3dl+DCgxkoziWX2mFTPLfGg5vZ0t6gmhdUtLvJtNIo0IX
21
+ 477E5UjmE1+rULQp/fsH6n5+H+t2eCED41ST+gkKbaQBUfIuUaCmdHz9sJaIIBw2
22
+ 6ordFa1nrLV4w5Uf6qYFnWVhIWX4GToyZSPO2s0DPYp3PWFJ4VtzKa2vp1TR5ZEA
23
+ dkij2eQ9M8bzWWmW+A7RNaI0CzLl967bKGBSaMVCsZGBarggWD8UwJnBhTuOPZGR
24
+ WQ4faXJSevxT+x9TgyUNJINPkz/KqreClzdL83cwxPzFFQto7zF6zMCsj0slqJjW
25
+ EQ==
27
26
  -----END CERTIFICATE-----
@@ -0,0 +1,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.1"
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
@@ -28,7 +28,6 @@ Gem::Specification.new do |spec|
28
28
  spec.metadata["bug_tracker_uri"] = "https://github.com/statelesscode/nerd_dice/issues"
29
29
  spec.metadata["documentation_uri"] = "https://github.com/statelesscode/nerd_dice/README.md"
30
30
  spec.metadata["github_repo"] = "https://github.com/statelesscode/nerd_dice"
31
- spec.metadata["rubygems_mfa_required"] = "true"
32
31
 
33
32
  # Specify which files should be added to the gem when it is released.
34
33
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -44,13 +43,13 @@ Gem::Specification.new do |spec|
44
43
  spec.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $PROGRAM_NAME.end_with?("gem")
45
44
 
46
45
  # Dependencies
47
- spec.add_dependency "securerandom", "~> 0.2", ">= 0.2.2"
46
+ spec.add_dependency "securerandom", "~> 0.1", ">= 0.1.1"
48
47
 
49
48
  # Development Dependencies
50
- spec.add_development_dependency "coveralls_reborn", "~> 0.27.0"
51
- spec.add_development_dependency "rubocop", "~> 1.46", ">= 1.46.0"
52
- spec.add_development_dependency "rubocop-performance", "~> 1.16", ">= 1.16.0"
49
+ spec.add_development_dependency "coveralls_reborn", "~> 0.23.0"
50
+ spec.add_development_dependency "rubocop", "~> 1.22", ">= 1.22.2"
51
+ spec.add_development_dependency "rubocop-performance", "~> 1.11", ">= 1.11.5"
53
52
  spec.add_development_dependency "rubocop-rake", "~> 0.6", ">= 0.6.0"
54
- spec.add_development_dependency "rubocop-rspec", "~> 2.18", ">= 2.18.1"
53
+ spec.add_development_dependency "rubocop-rspec", "~> 2.5", ">= 2.5.0"
55
54
  spec.add_development_dependency "simplecov-lcov", "~> 0.8.0"
56
55
  end
data.tar.gz.sig CHANGED
Binary file