games_dice 0.3.12 → 0.4.2
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.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +38 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +53 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +18 -0
- data/Gemfile +12 -0
- data/README.md +11 -33
- data/Rakefile +10 -23
- data/ext/games_dice/extconf.rb +4 -22
- data/ext/games_dice/games_dice.c +1 -1
- data/ext/games_dice/probabilities.c +128 -9
- data/ext/games_dice/probabilities.h +1 -1
- data/games_dice.gemspec +22 -36
- data/lib/games_dice/bunch.rb +203 -247
- data/lib/games_dice/bunch_helpers.rb +68 -0
- data/lib/games_dice/complex_die.rb +151 -270
- data/lib/games_dice/complex_die_helpers.rb +260 -60
- data/lib/games_dice/constants.rb +10 -10
- data/lib/games_dice/dice.rb +153 -143
- data/lib/games_dice/die.rb +101 -97
- data/lib/games_dice/die_result.rb +206 -189
- data/lib/games_dice/map_rule.rb +72 -70
- data/lib/games_dice/marshal.rb +41 -13
- data/lib/games_dice/parser.rb +245 -218
- data/lib/games_dice/reroll_rule.rb +76 -77
- data/lib/games_dice/version.rb +4 -1
- data/lib/games_dice.rb +23 -25
- data/spec/bunch_spec.rb +399 -420
- data/spec/complex_die_spec.rb +314 -305
- data/spec/dice_spec.rb +33 -34
- data/spec/die_result_spec.rb +174 -181
- data/spec/die_spec.rb +81 -81
- data/spec/helpers.rb +25 -25
- data/spec/map_rule_spec.rb +40 -44
- data/spec/parser_spec.rb +106 -82
- data/spec/probability_spec.rb +522 -526
- data/spec/readme_spec.rb +410 -390
- data/spec/reroll_rule_spec.rb +40 -44
- metadata +17 -129
- data/.travis.yml +0 -14
- data/lib/games_dice/prob_helpers.rb +0 -259
- data/lib/games_dice/probabilities.rb +0 -244
data/games_dice.gemspec
CHANGED
|
@@ -1,46 +1,32 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
6
|
require 'games_dice/version'
|
|
5
7
|
|
|
6
8
|
Gem::Specification.new do |gem|
|
|
7
|
-
gem.name =
|
|
9
|
+
gem.name = 'games_dice'
|
|
8
10
|
gem.version = GamesDice::VERSION
|
|
9
|
-
gem.authors = [
|
|
10
|
-
gem.email = [
|
|
11
|
-
gem.description =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
gem.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
gem.
|
|
19
|
-
gem.
|
|
20
|
-
gem.add_development_dependency "coveralls", ">= 0.6.7"
|
|
21
|
-
gem.add_development_dependency "json", ">= 1.7.7"
|
|
22
|
-
gem.add_development_dependency "rake-compiler", ">= 0.8.3"
|
|
23
|
-
if RUBY_VERSION <= "1.9.2"
|
|
24
|
-
gem.add_development_dependency "mime-types", "< 2.0.0"
|
|
25
|
-
end
|
|
26
|
-
|
|
11
|
+
gem.authors = ['Neil Slater']
|
|
12
|
+
gem.email = ['slobo777@gmail.com']
|
|
13
|
+
gem.description = <<~GEMDESC
|
|
14
|
+
A library for simulating dice. Use it to construct dice-rolling systems used in role-playing and board games.
|
|
15
|
+
GEMDESC
|
|
16
|
+
gem.summary = <<~GEMSUMM
|
|
17
|
+
Simulates and explains dice rolls from simple "1d6" to complex "roll 7 ten-sided dice, take best 3,
|
|
18
|
+
results of 10 roll again and add on".
|
|
19
|
+
GEMSUMM
|
|
20
|
+
gem.homepage = 'https://github.com/neilslater/games_dice'
|
|
21
|
+
gem.license = 'MIT'
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
# However, it has a C extension, and v3.0.0 is does not compile for 1.8.7. This only affects the gem build process, so
|
|
30
|
-
# is only really used in environments like Travis, and is safe to wrap like this in the gemspec.
|
|
31
|
-
if RUBY_DESCRIPTION !~ /jruby/
|
|
32
|
-
if RUBY_VERSION >= "1.9.0"
|
|
33
|
-
gem.add_development_dependency "redcarpet", ">=2.3.0"
|
|
34
|
-
else
|
|
35
|
-
gem.add_development_dependency "redcarpet", ">=2.3.0", "<3.0.0"
|
|
36
|
-
end
|
|
37
|
-
end
|
|
23
|
+
gem.required_ruby_version = '>= 3.3.0'
|
|
38
24
|
|
|
39
|
-
gem.add_dependency
|
|
25
|
+
gem.add_dependency 'parslet', '~> 2.0'
|
|
40
26
|
|
|
41
|
-
gem.files = `git ls-files`.split(
|
|
42
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
27
|
+
gem.files = `git ls-files -z`.split("\0").select { |file| File.file?(file) }
|
|
28
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
43
29
|
gem.extensions = gem.files.grep(%r{/extconf\.rb$})
|
|
44
|
-
gem.
|
|
45
|
-
gem.
|
|
30
|
+
gem.require_paths = ['lib']
|
|
31
|
+
gem.metadata['rubygems_mfa_required'] = 'true'
|
|
46
32
|
end
|
data/lib/games_dice/bunch.rb
CHANGED
|
@@ -1,247 +1,203 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# d
|
|
16
|
-
# d.
|
|
17
|
-
# d.
|
|
18
|
-
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
explanation += used_dice.map do |die_result|
|
|
205
|
-
die_result.explain_value
|
|
206
|
-
end.join(' + ')
|
|
207
|
-
explanation += " = #{@result}" if @ndice > 1
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
explanation
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
private
|
|
214
|
-
|
|
215
|
-
def name_number_sides_from_hash options
|
|
216
|
-
@name = options[:name].to_s
|
|
217
|
-
@ndice = Integer(options[:ndice])
|
|
218
|
-
raise ArgumentError, ":ndice must be 1 or more, but got #{@ndice}" unless @ndice > 0
|
|
219
|
-
@sides = Integer(options[:sides])
|
|
220
|
-
raise ArgumentError, ":sides must be 1 or more, but got #{@sides}" unless @sides > 0
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
def keep_mode_from_hash options
|
|
224
|
-
case options[:keep_mode]
|
|
225
|
-
when nil then
|
|
226
|
-
@keep_mode = nil
|
|
227
|
-
when :keep_best then
|
|
228
|
-
@keep_mode = :keep_best
|
|
229
|
-
@keep_number = Integer(options[:keep_number] || 1)
|
|
230
|
-
when :keep_worst then
|
|
231
|
-
@keep_mode = :keep_worst
|
|
232
|
-
@keep_number = Integer(options[:keep_number] || 1)
|
|
233
|
-
else
|
|
234
|
-
raise ArgumentError, ":keep_mode can be nil, :keep_best or :keep_worst. Got #{options[:keep_mode].inspect}"
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
def complex_die_params_from_hash options
|
|
239
|
-
cd_hash = Hash.new
|
|
240
|
-
[:maps,:rerolls].each do |k|
|
|
241
|
-
cd_hash[k] = options[k].clone if options[k]
|
|
242
|
-
end
|
|
243
|
-
# We deliberately do not clone this object, it will often be intended that it is shared
|
|
244
|
-
cd_hash[:prng] = options[:prng]
|
|
245
|
-
cd_hash
|
|
246
|
-
end
|
|
247
|
-
end # class Bunch
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'games_dice/bunch_helpers'
|
|
4
|
+
|
|
5
|
+
module GamesDice
|
|
6
|
+
# This class models a number of identical dice, which may be either GamesDice::Die or
|
|
7
|
+
# GamesDice::ComplexDie objects.
|
|
8
|
+
#
|
|
9
|
+
# An object of this class represents a fixed number of indentical dice that may be rolled and their
|
|
10
|
+
# values summed to make a total for the bunch.
|
|
11
|
+
#
|
|
12
|
+
# @example The ubiquitous '3d6'
|
|
13
|
+
# d = GamesDice::Bunch.new( :ndice => 3, :sides => 6 )
|
|
14
|
+
# d.roll # => 14
|
|
15
|
+
# d.result # => 14
|
|
16
|
+
# d.explain_result # => "2 + 6 + 6 = 14"
|
|
17
|
+
# d.max # => 18
|
|
18
|
+
#
|
|
19
|
+
# @example Roll 5d10, and keep the best 2
|
|
20
|
+
# d = GamesDice::Bunch.new( :ndice => 5, :sides => 10 , :keep_mode => :keep_best, :keep_number => 2 )
|
|
21
|
+
# d.roll # => 18
|
|
22
|
+
# d.result # => 18
|
|
23
|
+
# d.explain_result # => "4, 9, 2, 9, 1. Keep: 9 + 9 = 18"
|
|
24
|
+
#
|
|
25
|
+
class Bunch
|
|
26
|
+
include KeepHelpers
|
|
27
|
+
include ExplainHelpers
|
|
28
|
+
|
|
29
|
+
# The constructor accepts parameters that are suitable for either GamesDice::Die or GamesDice::ComplexDie
|
|
30
|
+
# and decides which of those classes to instantiate.
|
|
31
|
+
# @param [Hash] options
|
|
32
|
+
# @option options [Integer] :ndice Number of dice in the bunch, *mandatory*
|
|
33
|
+
# @option options [Integer] :sides Number of sides on a single die in the bunch, *mandatory*
|
|
34
|
+
# @option options [String] :name Optional name for the bunch
|
|
35
|
+
# @option options [Array<GamesDice::RerollRule,Array>] :rerolls Optional rules that cause the die to roll again
|
|
36
|
+
# @option options [Array<GamesDice::MapRule,Array>] :maps Optional rules to convert a value into a final result
|
|
37
|
+
# for the die
|
|
38
|
+
# @option options [#rand] :prng Optional alternative source of randomness to Ruby's built-in #rand, passed to
|
|
39
|
+
# GamesDice::Die's constructor
|
|
40
|
+
# @option options [Symbol] :keep_mode Optional, either *:keep_best* or *:keep_worst*
|
|
41
|
+
# @option options [Integer] :keep_number Optional number of dice to keep when :keep_mode is not nil
|
|
42
|
+
# @return [GamesDice::Bunch]
|
|
43
|
+
def initialize(options)
|
|
44
|
+
name_number_sides_from_hash(options)
|
|
45
|
+
keep_mode_from_hash(options)
|
|
46
|
+
|
|
47
|
+
raise ':prng does not support the rand() method' if options[:prng] && !options[:prng].respond_to?(:rand)
|
|
48
|
+
|
|
49
|
+
@single_die = if options[:rerolls] || options[:maps]
|
|
50
|
+
GamesDice::ComplexDie.new(@sides, complex_die_params_from_hash(options))
|
|
51
|
+
else
|
|
52
|
+
GamesDice::Die.new(@sides, options[:prng])
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Name to help identify bunch
|
|
57
|
+
# @return [String]
|
|
58
|
+
attr_reader :name
|
|
59
|
+
|
|
60
|
+
# Number of dice to roll
|
|
61
|
+
# @return [Integer]
|
|
62
|
+
attr_reader :ndice
|
|
63
|
+
|
|
64
|
+
# Individual die from the bunch
|
|
65
|
+
# @return [GamesDice::Die,GamesDice::ComplexDie]
|
|
66
|
+
attr_reader :single_die
|
|
67
|
+
|
|
68
|
+
# Can be nil, :keep_best or :keep_worst
|
|
69
|
+
# @return [Symbol,nil]
|
|
70
|
+
attr_reader :keep_mode
|
|
71
|
+
|
|
72
|
+
# Number of "best" or "worst" results to select when #keep_mode is not nil.
|
|
73
|
+
# @return [Integer,nil]
|
|
74
|
+
attr_reader :keep_number
|
|
75
|
+
|
|
76
|
+
# Result of most-recent roll, or nil if no roll made yet.
|
|
77
|
+
# @return [Integer,nil]
|
|
78
|
+
attr_reader :result
|
|
79
|
+
|
|
80
|
+
# @!attribute [r] label
|
|
81
|
+
# Description that will be used in explanations with more than one bunch
|
|
82
|
+
# @return [String]
|
|
83
|
+
def label
|
|
84
|
+
return @name if @name != ''
|
|
85
|
+
|
|
86
|
+
"#{@ndice}d#{@sides}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @!attribute [r] rerolls
|
|
90
|
+
# Sequence of re-roll rules, or nil if re-rolls are not required.
|
|
91
|
+
# @return [Array<GamesDice::RerollRule>, nil]
|
|
92
|
+
def rerolls
|
|
93
|
+
@single_die.rerolls
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @!attribute [r] maps
|
|
97
|
+
# Sequence of map rules, or nil if mapping is not required.
|
|
98
|
+
# @return [Array<GamesDice::MapRule>, nil]
|
|
99
|
+
def maps
|
|
100
|
+
@single_die.maps
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @!attribute [r] result_details
|
|
104
|
+
# After calling #roll, this is an array of GamesDice::DieResult objects. There is one from each #single_die rolled,
|
|
105
|
+
# allowing inspection of how the result was obtained.
|
|
106
|
+
# @return [Array<GamesDice::DieResult>, nil] Sequence of GamesDice::DieResult objects.
|
|
107
|
+
def result_details
|
|
108
|
+
return nil unless @raw_result_details
|
|
109
|
+
|
|
110
|
+
@raw_result_details.map { |r| r.is_a?(Integer) ? GamesDice::DieResult.new(r) : r }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @!attribute [r] min
|
|
114
|
+
# Minimum possible result from a call to #roll
|
|
115
|
+
# @return [Integer]
|
|
116
|
+
def min
|
|
117
|
+
n = @keep_mode ? [@keep_number, @ndice].min : @ndice
|
|
118
|
+
n * @single_die.min
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @!attribute [r] max
|
|
122
|
+
# Maximum possible result from a call to #roll
|
|
123
|
+
# @return [Integer]
|
|
124
|
+
def max
|
|
125
|
+
n = @keep_mode ? [@keep_number, @ndice].min : @ndice
|
|
126
|
+
n * @single_die.max
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Calculates the probability distribution for the bunch. When the bunch is composed of dice with
|
|
130
|
+
# open-ended re-roll rules, there are some arbitrary limits imposed to prevent large amounts of
|
|
131
|
+
# recursion.
|
|
132
|
+
# @return [GamesDice::Probabilities] Probability distribution of bunch.
|
|
133
|
+
def probabilities
|
|
134
|
+
return @probabilities if @probabilities
|
|
135
|
+
|
|
136
|
+
@probabilities = if @keep_mode && @ndice > @keep_number
|
|
137
|
+
@single_die.probabilities.repeat_n_sum_k(@ndice, @keep_number, @keep_mode)
|
|
138
|
+
else
|
|
139
|
+
@single_die.probabilities.repeat_sum(@ndice)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
@probabilities
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Simulates rolling the bunch of identical dice
|
|
146
|
+
# @return [Integer] Sum of all rolled dice, or sum of all keepers
|
|
147
|
+
def roll
|
|
148
|
+
generate_raw_results
|
|
149
|
+
return @result if !@keep_mode || @keep_number.to_i >= @ndice
|
|
150
|
+
|
|
151
|
+
use_dice = case @keep_mode
|
|
152
|
+
when :keep_best then @raw_result_details.sort[-@keep_number..]
|
|
153
|
+
when :keep_worst then @raw_result_details.sort[0..(@keep_number - 1)]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
@result = use_dice.sum
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @!attribute [r] explain_result
|
|
160
|
+
# Explanation of result, or nil if no call to #roll yet.
|
|
161
|
+
# @return [String,nil]
|
|
162
|
+
def explain_result
|
|
163
|
+
return nil unless @result
|
|
164
|
+
|
|
165
|
+
# With #keep_mode, we may need to show unused and used dice separately
|
|
166
|
+
used_dice = result_details
|
|
167
|
+
used_dice, = find_used_dice_due_to_keep_mode(result_details) if @keep_mode && @keep_number < @ndice
|
|
168
|
+
|
|
169
|
+
build_explanation(used_dice)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
def generate_raw_results
|
|
175
|
+
@result = 0
|
|
176
|
+
@raw_result_details = []
|
|
177
|
+
|
|
178
|
+
@ndice.times do
|
|
179
|
+
@result += @single_die.roll
|
|
180
|
+
@raw_result_details << @single_die.result
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def name_number_sides_from_hash(options)
|
|
185
|
+
@name = options[:name].to_s
|
|
186
|
+
@ndice = Integer(options[:ndice])
|
|
187
|
+
raise ArgumentError, ":ndice must be 1 or more, but got #{@ndice}" unless @ndice.positive?
|
|
188
|
+
|
|
189
|
+
@sides = Integer(options[:sides])
|
|
190
|
+
raise ArgumentError, ":sides must be 1 or more, but got #{@sides}" unless @sides.positive?
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def complex_die_params_from_hash(options)
|
|
194
|
+
cd_hash = {}
|
|
195
|
+
%i[maps rerolls].each do |k|
|
|
196
|
+
cd_hash[k] = options[k].clone if options[k]
|
|
197
|
+
end
|
|
198
|
+
# We deliberately do not clone this object, it will often be intended that it is shared
|
|
199
|
+
cd_hash[:prng] = options[:prng]
|
|
200
|
+
cd_hash
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GamesDice
|
|
4
|
+
class Bunch
|
|
5
|
+
# @!visibility private
|
|
6
|
+
# Private extension methods for GamesDice::Bunch keep rules
|
|
7
|
+
module KeepHelpers
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def keep_mode_from_hash(options)
|
|
11
|
+
@keep_mode = options[:keep_mode]
|
|
12
|
+
case @keep_mode
|
|
13
|
+
when nil
|
|
14
|
+
@keep_mode = nil
|
|
15
|
+
when :keep_best, :keep_worst
|
|
16
|
+
@keep_number = Integer(options[:keep_number] || 1)
|
|
17
|
+
else
|
|
18
|
+
raise ArgumentError, ":keep_mode can be nil, :keep_best or :keep_worst. Got #{options[:keep_mode].inspect}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def find_used_dice_due_to_keep_mode(used_dice, unused_dice = [])
|
|
23
|
+
full_dice = result_details.sort_by(&:total)
|
|
24
|
+
case @keep_mode
|
|
25
|
+
when :keep_best
|
|
26
|
+
used_dice = full_dice[-@keep_number..]
|
|
27
|
+
unused_dice = full_dice[0..(full_dice.length - 1 - @keep_number)]
|
|
28
|
+
when :keep_worst
|
|
29
|
+
used_dice = full_dice[0..(@keep_number - 1)]
|
|
30
|
+
unused_dice = full_dice[@keep_number..]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
[used_dice, unused_dice]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def explain_kept_dice(used_dice)
|
|
37
|
+
separator = @single_die.maps ? ', ' : ' + '
|
|
38
|
+
". Keep: #{used_dice.map(&:explain_total).join(separator)}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @!visibility private
|
|
43
|
+
# Private extension methods for GamesDice::Bunch explaining
|
|
44
|
+
module ExplainHelpers
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def build_explanation(used_dice)
|
|
48
|
+
if @keep_mode || @single_die.maps
|
|
49
|
+
explanation = explain_with_keep_or_map(used_dice)
|
|
50
|
+
else
|
|
51
|
+
explanation = used_dice.map(&:explain_value).join(' + ')
|
|
52
|
+
explanation += " = #{@result}" if @ndice > 1
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
explanation
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def explain_with_keep_or_map(used_dice)
|
|
59
|
+
explanation = result_details.map(&:explain_value).join(', ')
|
|
60
|
+
explanation += explain_kept_dice(used_dice) if @keep_mode
|
|
61
|
+
explanation += ". Successes: #{@result}" if @single_die.maps
|
|
62
|
+
explanation += " = #{@result}" if @keep_mode && !@single_die.maps && @keep_number > 1
|
|
63
|
+
|
|
64
|
+
explanation
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|