dicebag 3.1.0 → 3.2.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 755294b13e5e461c7cefc63493214beb228d539a
4
+ data.tar.gz: 340241ef792274c0da3a67b97f64ba81f18e6163
5
+ SHA512:
6
+ metadata.gz: 5efb90bebf321dd2a721f367ec7b1a3c4e57963858210393a0f246646d4cb3865c6f36260c390639007d824100fac44cd16707c9c539db41cf92c833d2073827
7
+ data.tar.gz: d2b2871d3940d219091daedc85c63771b69a1a789d087e972cb50ae41a0279880733abfabb61199b4d3bb9a35f8f52d00f202d97a4bad8ca47c3e6d5ac7c7a6a
@@ -1,9 +1,10 @@
1
+ # Encoding: UTF-8
2
+
1
3
  module DiceBag
2
4
  # The subclass for a label.
3
5
  class LabelPart < SimplePart
4
6
  def to_s
5
- return "(%s)" % self.value
7
+ format('(%s)', value)
6
8
  end
7
9
  end
8
-
9
10
  end
@@ -1,12 +1,27 @@
1
+ # Encoding: UTF-8
2
+
1
3
  module DiceBag
4
+ # This class parses the dice string into the individual
5
+ # components. To understand this code, please refer to
6
+ # the Parslet library's documentation.
2
7
  class Parser < Parslet::Parser
8
+ # We override the #parse method, so that we can
9
+ # assure that we always return an Array.
10
+ def parse(dstr)
11
+ result = super dstr
12
+
13
+ result = [result] unless result.is_a? Array
14
+
15
+ result
16
+ end
17
+
3
18
  # Base rules.
4
19
  rule(:space?) { str(' ').repeat }
5
20
 
6
21
  # Numbers are limited to 3 digit places. Why?
7
- # To prevent abuse from people rolling:
22
+ # To prevent abuse from people rolling:
8
23
  # 999999999D999999999 and 'DOS'-ing the app.
9
- rule(:number) { match('[0-9]').repeat(1,3) }
24
+ rule(:number) { match('[0-9]').repeat(1, 3) }
10
25
  rule(:number?) { number.maybe }
11
26
 
12
27
  # Label rule
@@ -14,15 +29,15 @@ module DiceBag
14
29
  # are not allowed to have commas in the label.
15
30
  # This for future use of parsing multiple dice
16
31
  # definitions in comma-separated strings.
17
- # The :label matches anything that ISN'T a
32
+ # The :label matches anything that ISN'T a
18
33
  # parenethesis or a comma.
19
34
  rule(:lparen) { str('(') }
20
35
  rule(:rparen) { str(')') }
21
36
  rule(:label) do
22
- lparen >>
23
- match('[^(),]').repeat(1).as(:label) >>
24
- rparen >>
25
- space?
37
+ lparen >>
38
+ match('[^(),]').repeat(1).as(:label) >>
39
+ rparen >>
40
+ space?
26
41
  end
27
42
 
28
43
  # count and sides rules.
@@ -49,9 +64,9 @@ module DiceBag
49
64
  # This allows options to be defined in any order and
50
65
  # even have more than one of the same option, however
51
66
  # only the last option of a given key will be kept.
52
- rule(:options) {
67
+ rule(:options) do
53
68
  space? >> (drop | explode | keep | reroll | target).repeat >> space?
54
- }
69
+ end
55
70
 
56
71
  rule(:options?) { options.maybe.as(:options) }
57
72
 
@@ -61,16 +76,16 @@ module DiceBag
61
76
  rule(:mul) { str('*') }
62
77
  rule(:div) { str('/') }
63
78
  rule(:op) { (add | sub | mul | div).as(:op) }
64
-
79
+
65
80
  # Part Rule
66
81
  # A part is an operator, followed by either an xDx
67
82
  # string or a static number value.
68
- rule(:part) do
69
- space? >>
70
- op >>
71
- space? >>
72
- (xdx | number).as(:value) >>
73
- space?
83
+ rule(:part) do
84
+ space? >>
85
+ op >>
86
+ space? >>
87
+ (xdx | number).as(:value) >>
88
+ space?
74
89
  end
75
90
 
76
91
  # All parts of a dice roll MUST start with an xDx
@@ -80,7 +95,7 @@ module DiceBag
80
95
 
81
96
  # A dice string is an optional label, followed by
82
97
  # the defined parts.
83
- rule(:dice) { label.maybe >> parts }
98
+ rule(:dice) { label.maybe >> parts }
84
99
 
85
100
  root(:dice)
86
101
  end
@@ -1,8 +1,10 @@
1
+ # Encoding: UTF-8
2
+
1
3
  module DiceBag
2
4
  # This class merely encapsulates the result,
3
5
  # providing convience methods to access the
4
6
  # results of each section if desired.
5
- class Result
7
+ class Result
6
8
  attr_reader :label
