From: Paul Novak Date: 2006-01-09T03:57:20+09:00 Subject: [QUIZ.SOLUTION] Dice Roller (#61) We don't need no steenking leexer/parsers ------=_Part_19248_25669739.1136746627816 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Content-Disposition: inline Leexer/parsers? We ain't got no Leexer/parsers. We don't need no Leexer/parsers. I don't have to show you any steenking Leexer/parser.=20 Just call eval and use Ruby's fine lexer/parser (apologies to Mel Brooks, John Huston and Banditos Mexicanos everywhere). This approach uses regex substitutions to first munge the input expression to deal with the default cases (like d6 to 1d6 and 1% to 1d100), then it substitutes ** for d and hands it over to the evaluator and prints the result. Eval does what we want after an override of Fixnum.** to give us our dice-rolling d-operator behavior. Conveniently, ** has the desired precedence relative to the other operators, plus it is binary and left-associative. This feels so evil. Seduced by the Dark Side I am. Note that we get lot's of additional behavior we don't need, but the original spec said the BNF was incomplete, so I was lazy and left the undef[ining] of >>, << et al is 'as an exercise...' I don't know enough D&D to know if they might be useful. BTW. In the spirit of 'why can't we all just get along?': the dice rolling algorithm is ported from this Python code at http://www.onlamp.com/pub/a/python/2002/07/11/recipes.html?page=3D3 from random import randrange def dice(num,sides): =09return reduce(lambda x,y,s=3Dsides:x + randrange(s), range(num+1))+num here is the Ruby: #!/usr/bin/env ruby # # roll.rb # # fix up Fixnum to override ** with our desired d behavior class Fixnum def ** (sides) # validation if sides<1 raise "Invalid sides value: '#{sides}', must be a positive Integer" end if self<1 raise "Invalid number of rolls: '#{self}', must be a postitive Integ= er" end # roll the dice (0..self).to_a.inject{|x,y| x + rand(sides)}+self end end dice_expression =3D ARGV[0] # default number of rolls is 1, substitute d6 =3D> 1d6 dice_expression =3D dice_expression.gsub(/(^|[^0-9)\s])(\s*d)/, '\11d') # d% =3D> d100 dice_expression =3D dice_expression.gsub(/d%/,'d100 ') # this feels so dirty...substitute d =3D> ** dice_expression =3D dice_expression.gsub(/d/, "**") (ARGV[1] || 1).to_i.times { print "#{eval(dice_expression)} " } ------=_Part_19248_25669739.1136746627816 Content-Type: application/octet-stream; name=roll.rb Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="roll.rb" #!/usr/bin/env ruby # # roll.rb # # fix up Fixnum to override ** with our desired d behavior class Fixnum def ** (sides) # validation if sides<1 raise "Invalid sides value: '#{sides}', must be a positive Integer" end if self<1 raise "Invalid number of rolls: '#{self}', must be a postitive Integer" end # roll the dice (0..self).to_a.inject{|x,y| x + rand(sides)}+self end end dice_expression = ARGV[0] # default number of rolls is 1, substitute d6 => 1d6 dice_expression = dice_expression.gsub(/(^|[^0-9)\s])(\s*d)/, '\11d') # d% => d100 dice_expression = dice_expression.gsub(/d%/,'d100 ') # this feels so dirty...substitute d => ** dice_expression = dice_expression.gsub(/d/, "**") (ARGV[1] || 1).to_i.times { print "#{eval(dice_expression)} " } ------=_Part_19248_25669739.1136746627816--