games_dice 0.4.0 → 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.
@@ -46,9 +46,11 @@ module GamesDice
46
46
  rule(:condition_type_and_num) do
47
47
  opint_or_int.as(:condition) >> comma >> ctl_string.as(:type) >> comma >> integer.as(:num)
48
48
  end
49
+
49
50
  rule(:condition_num_and_output) do
50
51
  opint_or_int.as(:condition) >> comma >> plus_minus_integer.as(:num) >> comma >> output_string.as(:output)
51
52
  end
53
+
52
54
  rule(:num_and_type) { integer.as(:num) >> comma >> ctl_string.as(:type) }
53
55
 
54
56
  rule(:reroll_params) { condition_type_and_num | condition_and_type | condition_only }
@@ -78,25 +80,41 @@ module GamesDice
78
80
  def parse(dice_description)
79
81
  dice_description = dice_description.to_s.strip
80
82
  # Force first item to start '+' for simpler parse rules
81
- dice_description = "+#{dice_description}" unless dice_description =~ /\A[+-]/
82
- dice_expressions = super(dice_description)
83
- { bunches: collect_bunches(dice_expressions), offset: collect_offset(dice_expressions) }
83
+ dice_description = "+#{dice_description}" unless /\A[+-]/.match?(dice_description)
84
+ dice_expressions = super
85
+ {
86
+ bunches: ParseTreeProcessor.collect_bunches(dice_expressions),
87
+ offset: ParseTreeProcessor.collect_offset(dice_expressions)
88
+ }
84
89
  end
85
90
 
86
- private
91
+ # Class converts parse tree to GamesDice hash model
92
+ # @!visibility private
93
+ class ParseTreeProcessor
94
+ class << self
95
+ def collect_bunches(dice_expressions)
96
+ dice_expressions[:bunches].select { |h| h[:ndice] }.map do |in_hash|
97
+ out_hash = {}
98
+ collect_bunch_basics(in_hash, out_hash)
99
+ collect_bunch_multiplier(in_hash, out_hash) if in_hash[:op]
100
+
101
+ in_hash[:mods]&.each do |mod|
102
+ collect_bunch_modifier(mod, out_hash)
103
+ end
104
+
105
+ out_hash
106
+ end
107
+ end
87
108
 
88
- def collect_bunches(dice_expressions)
89
- dice_expressions[:bunches].select { |h| h[:ndice] }.map do |in_hash|
90
- out_hash = {}
91
- # Convert integers
92
- %i[ndice sides].each do |s|
93
- next unless in_hash[s]
109
+ def collect_bunch_basics(in_hash, out_hash)
110
+ %i[ndice sides].each do |s|
111
+ next unless in_hash[s]
94
112
 
95
- out_hash[s] = in_hash[s].to_i
113
+ out_hash[s] = in_hash[s].to_i
114
+ end
96
115
  end
97
116
 
98
- # Multiplier
99
- if in_hash[:op]
117
+ def collect_bunch_multiplier(in_hash, out_hash)
100
118
  optype = in_hash[:op].to_s
101
119
  out_hash[:multiplier] = case optype
102
120
  when '+' then 1
@@ -104,116 +122,124 @@ module GamesDice
104
122
  end
105
123
  end
106
124
 
107
- # Modifiers
108
- in_hash[:mods]&.each do |mod|
125
+ def collect_bunch_modifier(mod, out_hash)
109
126
  if mod[:alias]
110
- collect_alias_modifier mod, out_hash
127
+ ParseTreeBunchModifier.collect_alias_modifier mod, out_hash
111
128
  elsif mod[:keep]
112
- collect_keeper_rule mod, out_hash
129
+ ParseTreeBunchModifier.collect_keeper_rule mod, out_hash
113
130
  elsif mod[:map]
114
- out_hash[:maps] ||= []
115
- collect_map_rule mod, out_hash
131
+ ParseTreeBunchModifier.collect_map_rule mod, out_hash
116
132
  elsif mod[:reroll]
117
- out_hash[:rerolls] ||= []
118
- collect_reroll_rule mod, out_hash
133
+ ParseTreeBunchModifier.collect_reroll_rule mod, out_hash
119
134
  end
120
135
  end
121
136
 
122
- out_hash
137
+ def collect_offset(dice_expressions)
138
+ dice_expressions[:bunches].select { |h| h[:constant] }.inject(0) do |total, in_hash|
139
+ c = in_hash[:constant].to_i
140
+ optype = in_hash[:op].to_s
141
+ if optype == '+'
142
+ total + c
143
+ else
144
+ total - c
145
+ end
146
+ end
147
+ end
123
148
  end
124
149
  end
125
150
 