7
9
  attr_reader :total
8
10
  attr_reader :sections
@@ -13,17 +15,14 @@ module DiceBag
13
15
  @sections = sections
14
16
  end
15
17
 
16
- def each(&block)
17
- self.sections.each do |section|
18
- yield section
19
- end
20
- return nil
18
+ def each
19
+ sections.each { |section| yield section }
21
20
  end
22
21
 
23
22
  def to_s
24
- return "#{self.label}: #{self.total}" unless self.label.empty?
25
- return self.total.to_s
23
+ return "#{label}: #{total}" unless label.empty?
24
+
25
+ total.to_s
26
26
  end
27
27
  end
28
-
29
28
  end
data/lib/dicebag/roll.rb CHANGED
@@ -1,98 +1,77 @@
1
+ # Encoding: UTF-8
2
+
1
3
  module DiceBag
2
4
  # This is the 'main' class of Dice Bag. This class
3
5
  # takes the dice string, parses it, and encapsulates
4
6
  # the actual rolling of the dice. If no dice string
5
7
  # is given, it defaults to DefaultRoll.
6
8
  class Roll
7
- attr :dstr
8
- attr :tree
9
+ include RollString
10
+
11
+ attr_reader :dstr
12
+ attr_reader :tree
9
13
 
10
- alias :parsed :tree
14
+ alias parsed tree
11
15
 
12
- def initialize(dstr=nil)
13
- @dstr = dstr ||= DefaultRoll
14
- @tree = DiceBag.parse(dstr)
16
+ def initialize(dstr = nil)
17
+ @dstr = dstr ||= DEFAULT_ROLL
18
+ @tree = DiceBag.parse dstr
15
19
  @result = nil
16
20
  end
17
21
 
18
22
  def notes
19
- s = ""
23
+ str = ''
20
24
 
21
- self.tree.each do |op, part|
22
- if part.is_a?(RollPart)
23
- n = part.notes
24
- s += "For: #{part}:\n#{n}\n\n" unless n.empty?
25
- end
25
+ tree.each do |_op, part|
26
+ next unless part.is_a?(RollPart) || !part.notes.empty?
27
+
28
+ str += format('For: %s\n%s\n\n', part, part.notes)
26
29
  end
27
30
 
28
- return s
31
+ str
29
32
  end
30
33
 
31
34
  def result
32
- self.roll() unless @result
33
- return @result
35
+ roll unless @result
36
+
37
+ @result
34
38
  end
35
39
 
36
40
  def roll
37
- total = 0
38
- label = ""
39
- sections = []
40
-
41
- self.tree.each do |op, part|
42
- do_push = true
43
-
44
- # If this is a RollPart instance,
45
- # ensure fresh results.
46
- part.roll() if part.is_a?(RollPart)
47
-
48
- case op
49
- when :label
50
- label = part.value()
51
- do_push = false
52
- when :start
53
- total = part.total()
54
- when :add
55
- total += part.total()
56
- when :sub
57
- total -= part.total()
58
- when :mul
59
- total *= part.total()
60
- when :div
61
- total /= part.total()
62
- end
63
-
64
- sections.push(part) if do_push
65
- end
41
+ @label = ''
42
+ @total = 0
43
+ @sections = []
66
44
 
67
- @result = Result.new(label, total, sections)
45
+ handle_tree
68
46
 
69
- return @result
47
+ @result = Result.new(@label, @total, @sections)
70
48
  end
71
49
 
72
- def to_s(with_space=true)
73
- s = ""
74
-
75
- sp = with_space ? ' ' : ''
76
-
77
- self.tree.each do |op, value|
78
- case op
79
- when :label
80
- s += "#{value}#{sp}"
81
- when :start
82
- s += "#{value}#{sp}"
83
- when :add
84
- s += "+#{sp}#{value}#{sp}"
85
- when :sub
86
- s += "-#{sp}#{value}#{sp}"
87
- when :mul
88
- s += "*#{sp}#{value}#{sp}"
89
- when :div
90
- s += "/#{sp}#{value}#{sp}"
50
+ private
51
+
52
+ def handle_tree
53
+ tree.each do |op, part|
54
+ if op == :label
55
+ @label = part.value
56
+ next
91
57
  end
58
+
59
+ part.roll if part.is_a?(RollPart) # ensure fresh results.
60
+
61
+ handle_op op, part
92
62
  end
63
+ end
93
64
 
94
- return s.strip
65
+ def handle_op(op, part)
66
+ case op
67
+ when :start then @total = part.total
68
+ when :add then @total += part.total
69
+ when :sub then @total -= part.total
70
+ when :mul then @total *= part.total
71
+ when :div then @total /= part.total
72
+ end
73
+
74
+ @sections.push part
95
75
  end
96
76
  end
97
-
98
77
  end
@@ -1,11 +1,15 @@
1
+ # Encoding: UTF-8
2
+
1
3
  module DiceBag
