dicebag 3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|