nerd_dice 0.3.1 → 0.4.0

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