2
4
  # This represents the xDx part of the dice string.
3
5
  class RollPart < SimplePart
6
+ include RollPartString
4
7
 
5
- attr :count
6
- attr :sides
7
- attr :parts
8
- attr :options
8
+ attr_reader :count
9
+ attr_reader :sides
10
+ attr_reader :parts
11
+ attr_reader :options
12
+ attr_reader :tally
9
13
 
10
14
  def initialize(part)
11
15
  @total = nil
@@ -16,127 +20,111 @@ module DiceBag
16
20
  @notes = part[:notes] || []
17
21
 
18
22
  # Our Default Options
19
- @options = {
20
- :explode => 0,
21
- :drop => 0,
22
- :keep => 0,
23
- :reroll => 0,
24
- :target => 0
25
- }
26
-
27
- @options.update(part[:options]) if part.has_key?(:options)
23
+ @options = { explode: 0, drop: 0, keep: 0, reroll: 0, target: 0 }
24
+
25
+ @options.update(part[:options]) if part.key?(:options)
28
26
  end
29
27
 
30
28
  def notes
31
- return @notes.join("\n") unless @notes.empty?
32
- return ""
29
+ @notes.empty? ? '' : @notes.join("\n")
33
30
  end
34
31
 
35
32
  # Checks to see if this instance has rolled yet
36
33
  # or not.
37
- def has_rolled?
38
- return @total.nil? ? false : true
34
+ def rolled?
35
+ @total.nil? ? false : true
39
36
  end
40
37
 
41
38
  # Rolls a single die from the xDx string.
42
- def roll_die()
43
- num = 0
44
- reroll = @options[:reroll]
39
+ def roll_die
40
+ num = 0
41
+ num = rand(sides) + 1 while num <= @options[:reroll]
45
42
 
46
- while num <= reroll
47
- num = rand(self.sides) + 1
48
- end
49
-
50
- return num
43
+ num
51
44
  end
52
45
 
53
46
  def roll
54
- results = []
55
- explode = @options[:explode]
56
-
57
- self.count.times do
58
- roll = self.roll_die()
47
+ generate_results
59
48
 
60
- results.push(roll)
61
-
62
- unless explode.zero?
63
- while roll >= explode
64
- roll = self.roll_die()
65
- results.push(roll)
66
- end
67
- end
68
- end
69
-
70
- results.sort!
71
- results.reverse!
49
+ @results.sort!
50
+ @results.reverse!
72
51
 
73
52
  # Save the tally in case it's requested later.
74
- @tally = results.dup()
53
+ @tally = @results.dup
75
54
 
76
- # Drop the low end numbers if :drop is less than zero.
77
- if @options[:drop] < 0
78
- results = results[0 ... @options[:drop]]
79
- end
55
+ # Drop the low end numbers if :drop is not zero.
56
+ handle_drop
80
57
 
81
58
  # Keep the high end numbers if :keep is greater than zero.
82
- if @options[:keep] > 0
83
- results = results[0 ... @options[:keep]]
84
- end
59
+ handle_keep
85
60
 
86
- # If we have a target number, count how many rolls
87
- # in the tally are >= than this number, otherwise
88
- # we just add up all the numbers.
89
- if @options[:target] and @options[:target] > 0
90
- @total = results.count {|r| r >= @options[:target]}
91
- else
92
- # I think reduce(:+) is ugly, but it's very fast.
93
- @total = results.reduce(:+)
94
- end
95
-
96
- return self
97
- end
61
+ # Set the total.
62
+ handle_total
98
63
 
99
- # Returns the tally from the roll. This is the entire
100
- # tally, even if a :keep or :drop options were given.
101
- def tally()
102
- return @tally
64
+ self
103
65
  end
104
66
 
105
- # Gets the total of the last roll; if there is no
67
+ # Gets the total of the last roll; if there is no
106
68
  # last roll, it calls roll() first.
107
69
  def total
108
- self.roll() if @total.nil?
109
- return @total
70
+ roll if @total.nil?
71
+
72
+ @total
73
+ end
74
+
75
+ def <=>(other)
76
+ total <=> other.total
110
77
  end
111
78
 
112
- # This takes the @parts hash and recreates the xDx
113
- # string. Optionally, passing true to the method will
114
- # remove spaces form the finished string.
115
- def to_s(no_spaces=false)
116
- s = ""
117
-
118
- sp = no_spaces ? "" : " "
119
-
120
- s += self.count.to_s unless self.count.zero?
121
- s += "d"
122
- s += self.sides.to_s
123
-
124
- unless @options[:explode].zero?
125
- s += "#{sp}e"
126
- s += @options[:explode].to_s unless @options[:explode] == self.sides
79
+ private
80
+
81
+ def generate_results
82
+ @results = []
83
+
84
+ count.times do
85
+ r = roll_die
86
+
87
+ @results.push(r)
88
+
89
+ handle_explode(r) unless @options[:explode].zero?
127
90
  end
