nerd_dice 0.2.0 → 0.3.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.
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "benchmark"
6
+ require "nerd_dice"
7
+
8
+ n = 50_000
9
+
10
+ RATIOS = {
11
+ total_dice_securerandom: 2.1,
12
+ total_dice_random_rand: 11.1,
13
+ total_dice_random_object: 13.0,
14
+ total_dice_randomized: 5.5,
15
+ total_dice_securerandom_3d6: 5.5,
16
+ total_dice_random_rand_3d6: 30.0,
17
+ total_dice_random_object_3d6: 25.5,
18
+ total_dice_randomized_3d6: 15.5,
19
+ roll_dice_securerandom: 4.0,
20
+ roll_dice_random_rand: 42.0,
21
+ roll_dice_random_object: 44.0,
22
+ roll_dice_randomized: 14.5,
23
+ roll_dice_securerandom_3d6: 13.0,
24
+ roll_dice_random_rand_3d6: 79.0,
25
+ roll_dice_random_object_3d6: 86.0,
26
+ roll_dice_randomized_3d6: 26.5,
27
+ roll_ability_scores_randomized: 26.5,
28
+ total_ability_scores_randomized: 26.5
29
+ }.freeze
30
+
31
+ def check_against_baseline!(baseline_value, test_value)
32
+ ratio = RATIOS[test_value.label.to_sym]
33
+ error_message = "Failed benchmark for #{test_value.label}. "
34
+ error_message += "Allowed ratio was #{ratio} actual ratio was #{test_value.real / baseline_value}"
35
+ raise NerdDice::Error, error_message if test_value.real > baseline_value * ratio
36
+ end
37
+
38
+ puts "Set baseline"
39
+ baselines = Benchmark.bmbm do |x|
40
+ # Random.rand()
41
+ x.report("Random.rand") do # standard rand()
42
+ n.times { Random.rand(1000) }
43
+ end
44
+
45
+ # SecureRandom.rand()
46
+ x.report("Sec.rand") do
47
+ n.times { SecureRandom.rand(1000) }
48
+ end
49
+ end
50
+
51
+ random_rand_baseline = baselines[0].real
52
+ securerandom_baseline = baselines[1].real
53
+
54
+ puts "Roll d1000s"
55
+ total_dice_d1000_results = Benchmark.bmbm do |x|
56
+ # NerdDice.total_dice securerandom
57
+ x.report("total_dice_securerandom") do
58
+ NerdDice.configuration.randomization_technique = :securerandom
59
+ n.times { NerdDice.total_dice(1000) }
60
+ end
61
+
62
+ x.report("total_dice_random_rand") do
63
+ NerdDice.configuration.randomization_technique = :random_rand
64
+ n.times { NerdDice.total_dice(1000) }
65
+ end
66
+
67
+ x.report("total_dice_random_object") do
68
+ NerdDice.configuration.randomization_technique = :random_object
69
+ n.times { NerdDice.total_dice(1000) }
70
+ end
71
+
72
+ x.report("total_dice_randomized") do
73
+ NerdDice.configuration.randomization_technique = :randomized
74
+ n.times { NerdDice.total_dice(1000) }
75
+ end
76
+ end
77
+
78
+ total_dice_securerandom = total_dice_d1000_results[0]
79
+ check_against_baseline! securerandom_baseline, total_dice_securerandom
80
+ total_dice_random_rand = total_dice_d1000_results[1]
81
+ check_against_baseline! random_rand_baseline, total_dice_random_rand
82
+ total_dice_random_object = total_dice_d1000_results[2]
83
+ check_against_baseline! random_rand_baseline, total_dice_random_object
84
+ total_dice_randomized = total_dice_d1000_results[3]
85
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), total_dice_randomized
86
+
87
+ roll_dice_d1000_results = Benchmark.bmbm do |x|
88
+ # NerdDice.roll_dice securerandom
89
+ x.report("roll_dice_securerandom") do
90
+ NerdDice.configuration.randomization_technique = :securerandom
91
+ n.times { NerdDice.roll_dice(1000) }
92
+ end
93
+
94
+ x.report("roll_dice_random_rand") do
95
+ NerdDice.configuration.randomization_technique = :random_rand
96
+ n.times { NerdDice.roll_dice(1000) }
97
+ end
98
+
99
+ x.report("roll_dice_random_object") do
100
+ NerdDice.configuration.randomization_technique = :random_object
101
+ n.times { NerdDice.roll_dice(1000) }
102
+ end
103
+
104
+ x.report("roll_dice_randomized") do
105
+ NerdDice.configuration.randomization_technique = :randomized
106
+ n.times { NerdDice.roll_dice(1000) }
107
+ end
108
+ end
109
+
110
+ roll_dice_securerandom = roll_dice_d1000_results[0]
111
+ check_against_baseline! securerandom_baseline, roll_dice_securerandom
112
+ roll_dice_random_rand = roll_dice_d1000_results[1]
113
+ check_against_baseline! random_rand_baseline, roll_dice_random_rand
114
+ roll_dice_random_object = roll_dice_d1000_results[2]
115
+ check_against_baseline! random_rand_baseline, roll_dice_random_object
116
+ roll_dice_randomized = roll_dice_d1000_results[3]
117
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), roll_dice_randomized
118
+
119
+ puts "Roll 3d6"
120
+ total_dice_3d6_results = Benchmark.bmbm do |x|
121
+ # NerdDice.total_dice securerandom
122
+ x.report("total_dice_securerandom_3d6") do
123
+ NerdDice.configuration.randomization_technique = :securerandom
124
+ n.times { NerdDice.total_dice(6, 3) }
125
+ end
126
+
127
+ x.report("total_dice_random_rand_3d6") do
128
+ NerdDice.configuration.randomization_technique = :random_rand
129
+ n.times { NerdDice.total_dice(6, 3) }
130
+ end
131
+
132
+ x.report("total_dice_random_object_3d6") do
133
+ NerdDice.configuration.randomization_technique = :random_object
134
+ n.times { NerdDice.total_dice(6, 3) }
135
+ end
136
+
137
+ x.report("total_dice_randomized_3d6") do
138
+ NerdDice.configuration.randomization_technique = :randomized
139
+ n.times { NerdDice.total_dice(6, 3) }
140
+ end
141
+ end
142
+
143
+ total_dice_3d6_securerandom = total_dice_3d6_results[0]
144
+ check_against_baseline! securerandom_baseline, total_dice_3d6_securerandom
145
+ total_dice_3d6_random_rand = total_dice_3d6_results[1]
146
+ check_against_baseline! random_rand_baseline, total_dice_3d6_random_rand
147
+ total_dice_3d6_random_object = total_dice_3d6_results[2]
148
+ check_against_baseline! random_rand_baseline, total_dice_3d6_random_object
149
+ total_dice_3d6_randomized = total_dice_3d6_results[3]
150
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), total_dice_3d6_randomized
151
+
152
+ roll_dice_3d6_results = Benchmark.bmbm do |x|
153
+ # NerdDice.roll_dice securerandom
154
+ x.report("roll_dice_securerandom_3d6") do
155
+ NerdDice.configuration.randomization_technique = :securerandom
156
+ n.times { NerdDice.roll_dice(6, 3) }
157
+ end
158
+
159
+ x.report("roll_dice_random_rand_3d6") do
160
+ NerdDice.configuration.randomization_technique = :random_rand
161
+ n.times { NerdDice.roll_dice(6, 3) }
162
+ end
163
+
164
+ x.report("roll_dice_random_object_3d6") do
165
+ NerdDice.configuration.randomization_technique = :random_object
166
+ n.times { NerdDice.roll_dice(6, 3) }
167
+ end
168
+
169
+ x.report("roll_dice_randomized_3d6") do
170
+ NerdDice.configuration.randomization_technique = :randomized
171
+ n.times { NerdDice.roll_dice(6, 3) }
172
+ end
173
+ end
174
+
175
+ roll_dice_3d6_securerandom = roll_dice_3d6_results[0]
176
+ check_against_baseline! securerandom_baseline, roll_dice_3d6_securerandom
177
+ roll_dice_3d6_random_rand = roll_dice_3d6_results[1]
178
+ check_against_baseline! random_rand_baseline, roll_dice_3d6_random_rand
179
+ roll_dice_3d6_random_object = roll_dice_3d6_results[2]
180
+ check_against_baseline! random_rand_baseline, roll_dice_3d6_random_object
181
+ roll_dice_3d6_randomized = roll_dice_3d6_results[3]
182
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), roll_dice_3d6_randomized
183
+
184
+ puts "Setting n down to 5,000 due to more intensive methods"
185
+ n = 5_000
186
+
187
+ puts "Roll and total ability scores"
188
+ roll_ability_scores_results = Benchmark.bmbm do |x|
189
+ x.report("roll_ability_scores_randomized") do
190
+ NerdDice.configuration.randomization_technique = :randomized
191
+ n.times { NerdDice.roll_ability_scores }
192
+ end
193
+
194
+ x.report("total_ability_scores_randomized") do
195
+ NerdDice.configuration.randomization_technique = :randomized
196
+ n.times { NerdDice.total_ability_scores }
197
+ end
198
+ end
199
+
200
+ roll_ability_scores_randomized = roll_ability_scores_results[0]
201
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)), roll_ability_scores_randomized
202
+ total_ability_scores_randomized = roll_ability_scores_results[1]
203
+ check_against_baseline! ((random_rand_baseline * 0.75) + (securerandom_baseline * 0.25)),
204
+ total_ability_scores_randomized
@@ -0,0 +1 @@
1
+ 0a2d4765e24c21fecf99229b0cc4fad806ca256d2187c45c442b25771af1b9cc
@@ -0,0 +1 @@
1
+ e345190f870eabd2a4fd6993f1a1d70d008ef8e6464767ba5697a46f7c4d6912c78a18039cec06c18dd6f0ee9f425453c078f780d36ccf65043ec3f7fba790a2
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ #############################
4
+ # Configuration class methods
5
+ #############################
6
+ # Usage:
7
+ # NerdDice.configure takes a block and yields the NerdDice::Configuration
8
+ # properties:
9
+ # If you wanted to configure several properties in a block:
10
+ # <tt>
11
+ # NerdDice.configure do |config|
12
+ # config.randomization_technique = :randomized
13
+ # config.die_background_color = "#FF0000"
14
+ # end
15
+ # </tt>
16
+ #
17
+ # NerdDice.configuration returns the NerdDice::Configuration object and lets you
18
+ # set properties on the NerdDice::Configuration object without using a block:
19
+ # <tt>
20
+ # config = NerdDice.configuration
21
+ # config.randomization_technique = :randomized
22
+ # config.die_background_color = "#FF0000"
23
+ # </tt>
24
+ module NerdDice
25
+ class << self
26
+ ############################
27
+ # configure class method
28
+ ############################
29
+ # Arguments: None
30
+ # Expects and yields to a block where configuration is specified.
31
+ # See README and NerdDice::Configuration class for config options
32
+ # Return (NerdDice::Configuration) the Configuration object tied to the
33
+ # @configuration class instance variable
34
+ def configure
35
+ yield configuration
36
+ configuration
37
+ end
38
+
39
+ ############################
40
+ # configuration class method
41
+ ############################
42
+ # Arguments: None
43
+ # Provides the lazy-loaded class instance variable @configuration
44
+ # Return (NerdDice::Configuration) the Configuration object tied to the
45
+ # @configuration class instance variable
46
+ def configuration
47
+ @configuration ||= Configuration.new
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ ###############################
4
+ # execute_die_roll class method
5
+ ###############################
6
+ # Usage:
7
+ # If you wanted to execute a single d4 die roll without a Die object, you would execute:
8
+ # <tt>NerdDice.execute_die_roll(4)</tt>
9
+ #
10
+ # If you wanted to execute a die roll with a different randomization technique
11
+ # than the one in NerdDice.configuration, you can supply an optional second argument
12
+ # <tt>NerdDice.execute_die_roll(4, :randomized)</tt>
13
+ module NerdDice
14
+ class << self
15
+ # Arguments:
16
+ # number_of_sides (Integer) => the number of sides of the die to roll
17
+ # using_generator (Symbol) => must be one of the symbols in
18
+ # RANDOMIZATION_TECHNIQUES or nil
19
+ #
20
+ # Return (Integer) => Value of the single die rolled
21
+ def execute_die_roll(number_of_sides, using_generator = nil)
22
+ @count_since_last_refresh ||= 0
23
+ gen = get_number_generator(using_generator)
24
+ result = gen.rand(number_of_sides) + 1
25
+ increment_and_evalutate_refresh_seed
26
+ result
27
+ end
28
+
29
+ private
30
+
31
+ def get_number_generator(using_generator = nil)
32
+ using_generator ||= configuration.randomization_technique
33
+ case using_generator
34
+ when :securerandom then SecureRandom
35
+ when :random_rand then Random
36
+ when :random_object then @random_object ||= Random.new
37
+ when :randomized then random_generator
38
+ else raise ArgumentError, "Unrecognized generator. Must be one of #{RANDOMIZATION_TECHNIQUES.join(', ')}"
39
+ end
40
+ end
41
+
42
+ def random_generator
43
+ gen = RANDOMIZATION_TECHNIQUES.reject { |el| el == :randomized }.sample
44
+ get_number_generator(gen)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ ############################
4
+ # harvest_totals method
5
+ ############################
6
+ # Usage:
7
+ # This method will take any collection of objects where each element responds to
8
+ # :total and return an array of the results of the total method.
9
+ #
10
+ # Example
11
+ # <tt>
12
+ # ability_score_array = NerdDice.roll_ability_scores
13
+ # => Array of 6 DiceSet objects
14
+ # totals_array = NerdDice.harvest_totals(totals_array)
15
+ # => [15, 14, 13, 12, 10, 8]
16
+ # # yes, it just happened to be the standard array by amazing coincidence
17
+ # </tt>
18
+ module NerdDice
19
+ class << self
20
+ # Arguments:
21
+ # collection (Enumerable) a collection where each element responds to total
22
+ #
23
+ # Return (Array) => Data type of each element will be whatever is returned by total method
24
+ def harvest_totals(collection)
25
+ collection.map(&:total)
26
+ rescue NoMethodError => e
27
+ specific_message =
28
+ case e.message
29
+ when /`total'/ then "Each element must respond to :total."
30
+ when /`map'/ then "Argument must respond to :map."
31
+ end
32
+ specific_message ? raise(ArgumentError, "You must provide a valid collection. #{specific_message}") : raise
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ ############################
4
+ # refresh_seed! class method
5
+ ############################
6
+ # Usage:
7
+ # NerdDice.refresh_seed! by default will refresh the seed for the generator
8
+ # configured in NerdDice.configuration. It can also be used with arguments
9
+ # to set a particular seed for use with deterministic testing. It sets
10
+ # the count_since_last_refresh to 0 whenever called.
11
+ #
12
+ # It cannot refresh or manipulate the seed for SecureRandom
13
+ #
14
+ # <tt>NerdDice.refresh_seed!</tt>
15
+ #
16
+ # With options
17
+ # <tt>
18
+ # previous_seed_data = NerdDice.refresh_seed!(
19
+ # randomization_technique: :randomized,
20
+ # random_rand_seed: 1337,
21
+ # random_object_seed: 24601
22
+ # )
23
+ # </tt>
24
+ module NerdDice
25
+ class << self
26
+ # Options: (none required)
27
+ # randomization_technique (Symbol) => must be one of the symbols in
28
+ # RANDOMIZATION_TECHNIQUES if specified
29
+ # random_rand_seed (Integer) => Seed to set for Random
30
+ # random_object_seed (Integer) => Seed to set for new Random object
31
+ # Return (Hash or nil) => Previous values of generator seeds that were refreshed
32
+ def refresh_seed!(**opts)
33
+ technique, random_rand_new_seed, random_object_new_seed = parse_refresh_options(opts)
34
+ @count_since_last_refresh = 0
35
+ return nil if technique == :securerandom
36
+
37
+ reset_appropriate_seeds!(technique, random_rand_new_seed, random_object_new_seed)
38
+ end
39
+
40
+ private
41
+
42
+ def parse_refresh_options(opts)
43
+ [
44
+ opts[:randomization_technique] || configuration.randomization_technique,
45
+ opts[:random_rand_seed],
46
+ opts[:random_object_seed]
47
+ ]
48
+ end
49
+
50
+ # rubocop:disable Metrics/MethodLength
51
+ def reset_appropriate_seeds!(technique, random_rand_new_seed, random_object_new_seed)
52
+ return_hash = {}
53
+ case technique
54
+ when :random_rand
55
+ return_hash[:random_rand_prior_seed] = refresh_random_rand_seed!(random_rand_new_seed)
56
+ when :random_object
57
+ return_hash[:random_object_prior_seed] = refresh_random_object_seed!(random_object_new_seed)
58
+ when :randomized
59
+ return_hash[:random_rand_prior_seed] = refresh_random_rand_seed!(random_rand_new_seed)
60
+ return_hash[:random_object_prior_seed] = refresh_random_object_seed!(random_object_new_seed)
61
+ end
62
+ return_hash
63
+ end
64
+ # rubocop:enable Metrics/MethodLength
65
+
66
+ def refresh_random_rand_seed!(new_seed)
67
+ new_seed ? Random.srand(new_seed) : Random.srand
68
+ end
69
+
70
+ def refresh_random_object_seed!(new_seed)
71
+ old_seed = @random_object&.seed
72
+ @random_object = new_seed ? Random.new(new_seed) : Random.new
73
+ old_seed
74
+ end
75
+
76
+ def increment_and_evalutate_refresh_seed
77
+ @count_since_last_refresh += 1
78
+ return unless configuration.refresh_seed_interval
79
+
80
+ refresh_seed! if @count_since_last_refresh >= configuration.refresh_seed_interval
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ ############################
4
+ # roll_ability_scores method
5
+ ############################
6
+ # Usage:
7
+ # If you wanted to get an array of DiceSet objects with your ability scores and
8
+ # default configuration you would execute:
9
+ # <tt>ability_score_array = NerdDice.roll_ability_scores</tt>
10
+ #
11
+ # If you wanted to specify configuration for the current operation without
12
+ # modifying the NerdDice.configuration, you can supply options for both the
13
+ # ability_score configuration and the properties of the DiceSet objects returned.
14
+ # Properties are specified in the method comment
15
+ # <tt>
16
+ # ability_score_array = NerdDice.roll_ability_scores(
17
+ # ability_score_array_size: 7,
18
+ # ability_score_number_of_sides: 8,
19
+ # ability_score_dice_rolled: 5,
20
+ # ability_score_dice_kept: 4,
21
+ # randomization_technique: :randomized,
22
+ # foreground_color: "#FF0000",
23
+ # background_color: "#FFFFFF"
24
+ # )
25
+ # </tt>
26
+ module NerdDice
27
+ class << self
28
+ # Arguments:
29
+ # opts (options Hash, DEFAULT: {}) any options you wish to include
30
+ # ABILITY SCORE OPTIONS
31
+ # :ability_score_array_size DEFAULT NerdDice.configuration.ability_score_array_size
32
+ # :ability_score_number_of_sides DEFAULT NerdDice.configuration.ability_score_number_of_sides
33
+ # :ability_score_dice_rolled DEFAULT NerdDice.configuration.ability_score_dice_rolled
34
+ # :ability_score_dice_kept DEFAULT NerdDice.configuration.ability_score_dice_kept
35
+ #
36
+ # DICE SET OPTIONS
37
+ # :randomization_technique (Symbol) => must be one of the symbols in
38
+ # RANDOMIZATION_TECHNIQUES or nil
39
+ # :foreground_color (String) => should resolve to a valid CSS color (format flexible)
40
+ # :background_color (String) => should resolve to a valid CSS color (format flexible)
41
+ #
42
+ # Return (Array of NerdDice::DiceSet) => One NerdDice::DiceSet element for each ability score
43
+ # rubocop:disable Metrics/MethodLength
44
+ def roll_ability_scores(**opts)
45
+ dice_opts = opts.reject { |key, _value| key.to_s.match?(/\Aability_score_[a-z_]+\z/) }
46
+ ability_score_options = interpret_ability_score_options(opts)
47
+ ability_score_array = []
48
+ ability_score_options[:ability_score_array_size].times do
49
+ ability_score_array << roll_dice(
50
+ ability_score_options[:ability_score_number_of_sides],
51
+ ability_score_options[:ability_score_dice_rolled],
52
+ **dice_opts
53
+ ).highest(
54
+ ability_score_options[:ability_score_dice_kept]
55
+ )
56
+ end
57
+ ability_score_array
58
+ end
59
+ # rubocop:enable Metrics/MethodLength
60
+
61
+ private
62
+
63
+ def interpret_ability_score_options(opts)
64
+ return_hash = {}
65
+ ABILITY_SCORE_KEYS.each { |key| return_hash[key] = parse_ability_score_option(opts, key) }
66
+ return_hash
67
+ end
68
+
69
+ def parse_ability_score_option(option_hash, option_key)
70
+ option_hash[option_key] || configuration.send(option_key.to_s)
71
+ end
72
+ end
73
+ end