dicebag 3.0.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.
- data/lib/dicebag.rb +168 -0
- data/lib/dicebag/label_part.rb +9 -0
- data/lib/dicebag/parser.rb +86 -0
- data/lib/dicebag/result.rb +29 -0
- data/lib/dicebag/roll.rb +98 -0
- data/lib/dicebag/roll_part.rb +133 -0
- data/lib/dicebag/simple_part.rb +21 -0
- data/lib/dicebag/static_part.rb +19 -0
- data/lib/dicebag/transform.rb +77 -0
- metadata +83 -0
data/lib/dicebag.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
# Copyright (c) 2012 Randy Carnahan <syn at dragonsbait dot com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the "Software"),
|
5
|
+
# to deal in the Software without restriction, including without limitation
|
6
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
7
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
8
|
+
# Software is furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included
|
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
|
19
|
+
# USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
#
|
21
|
+
# dicelib.rb -- version: 3.0.1
|
22
|
+
|
23
|
+
require 'parslet'
|
24
|
+
|
25
|
+
module DiceBag
|
26
|
+
|
27
|
+
DefaultRoll = "1d6"
|
28
|
+
|
29
|
+
class DiceBagError < Exception; end
|
30
|
+
|
31
|
+
###
|
32
|
+
# Module Methods
|
33
|
+
###
|
34
|
+
|
35
|
+
# This takes the parsed tree, AFTER it has
|
36
|
+
# been through the Transform class, and massages
|
37
|
+
# the data a bit more, to ease the iteration that
|
38
|
+
# happens in the Roll class. It will convert all
|
39
|
+
# values into the correct *Part class.
|
40
|
+
def self.normalize_tree(tree)
|
41
|
+
return tree.collect do |part|
|
42
|
+
|
43
|
+
case part
|
44
|
+
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
|
+
|
52
|
+
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
|
+
op = case part.first
|
58
|
+
when "+" then :add
|
59
|
+
when "-" then :sub
|
60
|
+
when "*" then :mul
|
61
|
+
when "/" then :div
|
62
|
+
else part.first
|
63
|
+
end
|
64
|
+
|
65
|
+
val = part.last
|
66
|
+
|
67
|
+
# If the value is a hash, it's an :xdx hash.
|
68
|
+
# Normalize it.
|
69
|
+
if val.is_a?(Hash)
|
70
|
+
xdx = normalize_xdx(val)
|
71
|
+
val = RollPart.new(xdx)
|
72
|
+
else
|
73
|
+
val = StaticPart.new(val)
|
74
|
+
end
|
75
|
+
|
76
|
+
part = [op, val]
|
77
|
+
end
|
78
|
+
|
79
|
+
part
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# This further massages the xDx hashes.
|
84
|
+
def self.normalize_xdx(xdx)
|
85
|
+
count = xdx[:xdx][:count]
|
86
|
+
sides = xdx[:xdx][:sides]
|
87
|
+
notes = []
|
88
|
+
|
89
|
+
# Default to at least 1 die.
|
90
|
+
count = 1 if count.zero? or count.nil?
|
91
|
+
|
92
|
+
# Set the :count and :sides keys directly
|
93
|
+
# and get ride of the :xdx sub-hash.
|
94
|
+
xdx[:count] = count
|
95
|
+
xdx[:sides] = sides
|
96
|
+
xdx.delete(:xdx)
|
97
|
+
|
98
|
+
if xdx[:options].empty?
|
99
|
+
xdx.delete(:options)
|
100
|
+
else
|
101
|
+
# VALIDATE ALL THE OPTIONS!!!
|
102
|
+
|
103
|
+
# Prevent Explosion abuse.
|
104
|
+
if xdx[:options].has_key?(:explode)
|
105
|
+
explode = xdx[:options][:explode]
|
106
|
+
|
107
|
+
if explode.nil? or explode.zero? or explode == 1
|
108
|
+
xdx[:options][:explode] = sides
|
109
|
+
notes.push("Explode set to #{sides}")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Prevent Reroll abuse.
|
114
|
+
if xdx[:options].has_key?(:reroll) and xdx[:options][:reroll] >= sides
|
115
|
+
xdx[:options][:reroll] = 0
|
116
|
+
notes.push("Reroll reset to 0.")
|
117
|
+
end
|
118
|
+
|
119
|
+
# Make sure there are enough dice to
|
120
|
+
# handle both Drop and Keep values.
|
121
|
+
# If not, both are reset to 0. Harsh.
|
122
|
+
drop = xdx[:options][:drop] || 0
|
123
|
+
keep = xdx[:options][:keep] || 0
|
124
|
+
|
125
|
+
if (drop + keep) >= count
|
126
|
+
xdx[:options][:drop] = 0
|
127
|
+
xdx[:options][:keep] = 0
|
128
|
+
notes.push("Drop and Keep Conflict. Both reset to 0.")
|
129
|
+
end
|
130
|
+
|
131
|
+
# Negate :drop. See why in RollPart#roll.
|
132
|
+
xdx[:options][:drop] = -(drop)
|
133
|
+
end
|
134
|
+
|
135
|
+
xdx[:notes] = notes unless notes.empty?
|
136
|
+
|
137
|
+
return xdx
|
138
|
+
end
|
139
|
+
|
140
|
+
# This is the wrapper for the parse, transform,
|
141
|
+
# and normalize calls. This is called by the Roll
|
142
|
+
# class, but may be called to get the raw returned
|
143
|
+
# array of parsed bits for other purposes.
|
144
|
+
def self.parse(dstr="")
|
145
|
+
begin
|
146
|
+
tree = Parser.new.parse(dstr)
|
147
|
+
ast = Transform.new.apply(tree)
|
148
|
+
|
149
|
+
return normalize_tree(ast)
|
150
|
+
|
151
|
+
rescue Parslet::ParseFailed => reason
|
152
|
+
# We're merely re-wrapping the error here to
|
153
|
+
# hide implementation from user who doesn't care
|
154
|
+
# to read the source.
|
155
|
+
raise DiceBagError, "Dice Parse Error for string: #{dstr}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
require 'dicebag/parser'
|
161
|
+
require 'dicebag/transform'
|
162
|
+
require 'dicebag/simple_part'
|
163
|
+
require 'dicebag/label_part'
|
164
|
+
require 'dicebag/static_part'
|
165
|
+
require 'dicebag/roll_part'
|
166
|
+
require 'dicebag/roll'
|
167
|
+
require 'dicebag/result'
|
168
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module DiceBag
|
2
|
+
class Parser < Parslet::Parser
|
3
|
+
# Base rules.
|
4
|
+
rule(:space?) { str(' ').repeat }
|
5
|
+
|
6
|
+
# Numbers are limited to 3 digit places. Why?
|
7
|
+
# To prevent abuse from people rolling:
|
8
|
+
# 999999999D999999999 and 'DOS'-ing the app.
|
9
|
+
rule(:number) { match('[0-9]').repeat(1,3) }
|
10
|
+
rule(:number?) { number.maybe }
|
11
|
+
|
12
|
+
# Label rule
|
13
|
+
# Labels must match '(<some text here>)' and
|
14
|
+
# are not allowed to have commas in the label.
|
15
|
+
# This for future use of parsing multiple dice
|
16
|
+
# definitions in comma-separated strings.
|
17
|
+
# The :label matches anything that ISN'T a
|
18
|
+
# parenethesis or a comma.
|
19
|
+
rule(:lparen) { str('(') }
|
20
|
+
rule(:rparen) { str(')') }
|
21
|
+
rule(:label) do
|
22
|
+
lparen >>
|
23
|
+
match('[^(),]').repeat(1).as(:label) >>
|
24
|
+
rparen >>
|
25
|
+
space?
|
26
|
+
end
|
27
|
+
|
28
|
+
# count and sides rules.
|
29
|
+
# :count is allowed to be nil, which will default
|
30
|
+
# to 1.
|
31
|
+
rule(:count) { number?.as(:count) }
|
32
|
+
rule(:sides) { match('[dD]') >> number.as(:sides) }
|
33
|
+
|
34
|
+
# xDx Parts.
|
35
|
+
# All xDx parts may be followed by none, one, or more
|
36
|
+
# options.
|
37
|
+
rule(:xdx) { (count >> sides).as(:xdx) >> options? }
|
38
|
+
|
39
|
+
# xdx Options.
|
40
|
+
# Note that :explode is allowed to NOT have a number
|
41
|
+
# assigned, which will leave it with a nil value.
|
42
|
+
# This is handled in RollPart#initialize.
|
43
|
+
rule(:explode) { str('e') >> number?.as(:explode) >> space? }
|
44
|
+
rule(:drop) { str('~') >> number.as(:drop) >> space? }
|
45
|
+
rule(:keep) { str('!') >> number.as(:keep) >> space? }
|
46
|
+
rule(:reroll) { str('r') >> number.as(:reroll) >> space? }
|
47
|
+
|
48
|
+
# This allows options to be defined in any order and
|
49
|
+
# even have more than one of the same option, however
|
50
|
+
# only the last option of a given key will be kept.
|
51
|
+
rule(:options) {
|
52
|
+
space? >> (drop | explode | keep | reroll).repeat >> space?
|
53
|
+
}
|
54
|
+
|
55
|
+
rule(:options?) { options.maybe.as(:options) }
|
56
|
+
|
57
|
+
# Part Operators.
|
58
|
+
rule(:add) { str('+') }
|
59
|
+
rule(:sub) { str('-') }
|
60
|
+
rule(:mul) { str('*') }
|
61
|
+
rule(:div) { str('/') }
|
62
|
+
rule(:op) { (add | sub | mul | div).as(:op) }
|
63
|
+
|
64
|
+
# Part Rule
|
65
|
+
# A part is an operator, followed by either an xDx
|
66
|
+
# string or a static number value.
|
67
|
+
rule(:part) do
|
68
|
+
space? >>
|
69
|
+
op >>
|
70
|
+
space? >>
|
71
|
+
(xdx | number).as(:value) >>
|
72
|
+
space?
|
73
|
+
end
|
74
|
+
|
75
|
+
# All parts of a dice roll MUST start with an xDx
|
76
|
+
# string and then followed by any optional parts.
|
77
|
+
# The first xDx string is labeled as :start.
|
78
|
+
rule(:parts) { xdx.as(:start) >> part.repeat }
|
79
|
+
|
80
|
+
# A dice string is an optional label, followed by
|
81
|
+
# the defined parts.
|
82
|
+
rule(:dice) { label.maybe >> parts }
|
83
|
+
|
84
|
+
root(:dice)
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module DiceBag
|
2
|
+
# This class merely encapsulates the result,
|
3
|
+
# providing convience methods to access the
|
4
|
+
# results of each section if desired.
|
5
|
+
class Result
|
6
|
+
attr_reader :label
|
7
|
+
attr_reader :total
|
8
|
+
attr_reader :sections
|
9
|
+
|
10
|
+
def initialize(label, total, sections)
|
11
|
+
@label = label
|
12
|
+
@total = total
|
13
|
+
@sections = sections
|
14
|
+
end
|
15
|
+
|
16
|
+
def each(&block)
|
17
|
+
self.sections.each do |section|
|
18
|
+
yield section
|
19
|
+
end
|
20
|
+
return nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
return "#{self.label}: #{self.total}" unless self.label.empty?
|
25
|
+
return self.total.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/dicebag/roll.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
module DiceBag
|
2
|
+
# This is the 'main' class of Dice Bag. This class
|
3
|
+
# takes the dice string, parses it, and encapsulates
|
4
|
+
# the actual rolling of the dice. If no dice string
|
5
|
+
# is given, it defaults to DefaultRoll.
|
6
|
+
class Roll
|
7
|
+
attr :dstr
|
8
|
+
attr :tree
|
9
|
+
|
10
|
+
alias :parsed :tree
|
11
|
+
|
12
|
+
def initialize(dstr=nil)
|
13
|
+
@dstr = dstr ||= DefaultRoll
|
14
|
+
@tree = DiceBag.parse(dstr)
|
15
|
+
@result = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def notes
|
19
|
+
s = ""
|
20
|
+
|
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
|
26
|
+
end
|
27
|
+
|
28
|
+
return s
|
29
|
+
end
|
30
|
+
|
31
|
+
def result
|
32
|
+
self.roll() unless @result
|
33
|
+
return @result
|
34
|
+
end
|
35
|
+
|
36
|
+
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
|
66
|
+
|
67
|
+
@result = Result.new(label, total, sections)
|
68
|
+
|
69
|
+
return @result
|
70
|
+
end
|
71
|
+
|
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}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
return s.strip
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module DiceBag
|
2
|
+
# This represents the xDx part of the dice string.
|
3
|
+
class RollPart < SimplePart
|
4
|
+
|
5
|
+
attr :count
|
6
|
+
attr :sides
|
7
|
+
attr :parts
|
8
|
+
attr :options
|
9
|
+
|
10
|
+
def initialize(part)
|
11
|
+
@total = nil
|
12
|
+
@tally = []
|
13
|
+
@value = part
|
14
|
+
@count = part[:count]
|
15
|
+
@sides = part[:sides]
|
16
|
+
@notes = part[:notes] || []
|
17
|
+
|
18
|
+
# Our Default Options
|
19
|
+
@options = {
|
20
|
+
:explode => 0,
|
21
|
+
:drop => 0,
|
22
|
+
:keep => 0,
|
23
|
+
:reroll => 0
|
24
|
+
}
|
25
|
+
|
26
|
+
@options.update(part[:options]) if part.has_key?(:options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def notes
|
30
|
+
return @notes.join("\n") unless @notes.empty?
|
31
|
+
return ""
|
32
|
+
end
|
33
|
+
|
34
|
+
# Checks to see if this instance has rolled yet
|
35
|
+
# or not.
|
36
|
+
def has_rolled?
|
37
|
+
return @total.nil? ? false : true
|
38
|
+
end
|
39
|
+
|
40
|
+
# Rolls a single die from the xDx string.
|
41
|
+
def roll_die()
|
42
|
+
num = 0
|
43
|
+
reroll = @options[:reroll]
|
44
|
+
|
45
|
+
while num <= reroll
|
46
|
+
num = rand(self.sides) + 1
|
47
|
+
end
|
48
|
+
|
49
|
+
return num
|
50
|
+
end
|
51
|
+
|
52
|
+
def roll
|
53
|
+
results = []
|
54
|
+
explode = @options[:explode]
|
55
|
+
|
56
|
+
self.count.times do
|
57
|
+
roll = self.roll_die()
|
58
|
+
|
59
|
+
results.push(roll)
|
60
|
+
|
61
|
+
unless explode.zero?
|
62
|
+
while roll >= explode
|
63
|
+
roll = self.roll_die()
|
64
|
+
results.push(roll)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
results.sort!
|
70
|
+
results.reverse!
|
71
|
+
|
72
|
+
# Save the tally in case it's requested later.
|
73
|
+
@tally = results.dup()
|
74
|
+
|
75
|
+
# Drop the low end numbers if :drop is less than zero.
|
76
|
+
if @options[:drop] < 0
|
77
|
+
results = results[0 ... @options[:drop]]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Keep the high end numbers if :keep is greater than zero.
|
81
|
+
if @options[:keep] > 0
|
82
|
+
results = results[0 ... @options[:keep]]
|
83
|
+
end
|
84
|
+
|
85
|
+
# I think reduce(:+) is ugly, but it's very fast.
|
86
|
+
@total = results.reduce(:+)
|
87
|
+
|
88
|
+
return self
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns the tally from the roll. This is the entire
|
92
|
+
# tally, even if a :keep or :drop options were given.
|
93
|
+
def tally()
|
94
|
+
return @tally
|
95
|
+
end
|
96
|
+
|
97
|
+
# Gets the total of the last roll; if there is no
|
98
|
+
# last roll, it calls roll() first.
|
99
|
+
def total
|
100
|
+
self.roll() if @total.nil?
|
101
|
+
return @total
|
102
|
+
end
|
103
|
+
|
104
|
+
# This takes the @parts hash and recreates the xDx
|
105
|
+
# string. Optionally, passing true to the method will
|
106
|
+
# remove spaces form the finished string.
|
107
|
+
def to_s(no_spaces=false)
|
108
|
+
s = ""
|
109
|
+
|
110
|
+
sp = no_spaces ? "" : " "
|
111
|
+
|
112
|
+
s += self.count.to_s unless self.count.zero?
|
113
|
+
s += "d"
|
114
|
+
s += self.sides.to_s
|
115
|
+
|
116
|
+
unless @options[:explode].zero?
|
117
|
+
s += "#{sp}e"
|
118
|
+
s += @options[:explode].to_s unless @options[:explode] == self.sides
|
119
|
+
end
|
120
|
+
|
121
|
+
s += "#{sp}~" + @options[:drop].abs.to_s unless @options[:drop].zero?
|
122
|
+
s += "#{sp}!" + @options[:keep].to_s unless @options[:keep].zero?
|
123
|
+
s += "#{sp}r" + @options[:reroll].to_s unless @options[:reroll].zero?
|
124
|
+
|
125
|
+
return s
|
126
|
+
end
|
127
|
+
|
128
|
+
def <=>(other)
|
129
|
+
return self.total <=> other.total
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DiceBag
|
2
|
+
# The most simplest of a part. If a given part of
|
3
|
+
# a dice string is not a Label, Fixnum, or a xDx part
|
4
|
+
# it will be an instance of this class, which simply
|
5
|
+
# returns the value given to it.
|
6
|
+
class SimplePart
|
7
|
+
attr :value
|
8
|
+
|
9
|
+
def initialize(part)
|
10
|
+
@value = part
|
11
|
+
end
|
12
|
+
|
13
|
+
def result
|
14
|
+
return @value
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
return @value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module DiceBag
|
2
|
+
# This represents a static, non-random number part
|
3
|
+
# of the dice string.
|
4
|
+
class StaticPart < SimplePart
|
5
|
+
def initialize(num)
|
6
|
+
num = num.to_i() if num.is_a?(String)
|
7
|
+
@value = num
|
8
|
+
end
|
9
|
+
|
10
|
+
def total
|
11
|
+
return self.value
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
return self.value.to_s()
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module DiceBag
|
2
|
+
class Transform < Parslet::Transform
|
3
|
+
|
4
|
+
def Transform.hashify_options(options)
|
5
|
+
opts = {}
|
6
|
+
options.each {|opt, val| opts[opt] = val} unless options.is_a?(Hash)
|
7
|
+
return opts
|
8
|
+
end
|
9
|
+
|
10
|
+
# Option transforms. These are turned into an array of
|
11
|
+
# 2-element arrays ('tagged arrays'), which is then
|
12
|
+
# hashified later. (There is no way to update the
|
13
|
+
# 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
|
+
|
18
|
+
# Explode is special, in that if it is nil, then it
|
19
|
+
# must remain that way.
|
20
|
+
rule(:explode => simple(:x)) do
|
21
|
+
x.nil? ? [:explode, nil] : [:explode, Integer(x)]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Parts {:ops => (:xdx | :number)}
|
25
|
+
# These are first-match, so the simple number will
|
26
|
+
# be matched before the xdx subtree.
|
27
|
+
|
28
|
+
# Match an operator followed by a static number.
|
29
|
+
rule(:op => simple(:o), :value => simple(:v)) do
|
30
|
+
[String(o), Integer(v)]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Match an operator followed by an :xdx subtree.
|
34
|
+
rule(:op => simple(:o), :value => subtree(:part)) do
|
35
|
+
[String(o),
|
36
|
+
{
|
37
|
+
:xdx => {
|
38
|
+
:count => Integer(part[:xdx][:count]),
|
39
|
+
:sides => Integer(part[:xdx][:sides])
|
40
|
+
},
|
41
|
+
:options => Transform.hashify_options(part[:options])
|
42
|
+
}
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Match a label by itself.
|
47
|
+
rule(:label => simple(:s)) { {:label => String(s)} }
|
48
|
+
|
49
|
+
# Match a label followed by a :start subtree.
|
50
|
+
rule(:label => simple(:s), :start => subtree(:part)) do
|
51
|
+
[
|
52
|
+
{:label => String(s)},
|
53
|
+
{:start => {
|
54
|
+
:xdx => part[:xdx],
|
55
|
+
:options => Transform.hashify_options(part[:options])
|
56
|
+
}
|
57
|
+
}
|
58
|
+
]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Match a :start subtree, with the label not present.
|
62
|
+
# Note that this returns a hash, but the final output
|
63
|
+
# will still be in an array.
|
64
|
+
rule(:start => subtree(:part)) do
|
65
|
+
{:start => {
|
66
|
+
:xdx => part[:xdx],
|
67
|
+
:options => Transform.hashify_options(part[:options])
|
68
|
+
}
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
# Convert the count and sides of an :xdx part.
|
73
|
+
rule(:count => simple(:c), :sides => simple(:s)) do
|
74
|
+
{ :count => Integer(c), :sides => Integer(s) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dicebag
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 3
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
version: 3.0.2
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- SynTruth
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-06-21 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: parslet
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 4
|
30
|
+
- 0
|
31
|
+
version: 1.4.0
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description: A very flexible dice rolling library for Ruby.
|
35
|
+
email: syntruth@gmail.com
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files: []
|
41
|
+
|
42
|
+
files:
|
43
|
+
- lib/dicebag/label_part.rb
|
44
|
+
- lib/dicebag/parser.rb
|
45
|
+
- lib/dicebag/result.rb
|
46
|
+
- lib/dicebag/roll.rb
|
47
|
+
- lib/dicebag/roll_part.rb
|
48
|
+
- lib/dicebag/simple_part.rb
|
49
|
+
- lib/dicebag/static_part.rb
|
50
|
+
- lib/dicebag/transform.rb
|
51
|
+
- lib/dicebag.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: https://github.com/syntruth/Dice-Bag
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.3.6
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: "Dice Bag: Ruby Dice Rolling Library"
|
82
|
+
test_files: []
|
83
|
+
|