91
+ end
128
92
 
129
- s += "#{sp}t" + @options[:target].to_s unless @options[:target].zero?
130
- s += "#{sp}~" + @options[:drop].abs.to_s unless @options[:drop].zero?
131
- s += "#{sp}!" + @options[:keep].to_s unless @options[:keep].zero?
132
- s += "#{sp}r" + @options[:reroll].to_s unless @options[:reroll].zero?
93
+ def handle_explode(r)
94
+ while r >= @options[:explode]
95
+ r = roll_die
133
96
 
134
- return s
97
+ @results.push(r)
98
+ end
135
99
  end
136
100
 
137
- def <=>(other)
138
- return self.total <=> other.total
101
+ def handle_drop
102
+ return unless @options[:drop] > 0
103
+
104
+ # Note that we invert the drop value here.
105
+ range = 0...-(@options[:drop])
106
+
107
+ @results = @results.slice range
108
+ end
109
+
110
+ def handle_keep
111
+ return unless @options[:keep] > 0
112
+
113
+ range = 0...@options[:keep]
114
+
115
+ @results = @results.slice range
139
116
  end
140
- end
141
117
 
118
+ def handle_total
119
+ # If we have a target number, count how many rolls
120
+ # in the results are >= than this number, otherwise
121
+ # we just add up all the numbers.
122
+ @total = if @options[:target] && @options[:target] > 0
123
+ @results.count { |r| r >= @options[:target] }
124
+ else
125
+ # I think reduce(:+) is ugly, but it's very fast.
126
+ @results.reduce(:+)
127
+ end
128
+ end
129
+ end
142
130
  end
@@ -0,0 +1,63 @@
1
+ # Encoding: UTF-8
2
+
3
+ # This encapsulates the RollPart string
4
+ # generation methods.
5
+ module RollPartString
6
+ # This takes the @parts hash and recreates the xDx
7
+ # string. Optionally, passing true to the method will
8
+ # remove spaces form the finished string.
9
+ def to_s(no_spaces = false)
10
+ @string = ''
11
+ @space = (no_spaces ? '' : ' ')
12
+
13
+ to_s_xdx
14
+ to_s_explode
15
+ to_s_drop
16
+ to_s_keep
17
+ to_s_reroll
18
+ to_s_target
19
+
20
+ @string
21
+ end
22
+
23
+ private
24
+
25
+ def to_s_xdx
26
+ c = count.zero? ? '' : count.to_s
27
+ s = sides.to_s
28
+
29
+ @string += format('%sd%s', c, s)
30
+ end
31
+
32
+ def to_s_explode
33
+ return unless @options[:explode].zero?
34
+
35
+ e = (@options[:explode] == sides) ? @options[:explode] : ''
36
+
37
+ @string += format('%se%s', @space, e)
38
+ end
39
+
40
+ def to_s_drop
41
+ return unless @options[:drop].zero?
42
+
43
+ @string += format('%sd%d', @space, @options[:drop])
44
+ end
45
+
46
+ def to_s_keep
47
+ return unless @options[:keep].zero?
48
+
49
+ @string += format('%sk%s', @space, @options[:keep])
50
+ end
51
+
52
+ def to_s_reroll
53
+ return unless @options[:reroll].zero?
54
+
55
+ @string += format('%sr%s', @space, @options[:reroll])
56
+ end
57
+
58
+ def to_s_target
59
+ return unless @options[:target].zero?
60
+
61
+ @string += format('%st%s', @space, @options[:target])
62
+ end
63
+ end
@@ -0,0 +1,44 @@
1
+ # Encoding: UTF-8
2
+
3
+ # This encapsulates the Roll class' string
4
+ # generation methods.
5
+ module RollString
6
+ def to_s(no_spaces = false)
7
+ @string = ''
8
+ @space = no_spaces ? '' : ' '
9
+
10
+ to_s_tree
11
+
12
+ @string.strip
13
+ end
14
+
15
+ private
16
+
17
+ def to_s_tree
18
+ tree.each { |op, value| @string += send("to_s_#{op}", value) }
19
+ end
20
+
21
+ def to_s_label(value)
22
+ "#{value}#{@space}"
23
+ end
24
+
25
+ def to_s_start(value)
26
+ "#{value}#{@space}"
27
+ end
28
+
29
+ def to_s_add(value)
30
+ "+#{@space}#{value}#{@space}"
31
+ end
32
+
33
+ def to_s_sub(value)
34
+ "-#{@space}#{value}#{@space}"
35
+ end
36
+
37
+ def to_s_mul(value)
38
+ "*#{@space}#{value}#{@space}"
39
+ end
40
+
41
+ def to_s_div(value)
42
+ "/#{@space}#{value}#{@space}"
43
+ end
44
+ end
@@ -4,18 +4,18 @@ module DiceBag
4
4
  # it will be an instance of this class, which simply
