Here's my solution.
I spent a few hours writing a BNF parser which was supposed to let me do this:
-- begin buggy code --
CENT = BnfTerm.new(/(%)/ ) { '100' }
INTEGER = /([1-9][0-9]*)/
DICE = BnfTerm.new(CENT,:|,INTEGER)
term = BnfTerm.new()
ROLL = BnfTerm.new(term, /d/, DICE) {|a,b|
(1..a.to_i).inject(0){|s,i|s+rand(b.to_i)+1} }
term.define(DICE, :|,ROLL) {|m| m}
#...
class Dice
@@rule = DIEROLL
def initialize expr
@expr = expr
end
def roll
@@rule.parse(@expr)
end
end
-- end --
but it was too brittle, and it would go into endless recursion on a
lot of valid inputs.
So I switched to a quick,short simple solution: add a #d method to
integer and let eval do the work:
--- dice.rb --
class Integer
def d n
(0...self).inject(0){|s,i| s+rand(n)+1}
end
end
class Dice
def initialize str
@rule= str.gsub(/%/,'100').gsub(/([^\d)]|^)d/,'\1 1d') # %->100
and bare d ->1d
while @rule.gsub!(/([^.])d(\d+|\(.*\))/,'\1.d(\2)') #
'dX' -> '.d(X)'
end
#repeat to deal with nesting
end
def roll
eval(@rule)
end
end
d = Dice.new(ARGV[0]||'d6')
(ARGV[1] || 1).to_i.times { print "#{d.roll} " }
puts
---
-Adam