Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > [SOLUTION] Dice Roller (#61)

Thread Tools

[SOLUTION] Dice Roller (#61)

John Earles
Posts: n/a
Hi!=A0 This is my first entry to a RUBY-QUIZ.=A0 Sure I could have opted =
to use
'eval', but I figured I might as well learn a little about expression
parsing.=A0 Fun stuff!
My entry is focused around The Shunting Yard Algorithm, allowing me to
parse, transform and evaluate in the same step.=A0 I do not keep the =
transform, so it is slightly inefficient to perform multiple 'rolls'.=A0 =
is an optimization for another day.
I also got to try out some new regexp stuff.=A0 Of particular note is =
the use
of (?=3Dd) in the expression used to search for d's that need an =
implicit 1
lvalue.=A0 I was having problems with 5dddd7 type commands until I =
this allowed the target d NOT to be consumed by the regexp.
As a final note I am a Ruby Newbie, and a Java developer by day, so any =
or comments on my coding would be appreciated.=A0 Thanks!
- John
$DEBUG =3D false
# Dice Roller entry point
def roll_dice( dice_command, roll_count )
=A0 begin
=A0=A0=A0 puts "Executing #{roll_count} roll(s) of #{dice_command}"
=A0=A0=A0 results, total =3D dice_command ).roll( roll_count )
=A0=A0=A0 puts "Result: [#{results.join(', ')}] =3D> #{total}"=A0=20
=A0 rescue Exception =3D> e
=A0=A0=A0 puts "Roll error: #{e}"
=A0 end
class Dice=A0=20
=A0 # operator =3D> [precendence, associativity]
=A0 @@operators =3D { "d" =3D> [3, :right],
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A 0 "*" =3D> [2, :left] =
, "/" =3D> [2, :left],
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A 0 "+" =3D> [1, :left] =
, "-" =3D> [1, :left]
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 }
=A0 # Initialize the Stacks and load the dice instructions
=A0 def initialize( dice_command )=A0=A0=A0=20
=A0=A0=A0 if $DEBUG
=A0=A0=A0=A0=A0 alias :d :dnd_roll_loaded
=A0=A0=A0 else
=A0=A0=A0=A0=A0 alias :d :dnd_roll_random
=A0=A0=A0 end
=A0=A0=A0 @operator_stack, @value_stack =3D [], []
=A0=A0=A0 prepare_instructions( dice_command )
=A0 end
=A0 def roll( roll_count )
=A0=A0=A0 results =3D (1..roll_count).collect { execute }
=A0=A0=A0 [results, results.inject {|sum, item| sum + item } || 0]
=A0 end=A0=20
=A0 private
=A0 # The infix command is parsed into tokens and then executed using
=A0=A0# The Shunting Yard Algorithm. Evaluation is done "on-the-fly" as
=A0 # items are placed on the value stack (acting as the post-fix =
=A0 def execute
=A0=A0=A0 @operator_stack.clear
=A0=A0=A0 @value_stack.clear
=A0=A0=A0 # Process the tokens in L -> R order
=A0=A0=A0 # Look for non-digit characters and numbers
=A0=A0=A0 @instructions.scan(/\D|\d+/) do | token |
=A0=A0=A0=A0=A0 case token
=A0=A0=A0=A0=A0=A0=A0 when "("
=A0=A0=A0=A0=A0=A0=A0=A0=A0 @operator_stack.push token
=A0=A0=A0=A0=A0=A0=A0 when /\d+/ # any number
=A0=A0=A0=A0=A0=A0=A0=A0=A0 @value_stack.push token.to_i
=A0=A0=A0=A0=A0=A0=A0 when /[-\+*\/d]/ # the operators
=A0=A0=A0=A0=A0=A0=A0=A0=A0 finished =3D false
=A0=A0=A0=A0=A0=A0=A0=A0=A0 until finished or @operator_stack.empty?
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 if higher_operator(token)
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 finished =3D true
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 else
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 resolve_expression
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 end
=A0=A0=A0=A0=A0=A0=A0=A0=A0 end
=A0=A0=A0=A0=A0 =A0=A0=A0=A0@operator_stack.push token
=A0=A0=A0=A0=A0=A0=A0 when ")"
=A0=A0=A0=A0=A0=A0=A0=A0=A0 resolve_expression while =
@operator_stack.last !=3D "("
=A0=A0=A0=A0=A0=A0=A0=A0=A0 @operator_stack.pop
=A0=A0=A0=A0=A0=A0=A0 else
=A0=A0=A0=A0=A0=A0=A0=A0=A0 raise "Invalid token found: #{token}"
=A0=A0=A0=A0=A0 end
=A0=A0=A0 end
=A0=A0=A0 resolve_expression while !@operator_stack.empty?
=A0=A0=A0 raise "Unexpected problem. #{@value_stack.size} values remain =
execution." \
=A0=A0=A0=A0=A0 unless @value_stack.size =3D=3D 1
=A0=A0=A0 @value_stack.pop
=A0 end=A0=A0=A0=20
=A0 def resolve_expression
=A0=A0=A0 opr, rhv, lhv =3D @operator_stack.pop, @value_stack.pop, =
=A0=A0=A0 raise "No more values left for #{opr} to consume!" unless rhv =
&& lhv
=A0=A0=A0 value =3D (opr =3D=3D "d") ? value =3D d( lhv, rhv ) : =
lhv.send( opr, rhv )
=A0=A0=A0 @value_stack.push value.to_i
=A0 end
=A0 def dnd_roll_random( roll_count, die_value )
=A0=A0=A0 (1..roll_count).inject(0) { |value, item| value + ( =
rand(die_value) + 1
) }
=A0 end
=A0 def dnd_roll_loaded( roll_count, die_value )
=A0=A0=A0 roll_count * die_value
=A0 end
=A0 def higher_operator(opr)
=A0=A0=A0 if associativity(opr) =3D=3D :left
=A0=A0=A0=A0=A0 precedence(opr) > precedence(@operator_stack.last)
=A0=A0=A0 else
=A0=A0=A0=A0=A0 precedence(opr) >=3D =
=A0=A0=A0 end
=A0 end
=A0 def precedence(opr)
=A0=A0=A0 @@operators[opr] ? @@operators[opr][0] : 0
=A0 end
=A0 def associativity(opr)
=A0=A0=A0 @@operators[opr] ? @@operators[opr][1] : :left
=A0 end
=A0 def prepare_instructions( dice_command )
=A0=A0=A0 # 1) Eliminate all whitespace.
=A0=A0=A0 # 2) Substitute d100 for d%=20
=A0=A0=A0 # 3) Insert the implied 1 if a d is the first character
=A0 =A0=A0#=A0=A0=A0 or is preceded by an operator other than ')'
=A0=A0=A0 @instructions =3D dice_command.gsub(/\s+/, '')=A0=A0=A0=20
=A0=A0=A0 @instructions.gsub!(/d%/, 'd100')
=A0=A0=A0 @instructions.gsub!(/([-\+*\/(d]|\A)(?=3Dd)/, '\11')=A0=A0=20
=A0=A0=A0 puts "Normalized instructions: #@instructions" if $DEBUG
=A0=A0=A0 raise "Unmatched left / right parenthesis" unless \
=A0=A0=A0=A0=A0 @instructions.scan(/\(/).size =3D=3D =
=A0 end=A0=20
# Argument parsing
if $0 =3D=3D __FILE__
=A0 raise "DiceRoller dice_command [roll_count=3D1]" unless =
ARGV.length )
=A0 roll_dice(ARGV[0], ARGV[1] ? ARGV[1].to_i : 1)

No virus found in this outgoing message.
Checked by AVG Free Edition.
Version: 7.1.371 / Virus Database: 267.14.15/223 - Release Date: =

Reply With Quote

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off

Similar Threads
Thread Thread Starter Forum Replies Last Post
[QUIZ.SUMMARY] Dice Roller (#61) Matthew Moss Ruby 11 01-13-2006 08:29 PM
[QUIZ] Dice Roller (#61) Ruby Quiz Ruby 106 01-10-2006 06:48 PM
[QUIZ.SOLUTION] Dice Roller (#61) We don't need no steenking leexer/parsers Paul Novak Ruby 2 01-10-2006 02:28 AM
[SOLUTION] Dice Roller Stefan Walk Ruby 0 01-08-2006 10:56 PM