5
5
  # returns the value given to it.
6
6
  class SimplePart
7
- attr :value
7
+ attr_reader :value
8
8
 
9
9
  def initialize(part)
10
10
  @value = part
11
11
  end
12
12
 
13
13
  def result
14
- return @value
14
+ value
15
15
  end
16
16
 
17
17
  def to_s
18
- return @value
18
+ value
19
19
  end
20
20
  end
21
21
  end
@@ -3,17 +3,16 @@ module DiceBag
3
3
  # of the dice string.
4
4
  class StaticPart < SimplePart
5
5
  def initialize(num)
6
- num = num.to_i() if num.is_a?(String)
6
+ num = num.to_i if num.is_a?(String)
7
7
  @value = num
8
8
  end
9
9
 
10
10
  def total
11
- return self.value
11
+ value
12
12
  end
13
13
 
14
14
  def to_s
15
- return self.value.to_s()
15
+ value.to_s
16
16
  end
17
17
  end
18
-
19
18
  end
@@ -1,25 +1,32 @@
1
+ # Encoding: UTF-8
2
+
3
+ # This continues definining the DiceBag module.
1
4
  module DiceBag
5
+ # This is the Transform subclass that takes the
6
+ # parsed tree and transforms it into its (almost)
7
+ # final form. (It gets a normalization pass later.)
2
8
  class Transform < Parslet::Transform
3
-
4
- def Transform.hashify_options(options)
9
+ def self.hashify_options(options)
5
10
  opts = {}
6
- options.each {|opt, val| opts[opt] = val} if options.is_a?(Array)
7
- return opts
11
+
12
+ options.each { |opt, val| opts[opt] = val } if options.is_a?(Array)
13
+
14
+ opts
8
15
  end
9
16
 
10
17
  # Option transforms. These are turned into an array of
11
18
  # 2-element arrays ('tagged arrays'), which is then
12
- # hashified later. (There is no way to update the
19
+ # hashified later. (There is no way to update the
13
20
  # options when these rules are matched.)
14
- rule(:drop => simple(:x)) { [:drop, Integer(x)] }
15
- rule(:keep => simple(:x)) { [:keep, Integer(x)] }
16
- rule(:reroll => simple(:x)) { [:reroll, Integer(x)] }
17
- rule(:target => simple(:x)) { [:target, Integer(x)] }
18
-
21
+ rule(drop: simple(:x)) { [:drop, Integer(x)] }
22
+ rule(keep: simple(:x)) { [:keep, Integer(x)] }
23
+ rule(reroll: simple(:x)) { [:reroll, Integer(x)] }
24
+ rule(target: simple(:x)) { [:target, Integer(x)] }
25
+
19
26
  # Explode is special, in that if it is nil, then it
20
27
  # must remain that way.
21
- rule(:explode => simple(:x)) do
22
- x.nil? ? [:explode, nil] : [:explode, Integer(x)]
28
+ rule(explode: simple(:x)) do
29
+ [:explode, (x ? Integer(x) : nil)]
23
30
  end
24
31
 
25
32
  # Parts {:ops => (:xdx | :number)}
@@ -27,52 +34,54 @@ module DiceBag
27
34
  # be matched before the xdx subtree.
28
35
 
29
36
  # Match an operator followed by a static number.
30
- rule(:op => simple(:o), :value => simple(:v)) do
37
+ # TODO: find out why this is not matching simple
38
+ # op => integers! -- 2016-04-18
39
+ rule(op: simple(:o), value: simple(:v)) do
31
40
  [String(o), Integer(v)]
32
41
  end
33
42
 
34
43
  # Match an operator followed by an :xdx subtree.
35
- rule(:op => simple(:o), :value => subtree(:part)) do
36
- [String(o),
37
- {
38
- :xdx => {
39
- :count => Integer(part[:xdx][:count]),
40
- :sides => Integer(part[:xdx][:sides])
41
- },
42
- :options => Transform.hashify_options(part[:options])
43
- }
44
- ]
44
+ rule(op: simple(:o), value: subtree(:part)) do
45
+ value = if part.is_a? Hash
46
+ count = Integer(part[:xdx][:count])
47
+ sides = Integer(part[:xdx][:sides])
48
+ options = Transform.hashify_options(part[:options])
49
+
50
+ { xdx: { count: count, sides: sides }, options: options }
51
+ else
52
+ Integer(part)
53
+ end
54
+
55
+ [String(o), value]
45
56
  end
46
57
 
47
58
  # Match a label by itself.
48
- rule(:label => simple(:s)) { {:label => String(s)} }
59
+ rule(label: simple(:s)) { { label: String(s) } }
49
60
 
50
61
  # Match a label followed by a :start subtree.
