Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > [QUIZ.SOLUTION] Dice Roller (#61) We don't need no steenking leexer/parsers

Reply
Thread Tools

[QUIZ.SOLUTION] Dice Roller (#61) We don't need no steenking leexer/parsers

 
 
Paul Novak
Guest
Posts: n/a
 
      01-08-2006
------=_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/2....html?page=3D3

from random import randrange
def dice(num,sides):
=09return reduce(lambda x,y,s=3Dsides + 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--


 
Reply With Quote
 
 
 
 
Matthew Moss
Guest
Posts: n/a
 
      01-09-2006
Here's my own solution, which isn't nearly as pretty or clever as some
of the stuff I've seen. I had started revising it, but it started
looking more and more like Dennis Ranke's solution, which wasn't good
since I was looking at his code to see how to do certain things in
Ruby. =3D)

In any case, I've decided to post my original, not-so-clever version.
But I'm glad to see all the entries; it'll be interesting to write up
a summary. I've certainly learned a lot.

----

class Dice

TOKENS =3D {
:integer =3D> /[1-9][0-9]*/,
ercent =3D> /%/,
:lparen =3D> /\(/,
:rparen =3D> /\)/,
lus =3D> /\+/,
:minus =3D> /-/,
:times =3D> /\*/,
:divide =3D> /\//,
:dice =3D> /d/
}

class Lexer
def initialize(str)
@str =3D str
end

include Enumerable
def each
s =3D @str
until s.empty?
(tok, pat) =3D TOKENS.find { |tok, pat| s =3D~ pat && $`.empty? =
}
raise "Bad input!" if tok.nil?
yield(tok, $&)
s =3D s[$&.length .. -1]
end
end
end

class Parser
def initialize(tok)
@tokens =3D tok.to_a
@index =3D 0
@marks =3D []
end

def action
@marks.push(@index)
end

def commit
@marks.pop
end

def rollback
@index =3D @marks.last
end

def next
tok =3D @tokens[@index]
raise "Out of tokens!" if tok.nil?
@index +=3D 1
tok
end
end

def initialize(str)
@parser =3D Parser.new(Lexer.new(str))
@dice =3D expr
end

def roll
@dice.call
end

def expr
# fact expr_
expr_(fact)
end

def expr_(lhs)
# '+' fact expr_
# '-' fact expr_
# nil

@parser.action

begin
tok =3D @parser.next
rescue
res =3D lhs
else
case tok[0]
when lus
rhs =3D fact
res =3D expr_(proc { lhs.call + rhs.call })
when :minus
rhs =3D fact
res =3D expr_(proc { lhs.call - rhs.call })
else
@parser.rollback
res =3D lhs
end
end

@parser.commit
res
end

def fact
# term fact_
fact_(term)
end

def fact_(lhs)
# '*' term fact_
# '/' term fact_
# nil

@parser.action

begin
tok =3D @parser.next
rescue
res =3D lhs
else
case tok[0]
when :times
rhs =3D term
res =3D fact_(proc { lhs.call * rhs.call })
when :divide
rhs =3D term
res =3D fact_(proc { lhs.call / rhs.call })
else
@parser.rollback
res =3D lhs
end
end

@parser.commit
res
end

def term
# dice
# unit term_

begin
res =3D dice(proc { 1 })
rescue
res =3D term_(unit)
end

res
end

def term_(lhs)
# dice term_
# nil
begin
res =3D term_(dice(lhs))
rescue
res =3D lhs
end

res
end

def dice(lhs)
# 'd' spec

@parser.action

tok =3D @parser.next
case tok[0]
when :dice
rhs =3D spec
res =3D proc { (1 .. lhs.call).inject(0) {|s,v| s +=3D rand(rhs.cal=
l)+1 }}
else
@parser.rollback
raise "Expected dice, found #{tok[0]} '#{tok[1]}'\n"
end

@parser.commit
res
end

def spec
# '%'
# unit

@parser.action

tok =3D @parser.next
case tok[0]
when ercent
res =3D proc { 100 }
else
@parser.rollback
res =3D unit
end

@parser.commit
res
end

def unit
# '(' expr ')'
# INT (non-zero, literal zero not allowed)

@parser.action

tok =3D @parser.next
case tok[0]
when :integer
res =3D proc { tok[1].to_i }
when :lparen
begin
res =3D expr
tok =3D @parser.next
raise unless tok[0] =3D=3D :rparen
rescue
@parser.rollback
raise "Expected (expr), found #{tok[0]} '#{tok[1]}'\n"
end
else
@parser.rollback
raise "Expected integer, found #{tok[0]} '#{tok[1]}'\n"
end

@parser.commit
res
end
end


# main

d =3D Dice.new(ARGV[0] || "d6")
(ARGV[1] || 1).to_i.times { print "#{d.roll} " }


 
Reply With Quote
 
 
 
 
Joby Bednar
Guest
Posts: n/a
 
      01-10-2006
This isn't so much a solution to the quiz, but more of an addition to
the whole dice rolling thing. Assume you have an array of numbers
between 1-6... outputs the face of the dice in ascii art, one for each
element in the array:

class Array
def to_dice
logic = [
lambda{|n| '+-----+ '},
lambda{|n| (n>3 ? '|O ' : '| ')+(n>1 ? ' O| ' : ' | ')},
lambda{|n| (n==6 ? '|O ' : '| ')+
(n%2==1 ? 'O' : ' ')+(n==6 ? ' O| ' : ' | ')},
lambda{|n| (n>1 ? '|O ' : '| ')+(n>3 ? ' O| ' : ' | ')}
]

str=''
5.times {|row|
self.each {|n| str += logic[row%4].call(n) }
str+="\n"
}
str
end
end


#Example:
puts [1,2,3,4,5,6].to_dice

-Joby

 
Reply With Quote
 
 
 
Reply

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
[SOLUTION] Dice Roller Stefan Walk Ruby 0 01-08-2006 10:56 PM
[SOLUTION] Dice Roller (#61) John Earles Ruby 0 01-08-2006 09:59 PM



Advertisments