126
- def collect_offset(dice_expressions)
127
- dice_expressions[:bunches].select { |h| h[:constant] }.inject(0) do |total, in_hash|
128
- c = in_hash[:constant].to_i
129
- optype = in_hash[:op].to_s
130
- if optype == '+'
131
- total += c
132
- else
133
- total -= c
151
+ # Class for collating bunch data into bunch construction hash
152
+ # @!visibility private
153
+ class ParseTreeBunchModifier
154
+ class << self
155
+ # Called when we have a single letter convenient alias for common dice adjustments
156
+ def collect_alias_modifier(alias_mod, out_hash)
157
+ alias_name = alias_mod[:alias].to_s
158
+ case alias_name
159
+ when 'x' # Exploding re-roll
160
+ out_hash[:rerolls] ||= []
161
+ out_hash[:rerolls] << [out_hash[:sides], :==, :reroll_add]
162
+ end
134
163
  end
135
- total
136
- end
137
- end
138
164
 
139
- # Called when we have a single letter convenient alias for common dice adjustments
140
- def collect_alias_modifier(alias_mod, out_hash)
141
- alias_name = alias_mod[:alias].to_s
142
- case alias_name
143
- when 'x' # Exploding re-roll
144
- out_hash[:rerolls] ||= []
145
- out_hash[:rerolls] << [out_hash[:sides], :==, :reroll_add]
146
- end
147
- end
165
+ # Called for any parsed reroll rule
166
+ def collect_reroll_rule(reroll_mod, out_hash)
167
+ out_hash[:rerolls] ||= []
168
+ if reroll_mod[:simple_value]
169
+ out_hash[:rerolls] << [reroll_mod[:simple_value].to_i, :>=, :reroll_replace]
170
+ return
171
+ end
148
172
 
149
- # Called for any parsed reroll rule
150
- def collect_reroll_rule(reroll_mod, out_hash)
151
- out_hash[:rerolls] ||= []
152
- if reroll_mod[:simple_value]
153
- out_hash[:rerolls] << [reroll_mod[:simple_value].to_i, :>=, :reroll_replace]
154
- return
155
- end
173
+ collect_complex_reroll_rule(reroll_mod, out_hash)
174
+ end
156
175
 
157
- # Typical reroll_mod: {:reroll=>"r"@5, :condition=>{:compare_num=>"10"@7}, :type=>"add"@10}
158
- op = get_op_symbol(reroll_mod[:condition][:comparison] || '==')
159
- v = reroll_mod[:condition][:compare_num].to_i
160
- type = "reroll_#{reroll_mod[:type] || 'replace'}".to_sym
176
+ def collect_complex_reroll_rule(reroll_mod, out_hash)
177
+ # Typical reroll_mod: {:reroll=>"r"@5, :condition=>{:compare_num=>"10"@7}, :type=>"add"@10}
178
+ op = get_op_symbol(reroll_mod[:condition][:comparison] || '==')
179
+ v = reroll_mod[:condition][:compare_num].to_i
180
+ type = :"reroll_#{reroll_mod[:type] || 'replace'}"
181
+
182
+ out_hash[:rerolls] << if reroll_mod[:num]
183
+ [v, op, type, reroll_mod[:num].to_i]
184
+ else
185
+ [v, op, type]
186
+ end
187
+ end
161
188
 
162
- out_hash[:rerolls] << if reroll_mod[:num]
163
- [v, op, type, reroll_mod[:num].to_i]
164
- else
165
- [v, op, type]
166
- end
167
- end
189
+ # Called for any parsed keeper mode
190
+ def collect_keeper_rule(keeper_mod, out_hash)
191
+ raise 'Cannot set keepers for a bunch twice' if out_hash[:keep_mode]
168
192
 
169
- # Called for any parsed keeper mode
170
- def collect_keeper_rule(keeper_mod, out_hash)
171
- raise 'Cannot set keepers for a bunch twice' if out_hash[:keep_mode]
193
+ if keeper_mod[:simple_value]
194
+ out_hash[:keep_mode] = :keep_best
195
+ out_hash[:keep_number] = keeper_mod[:simple_value].to_i
196
+ return
197
+ end
172
198
 
173
- if keeper_mod[:simple_value]
174
- out_hash[:keep_mode] = :keep_best
175
- out_hash[:keep_number] = keeper_mod[:simple_value].to_i
176
- return
177
- end
199
+ # Typical keeper_mod: {:keep=>"k"@5, :num=>"1"@7, :type=>"worst"@9}
200
+ out_hash[:keep_number] = keeper_mod[:num].to_i
201
+ out_hash[:keep_mode] = :"keep_#{keeper_mod[:type] || 'best'}"
202
+ end
178
203
 
179
- # Typical keeper_mod: {:keep=>"k"@5, :num=>"1"@7, :type=>"worst"@9}
180
- out_hash[:keep_number] = keeper_mod[:num].to_i
181
- out_hash[:keep_mode] = "keep_#{keeper_mod[:type] || 'best'}".to_sym
182
- end
204
+ # Called for any parsed map mode
205
+ def collect_map_rule(map_mod, out_hash)
206
+ out_hash[:maps] ||= []
207
+ if map_mod[:simple_value]
208
+ out_hash[:maps] << [map_mod[:simple_value].to_i, :<=, 1]
209
+ return
210
+ end
183
211
 