51
- rule(:label => simple(:s), :start => subtree(:part)) do
52
- [
53
- {:label => String(s)},
54
- {:start => {
55
- :xdx => part[:xdx],
56
- :options => Transform.hashify_options(part[:options])
57
- }
58
- }
59
- ]
62
+ rule(label: simple(:s), start: subtree(:part)) do
63
+ label = String(s)
64
+ xdx = part[:xdx]
65
+ options = Transform.hashify_options(part[:options])
66
+
67
+ [{ label: label }, { start: { xdx: xdx, options: options } }]
60
68
  end
61
69
 
62
70
  # Match a :start subtree, with the label not present.
63
71
  # Note that this returns a hash, but the final output
64
72
  # will still be in an array.
65
- rule(:start => subtree(:part)) do
66
- {:start => {
67
- :xdx => part[:xdx],
68
- :options => Transform.hashify_options(part[:options])
73
+ rule(start: subtree(:part)) do
74
+ {
75
+ start: {
76
+ xdx: part[:xdx],
77
+ options: Transform.hashify_options(part[:options])
69
78
  }
70
79
  }
71
80
  end
72
81
 
73
82
  # Convert the count and sides of an :xdx part.
74
- rule(:count => simple(:c), :sides => simple(:s)) do
75
- { :count => Integer(c), :sides => Integer(s) }
83
+ rule(count: simple(:c), sides: simple(:s)) do
84
+ { count: Integer(c), sides: Integer(s) }
76
85
  end
77
86
  end
78
87
  end
data/lib/dicebag.rb CHANGED
@@ -1,31 +1,33 @@
1
1
  # Copyright (c) 2012 Randy Carnahan <syn at dragonsbait dot com>
2
2
  #
3
- # Permission is hereby granted, free of charge, to any person obtaining
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the "Software"),
5
- # to deal in the Software without restriction, including without limitation
5
+ # to deal in the Software without restriction, including without limitation
6
6
  # the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
7
  # and/or sell copies of the Software, and to permit persons to whom the
8
8
  # Software is furnished to do so, subject to the following conditions:
9
9
  #
10
- # The above copyright notice and this permission notice shall be included
10
+ # The above copyright notice and this permission notice shall be included
11
11
  # in all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
16
- # NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
- # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18
- # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
16
+ # NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
19
19
  # USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
- # dicelib.rb -- version: 3.1.0
21
+ # dicelib.rb -- version: 3.2.0
22
22
 
23
23
  require 'parslet'
24
24
 
25
+ # This defined the main DiceBag module.
25
26
  module DiceBag
27
+ DEFAULT_ROLL = '1d6'.freeze
26
28
 
27
- DefaultRoll = "1d6"
28
-
29
+ # This is our generic DiceBagError
30
+ # exception subclass.
29
31
  class DiceBagError < Exception; end
30
32
 
31
33
  ###
@@ -38,148 +40,160 @@ module DiceBag
38
40
  # happens in the Roll class. It will convert all
39
41
  # values into the correct *Part class.
40
42
  def self.normalize_tree(tree)
41
- return tree.collect do |part|
42
-
43
+ tree.collect do |part|
43
44
  case part
44
45
  when Hash
45
- if part.has_key?(:label)
46
- part = [:label, LabelPart.new(part[:label])]
47
- elsif part.has_key?(:start)
48
- xdx = normalize_xdx(part[:start])
49
- part = [:start, RollPart.new(xdx)]
50
- end
51
-
46
+ normalize_hash part
52
47
  when Array
53
- # We swap out the strings for symbols.
54
- # If the op is not one of the arithimetic
55
- # operators, then the op itself is returned.
56
- # (This should only happen on :start arrays.)
57
-
58
- op = part.first
59
- val = part.last
60
-
61
- op = case op
62
- when "+" then :add
63
- when "-" then :sub
64
- when "*" then :mul
65
- when "/" then :div
66
- end
67
-
68
- # If the value is a hash, it's an :xdx hash.
69
- # Normalize it.
70
- if val.is_a?(Hash)
71
- xdx = normalize_xdx(val)
72
- val = RollPart.new(xdx)
73
- else
74
- val = StaticPart.new(val)
75
- end
76
-
77
- part = [op, val]
48
+ normalize_array part
49
+ else
50
+ part
78
51
  end
52
+ end
53
+ end
54
+
55
+ def self.normalize_hash(part)
56
+ return [:label, LabelPart.new(part[:label])] if part.key? :label
57
+
58
+ if part.key? :start
59
+ xdx = normalize_xdx(part[:start])
60
+
61
+ return [:start, RollPart.new(xdx)]
62
+ end
63
+
64
+ part
65
+ end
66
+
67
+ def self.normalize_array(part)
68
+ [normalize_op(part.first), normalize_value(part.last)]
69
+ end
79
70
 
80
- part
71
+ def self.normalize_op(op)
72
+ # We swap out the strings for symbols.
73
+ # If the op is not one of the arithimetic
74
+ # operators, then the op itself is returned.
75
+ # (This should only happen on :start arrays.)
76
+ case op
77
+ when '+' then :add
78
+ when '-' then :sub
79
+ when '*' then :mul
80
+ when '/' then :div
81
+ else
82
+ op
81
83
  end
82
84
  end
83
85
 
86
+ def self.normalize_value(val)
87
+ val.is_a?(Hash) ? RollPart.new(normalize_xdx(val)) : StaticPart.new(val)
88
+ end
89
+
84
90
  # This further massages the xDx hashes.
85
91
  def self.normalize_xdx(xdx)
86
92
  count = xdx[:xdx][:count]
87
93
  sides = xdx[:xdx][:sides]
88
- notes = []
94
+
95
+ # Delete the no longer needed :xdx key.
96
+ xdx.delete(:xdx)
89
97
 
90
98
  # Default to at least 1 die.
91
- count = 1 if count.zero? or count.nil?
99
+ count = 1 if count.zero? || count.nil?
92
100
 
93
101
  # Set the :count and :sides keys directly
94
- # and get rid of the :xdx sub-hash.
102
+ # and set the notes array.
95
103
  xdx[:count] = count
96
104
  xdx[:sides] = sides
97
- xdx.delete(:xdx)
105
+ xdx[:notes] = []
106
+
107
+ normalize_options xdx
108
+ end
98
109
 
110
+ def self.normalize_options(xdx)
99
111
  if xdx[:options].empty?
100
112
  xdx.delete(:options)
101
113
  else
102
- # VALIDATE ALL THE OPTIONS!!!
114
+ normalize_explode xdx
115
+ normalize_reroll xdx
116
+ normalize_drop_keep xdx
117
+ normalize_target xdx
118
+ end
103
119
 
104
- # Prevent Explosion abuse.
105
- if xdx[:options].has_key?(:explode)
106
- explode = xdx[:options][:explode]
120
+ xdx
121
+ end
107
122
 
108
- if explode.nil? or explode.zero? or explode == 1
109
- xdx[:options][:explode] = sides
110
- notes.push("Explode set to #{sides}")
111
- end
112
- end
123
+ # Prevent Explosion abuse.
124
+ def self.normalize_explode(xdx)
125
+ return unless xdx[:options].key? :explode
113
126
 
114
- # Prevent Reroll abuse.
115
- if xdx[:options].has_key?(:reroll) and xdx[:options][:reroll] >= sides
116
- xdx[:options][:reroll] = 0
117
- notes.push("Reroll reset to 0.")
118
- end
127
+ explode = xdx[:options][:explode]
119
128
 
120
- # Make sure there are enough dice to
121
- # handle both Drop and Keep values.
122
- # If not, both are reset to 0. Harsh.
123
- drop = xdx[:options][:drop] || 0
124
- keep = xdx[:options][:keep] || 0
129
+ if explode.nil? || explode.zero? || explode == 1
130
+ xdx[:options][:explode] = xdx[:sides]
125
131
 
126
- if (drop + keep) >= count
127
- xdx[:options][:drop] = 0
128
- xdx[:options][:keep] = 0
129
- notes.push("Drop and Keep Conflict. Both reset to 0.")
130
- end
132
+ xdx[:notes].push("Explode set to #{xdx[:sides]}")
133
+ end
134
+ end
131
135
 
132
- # Negate :drop. See why in RollPart#roll.
133
- xdx[:options][:drop] = -(drop)
136
+ # Prevent Reroll abuse.
137
+ def self.normalize_reroll(xdx)
138
+ return unless xdx[:options].key? :reroll
134
139
 
135
- # Finally, if we have a target number, make sure it is equal
136
- # to or less than the dice sides and greater than 0, otherwise,
137
- # set it to 0 (aka no target number) and add a note.
138
- if xdx[:options].has_key?(:target)
139
- target = xdx[:options][:target]
140
+ if xdx[:options][:reroll] >= xdx[:sides]
141
+ xdx[:options][:reroll] = 0
140
142
 
141
- if target > sides or target < 0
142
- xdx[:options][:target] = 0
143
- notes.push("Target number too large or is negative; reset to 0.")
144
- end
145
- end
143
+ xdx[:notes].push 'Reroll reset to 0.'
146
144
  end
145
+ end
147
146
 
148
- xdx[:notes] = notes unless notes.empty?
147
+ # Make sure there are enough dice to
148
+ # handle both Drop and Keep values.
149
+ # If not, both are reset to 0. Harsh.
150
+ def self.normalize_drop_keep(xdx)
151
+ drop = xdx[:options].fetch(:drop, 0)
152
+ keep = xdx[:options].fetch(:keep, 0)
149
153
 
150
- return xdx
154
+ if (drop + keep) >= xdx[:count]
155
+ xdx[:options][:drop] = 0
156
+ xdx[:options][:keep] = 0
157
+
158
+ xdx[:notes].push 'Drop and Keep Conflict. Both reset to 0.'
159
+ end
160
+ end
161
+
162
+ # Finally, if we have a target number, make sure it is equal
163
+ # to or less than the dice sides and greater than 0, otherwise,
164
+ # set it to 0 (aka no target number) and add a note.
165
+ def self.normalize_target(xdx)
166
+ return unless xdx[:options].key? :target
167
+
168
+ target = xdx[:options][:target]
169
+
170
+ return if target >= 0 && target <= xdx[:sides]
171
+
172
+ xdx[:options][:target] = 0
173
+
174
+ xdx[:notes].push 'Target number too large or is negative; reset to 0.'
151
175
  end
152
176
 
153
177
  # This is the wrapper for the parse, transform,
154
178
  # and normalize calls. This is called by the Roll
155
179
  # class, but may be called to get the raw returned
156
180
  # array of parsed bits for other purposes.
157
- def self.parse(dstr="")
158
- begin
159
- tree = Parser.new.parse(dstr)
160
- ast = Transform.new.apply(tree)
161
-
162
- # Sometimes, we get a hash back, so wrap it as
163
- # a single element array.
164
- ast = [ast] unless ast.is_a?(Array)
165
-
166
- return normalize_tree(ast)
167
-
168
- rescue Parslet::ParseFailed => reason
169
- # We're merely re-wrapping the error here to
170
- # hide implementation from user who doesn't care
171
- # to read the source.
172
- raise DiceBagError, "Dice Parse Error for string: #{dstr}"
173
- end
174
- end
175
- end
176
-
177
- require 'dicebag/parser'
178
- require 'dicebag/transform'
179
- require 'dicebag/simple_part'
180
- require 'dicebag/label_part'
181
- require 'dicebag/static_part'
182
- require 'dicebag/roll_part'
183
- require 'dicebag/roll'
184
- require 'dicebag/result'
181
+ def self.parse(dstr = '')
182
+ tree = Parser.new.parse(dstr)
183
+ ast = Transform.new.apply(tree)
185
184
 
185
+ normalize_tree ast
186
+ end
187
+ end
188
+
189
+ # Our sub-modules.
190
+ require_relative './dicebag/roll_string'
191
+ require_relative './dicebag/roll_part_string'
192
+ require_relative './dicebag/parser'
193
+ require_relative './dicebag/transform'
194
+ require_relative './dicebag/simple_part'
195
+ require_relative './dicebag/label_part'
196
+ require_relative './dicebag/static_part'
197
+ require_relative './dicebag/roll_part'
198
+ require_relative './dicebag/roll'
199
+ require_relative './dicebag/result'
metadata CHANGED
@@ -1,30 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dicebag
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
5
- prerelease:
4
+ version: 3.2.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - SynTruth
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-06-21 00:00:00.000000000 Z
11
+ date: 2016-04-18 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: parslet
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - ">="
20
18
  - !ruby/object:Gem::Version
21
19
  version: 1.4.0
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: 1.4.0
30
27
  description: A very flexible dice rolling library for Ruby.
@@ -33,37 +30,38 @@ executables: []
33
30
  extensions: []
34
31
  extra_rdoc_files: []
35
32
  files:
33
+ - lib/dicebag.rb
36
34
  - lib/dicebag/label_part.rb
37
35
  - lib/dicebag/parser.rb
38
36
  - lib/dicebag/result.rb
39
37
  - lib/dicebag/roll.rb
40
38
  - lib/dicebag/roll_part.rb
39
+ - lib/dicebag/roll_part_string.rb
40
+ - lib/dicebag/roll_string.rb
41
41
  - lib/dicebag/simple_part.rb
42
42
  - lib/dicebag/static_part.rb
43
43
  - lib/dicebag/transform.rb
44
- - lib/dicebag.rb
45
44
  homepage: https://github.com/syntruth/Dice-Bag
46
45
  licenses: []
46
+ metadata: {}
47
47
  post_install_message:
48
48
  rdoc_options: []
49
49
  require_paths:
50
50
  - lib
51
51
  required_ruby_version: !ruby/object:Gem::Requirement
52
- none: false
53
52
  requirements:
54
- - - ! '>='
53
+ - - ">="
55
54
  - !ruby/object:Gem::Version
56
55
  version: '0'
57
56
  required_rubygems_version: !ruby/object:Gem::Requirement
58
- none: false
59
57
  requirements:
60
- - - ! '>='
58
+ - - ">="
61
59
  - !ruby/object:Gem::Version
62
60
  version: '0'
63
61
  requirements: []
64
62
  rubyforge_project:
65
- rubygems_version: 1.8.23
63
+ rubygems_version: 2.4.5.1
66
64
  signing_key:
67
- specification_version: 3
68
- summary: ! 'Dice Bag: Ruby Dice Rolling Library'
65
+ specification_version: 4
66
+ summary: 'Dice Bag: Ruby Dice Rolling Library'
69
67
  test_files: []