dicebag 3.1.0 → 3.2.0

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