184
- # Called for any parsed map mode
185
- def collect_map_rule(map_mod, out_hash)
186
- out_hash[:maps] ||= []
187
- if map_mod[:simple_value]
188
- out_hash[:maps] << [map_mod[:simple_value].to_i, :<=, 1]
189
- return
190
- end
212
+ collect_complex_map_rule(map_mod, out_hash)
213
+ end
191
214
 
192
- # Typical map_mod: {:map=>"m"@4, :condition=>{:compare_num=>"5"@6}, :num=>"2"@8, :output=>"Qwerty"@10}
193
- op = get_op_symbol(map_mod[:condition][:comparison] || '>=')
194
- v = map_mod[:condition][:compare_num].to_i
195
- out_val = 1
196
- out_val = map_mod[:num].to_i if map_mod[:num]
197
-
198
- out_hash[:maps] << if map_mod[:output]
199
- [v, op, out_val, map_mod[:output].to_s]
200
- else
201
- [v, op, out_val]
202
- end
203
- end
215
+ def collect_complex_map_rule(map_mod, out_hash)
216
+ # Typical map_mod: {:map=>"m"@4, :condition=>{:compare_num=>"5"@6}, :num=>"2"@8, :output=>"Qwerty"@10}
217
+ op = get_op_symbol(map_mod[:condition][:comparison] || '>=')
218
+ v = map_mod[:condition][:compare_num].to_i
219
+ out_val = 1
220
+ out_val = map_mod[:num].to_i if map_mod[:num]
221
+
222
+ out_hash[:maps] << if map_mod[:output]
223
+ [v, op, out_val, map_mod[:output].to_s]
224
+ else
225
+ [v, op, out_val]
226
+ end
227
+ end
204
228
 
205
- # The dice description language uses (r).op.x, whilst GamesDice::RerollRule uses x.op.(r), so
206
- # as well as converting to a symbol, we must reverse sense of input to constructor
207
- OP_CONVERSION = {
208
- '==' => :==,
209
- '>=' => :<=,
210
- '>' => :<,
211
- '<' => :>,
212
- '<=' => :>=
213
- }.freeze
214
-
215
- def get_op_symbol(parsed_op_string)
216
- OP_CONVERSION[parsed_op_string.to_s]
229
+ # The dice description language uses (r).op.x, whilst GamesDice::RerollRule uses x.op.(r), so
230
+ # as well as converting to a symbol, we must reverse sense of input to constructor
231
+ OP_CONVERSION = {
232
+ '==' => :==,
233
+ '>=' => :<=,
234
+ '>' => :<,
235
+ '<' => :>,
236
+ '<=' => :>=
237
+ }.freeze
238
+
239
+ def get_op_symbol(parsed_op_string)
240
+ OP_CONVERSION[parsed_op_string.to_s]
241
+ end
242
+ end
217
243
  end
218
244
  end
219
245
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GamesDice
4
- VERSION = '0.4.0'
4
+ # Current version of the gem.
5
+ VERSION = '0.4.2'
5
6
  end
data/lib/games_dice.rb CHANGED
@@ -1,11 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'games_dice/version'
4
- begin
5
- require 'games_dice/games_dice'
6
- rescue LoadError
7
- require 'games_dice/probabilities'
8
- end
9
4
  require 'games_dice/constants'
10
5
  require 'games_dice/die'
11
6
  require 'games_dice/die_result'
@@ -15,20 +10,23 @@ require 'games_dice/complex_die'
15
10
  require 'games_dice/bunch'
16
11
  require 'games_dice/dice'
17
12
  require 'games_dice/parser'
13
+ require 'games_dice/games_dice'
18
14
  require 'games_dice/marshal'
19
15
 
16
+ # GamesDice is a library for simulating dice combinations used in dice and board games.
20
17
  module GamesDice
21
- # @!visibility private
22
- @@parser = GamesDice::Parser.new
23
-
24
18
  # Creates an instance of GamesDice::Dice from a string description.
25
19
  # @param [String] dice_description Uses a variation of common game notation, examples: '1d6', '3d8+1d4+7', '5d10k2'
26
20
  # @param [#rand] prng Optional random number generator, default is to use Ruby's built-in #rand()
27
21
  # @return [GamesDice::Dice] A new dice object.
28
22
  #
29
23
  def self.create(dice_description, prng = nil)
30
- parsed = @@parser.parse(dice_description)
24
+ parsed = parser.parse(dice_description)
31
25
  parsed[:bunches].each { |bunch| bunch.merge!(prng: prng) } if prng
32
26
  GamesDice::Dice.new(parsed[:bunches], parsed[:offset])
33
27
  end
28
+
29
+ def self.parser
30
+ @parser ||= GamesDice::Parser.new
31
+ end
34
32
  end