# Dice Roller (#61)

**URL:** <https://www.ruby-forum.com/t/dice-roller-61/94767>\
**Category:** Ruby\
**Created:** [January 8, 2006, 11:00pm UTC](https://www.ruby-forum.com/t/dice-roller-61/94767 "2006-01-08T23:00:09Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![John\_Earles](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/58f4c7/32.png) [@John\_Earles](https://www.ruby-forum.com/u/John_Earles)\
**Post date:** [January 8, 2006, 11:00pm UTC](https://www.ruby-forum.com/t/dice-roller-61/94767/1 "2006-01-08T23:00:09Z")

</div>

Hi! This is my first entry to a RUBY-QUIZ. Sure I could have opted to  
use  
‘eval’, but I figured I might as well learn a little about expression  
parsing. Fun stuff!

My entry is focused around The Shunting Yard Algorithm, allowing me to  
parse, transform and evaluate in the same step. I do not keep the  
‘postfix’  
transform, so it is slightly inefficient to perform multiple ‘rolls’.  
That  
is an optimization for another day.

I also got to try out some new regexp stuff. Of particular note is the  
use  
of (?=d) in the expression used to search for d’s that need an implicit  
1  
lvalue. I was having problems with 5dddd7 type commands until I  
discovered  
this allowed the target d NOT to be consumed by the regexp.

As a final note I am a Ruby N., and a Java developer by day, so any  
tips  
or comments on my coding would be appreciated. Thanks!

- John

* * *

$DEBUG = false

# Dice Roller entry point

def roll\_dice( dice\_command, roll\_count )  
begin  
puts “Executing #{roll\_count} roll(s) of #{dice\_command}”  
results, total = Dice.new( dice\_command ).roll( roll\_count )  
puts “Result: [#{results.join(’, ')}] =\> #{total}”  
rescue Exception =\> e  
puts “Roll error: #{e}”  
end  
end

class Dice

# operator =\> [precendence, associativity]

@@operators = { “d” =\> [3, :right],  
“\*” =\> [2, :left] , “/” =\> [2, :left],  
“+” =\> [1, :left] , “-” =\> [1, :left]  
}

# Initialize the Stacks and load the dice instructions

def initialize( dice\_command )  
if $DEBUG  
alias :d :dnd\_roll\_loaded  
else  
alias :d :dnd\_roll\_random  
end

```
@operator_stack, @value_stack = [], []
prepare_instructions( dice_command )

```

end

def roll( roll\_count )  
results = (1…roll\_count).collect { execute }  
[results, results.inject {|sum, item| sum + item } || 0]  
end

private

# The infix command is parsed into tokens and then executed using

# The Shunting Yard Algorithm. Evaluation is done “on-the-fly” as

# items are placed on the value stack (acting as the post-fix

“output”).  
def execute  
@operator\_stack.clear  
@value\_stack.clear

```
# Process the tokens in L -> R order
# Look for non-digit characters and numbers
@instructions.scan(/\D|\d+/) do | token |
  case token
    when "("
      @operator_stack.push token

    when /\d+/ # any number
      @value_stack.push token.to_i
     
    when /[-\+*\/d]/ # the operators
      finished = false
      until finished or @operator_stack.empty?
        if higher_operator(token)
          finished = true
        else
          resolve_expression
        end
      end
      @operator_stack.push token

    when ")"
      resolve_expression while @operator_stack.last != "("
      @operator_stack.pop
     
    else
      raise "Invalid token found: #{token}"
  end
end

resolve_expression while !@operator_stack.empty?

raise "Unexpected problem. #{@value_stack.size} values remain after

```

execution."   
unless @value\_stack.size == 1  
@value\_stack.pop  
end

def resolve\_expression  
opr, rhv, lhv = @operator\_stack.pop, @value\_stack.pop,  
@value\_stack.pop  
raise “No more values left for #{opr} to consume!” unless rhv && lhv

```
value = (opr == "d") ? value = d( lhv, rhv ) : lhv.send( opr, rhv )
@value_stack.push value.to_i

```

end

def dnd\_roll\_random( roll\_count, die\_value )  
(1…roll\_count).inject(0) { |value, item| value + ( rand(die\_value)

- 1  
) }  
end

# Argument parsing

if $0 == **FILE**  
raise “DiceRoller dice\_command [roll\_count=1]” unless (1…2).include?(  
ARGV.length )  
roll\_dice(ARGV[0], ARGV[1] ? ARGV[1].to\_i : 1)  
end
