this is my first ruby quiz, and here comes my solution.
as a couple of other solutions, it uses ruby to do the dirty work, and
implements the diceroll as a method on integer.
!g
this is my first ruby quiz, and here comes my solution.
as a couple of other solutions, it uses ruby to do the dirty work, and
implements the diceroll as a method on integer.
!g
On 1/6/06, Jim F. [email protected] wrote:
On 1/6/06, James Edward G. II [email protected] wrote:
Actually, when rolled together, both dice are zero-based. The
double-nought is the only special combination of 00 → 100. When
rolled singly, a d10 has 0 → 10. Rolling a 0 is never possible.No wonder I don’t play D&D. I don’t think I am smart enough.
Heh, I wouldn’t say that. But I will admit that most of my attraction
to the game is the mental challenge of managing (and taking advantage
of) the complex rule system. Call me munchkin. Shrug.
What does 0 → 10 mean. Does it mean a dice can have the
values 0,1,2,3…10?
No, that was a typo on my part. Should have been 0 → 9 (e.g. rand(10)).
If so, why is a 0 never possible?
Zero is possible, but is interpreted as a 10. So the effective range
is 1 → 19 (rand(10) + 1). In this respect, the outcome of a d10
follows the same rules of the outcome from a d6, d8 or d20 (rand(N) +
1). The presentation on the dice is the only difference. As noted by
Austin, this is primarily due to space limitations, but also for
convenience when using two d10 to simulate a d100.
Jacob F.
On 1/6/06, Jacob F. [email protected] wrote:
Actually, when rolled together, both dice are zero-based. The
double-nought is the only special combination of 00 → 100. When
rolled singly, a d10 has 0 → 10. Rolling a 0 is never possible.
On 1/6/06, Jim F. [email protected] wrote:
What does 0 → 10 mean. Does it mean a dice can have the
values 0,1,2,3…10?
On 1/9/06, Jacob F. [email protected] wrote:
No, that was a typo on my part. Should have been 0 → 9 (e.g. rand(10)).
Ok, I must fix this previous apology to Jim F… 0 → 10 was not
a typo. He just misinterpreted my notation. And seeing as I followed
his misinterpretation before second glance, it’s perfectly justified.
In the paragraph above the → represented “is interpreted as”. So when
rolling two ten sided dice for a d100, double-nought (‘00’) is
interpreted as 100. When rolling a single d10, 0 is interpreted as 10.
Jacob F.
On 1/7/06, Reinder V. [email protected] wrote:
([1,21])d([4,16])+3 = ([a,b]d[c,d] = [ac,bd] if a,b,c, and d > 0)
[4,336]+3 = ([x,y] + c = [x+c,y+c])
[7,339]
As pointed out by others, the result should actually by [4,339]. The
error above is in the second to last step. [a,b]d[c,d] != [ac,bd],
it should be [a,b]d[c,d] = [a,bd]. The minimum effective value on a
die roll is always one, regardless of the number of sides (ie. whether
using a c-sided die or d-sided die). The minimum number of rolls being
a, the minimum roll total would then be a1 = a. So we have:
[1,21]d[4,16]+3 =
[1,21*16]+3 =
[1+3,336+3] =
[4,339]
Jacob F.
On 1/7/06, Ron M [email protected] wrote:
Uh, of course you can make such a polyhedron. Consider the
Egyptian and Mayan pyramids as examples of 5-sided polyhedron
(four triangles on the sides and a square on the bottom).
Adjusting the steepness of the sides can make it as fair
or unfair as you’d want.Sure, they’re not regular polyhedra, but neither is the d30 you spoke of.
In fact, I’ve seen a variation on this for the infamous “d3” somtimes
used in D&D. The standard approach is to roll a d6 then divide the
result by two (rounding up). However, one dice seller instead offers a
non-regular tetrahedron where one vertex is stretched out away from
the fourth side. This makes the three stretched side much more likely
than the fourth side, and given the dynamics of the dice, it is very
unlikely for it to land standing on that fourth side. So you have, in
effect, a three sided die. If the fourth side does ever come up, you
just reroll.
Jacob F.
I really love this mailing list.
It’s simple and with threaded view it’s perfect.
But sometimes a forum with an edit function is handy too :>
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
On Sun, 08 Jan 2006 19:33:18 -0000, a complete idiot (i.e. me) wrote:
As well as the main ‘roll.rb’ I also included a separate utility that
uses loaded dice to find min/max achievable.
Yeah, well, the penny dropped. That’ll teach me to try and understand
maths - I did so well at avoiding it in the main thing, then decided to
try that…
Now I’ve succeeded in making a complete fool of myself I’ll just get
me
coat…
On Sat, Jan 07, 2006 at 04:00:52AM +0900, Gregory S. wrote:
} On Sat, Jan 07, 2006 at 03:56:47AM +0900, Ruby Q. wrote:
} […]
} } [NOTE: The BNF above is simplified here for clarity and space. If
} } requested, I will make available the full BNF description I’ve used
in my
} } own solution, which incorporates the association and precedence
rules.]
}
} I would appreciate the full BNF, please.
Okay, so I said I wanted the full BNF, and I thought it would be useful
if
I could find a convenient Ruby lex/yacc. Well, I couldn’t. I now have
two
solutions, both using my own parsing. Both use eval. One adds a method
to
Fixnum to let eval do even more work. The more complicated version with
syntax trees, which came first, took roughly two hours to work out. The
second, simpler version with the Fixnum method took about 20 minutes to
build from the first version. Note that I maintained left associativity
with the d operator in both methods without having the 3dddd6 problem
Matthew M. mentioned.
test61.rb runs the code on commandline arguments
61.rb is the complicated syntax tree version
61alt.rb is the simpler Fixnum method version
################################################################
#require ‘61’
require ‘61alt’
d = Dice.new(ARGV[0])
(ARGV[1] || 1).to_i.times { print "#{d.roll} " }
puts ‘’
####################################################################
module DiceRoller
class ArithOperator
def initialize(left, op, right)
@left = left
@op = op
@right = right
end
def to_i
return (eval "#{@left.to_i}#{@op}#{@right.to_i}")
end
end
class DieOperator
#op is a dummy
def initialize(left, op, right)
@left = left
@right = right
end
def to_i
count = @left.to_i
fail "Die count must be nonnegative: '#{count}'" if count < 0
die = @right.to_i
fail "Die size must be positive: '#{die}'" if die < 1
return (1..count).inject(0) { |sum, waste| sum + (rand(die)+1) }
end
end
OpClass = { ‘+’ => ArithOperator,
‘-’ => ArithOperator,
‘*’ => ArithOperator,
‘/’ => ArithOperator,
‘d’ => DieOperator }
def lex(str)
tokens = str.scan(/(00)|([-/()+d%0])|([1-9][0-9])|(.+)/)
tokens.each_index { |i|
tokens[i] = tokens[i].compact[0]
if not /^(00)|([-/()+d%0])|([1-9][0-9])$/ =~ tokens[i]
if /^\s+$/ =~ tokens[i]
tokens[i] = nil
else
fail “Found garbage in expression: ‘#{tokens[i]}’”
end
end
}
return tokens.compact
end
def validate_and_cook(tokens)
oper = /[-*/+d]/
num = /(\d+)|%/
last_was_op = true
paren_depth = 0
prev = ‘’
working = []
tokens.each_index { |i|
tok = tokens[i]
if num =~ tok
fail ‘A number cannot follow an expression!’ if not last_was_op
fail ‘Found spurious zero or number starting with zero!’ if tok
== ‘0’
if ( tok == ‘00’ || tok == ‘%’ )
fail ‘Can only use % or 00 after d!’ if prev != ‘d’
tokens[i] = 100
working << 100
else
working << tok.to_i
end
last_was_op = false
elsif oper =~ tok
if last_was_op
#handle case of dX meaning 1dX
if tok == ‘d’
fail ‘A d cannot follow a d!’ if prev == RollMethod
working << 1
else
fail ‘An operator cannot follow a operator!’
end
end
working << tok
last_was_op = true
elsif tok == “(”
fail ‘An expression cannot follow an expression!’ if not
last_was_op
paren_depth += 1
working << :p_open
elsif tok == “)”
fail ‘Incomplete expression at close paren!’ if last_was_op
fail ‘Too many close parens!’ if paren_depth < 1
paren_depth -= 1
last_was_op = false
working << :p_close
else #what did I miss?
fail “What kind of token is this? ‘#{tok}’”
end
prev = tok
}
fail ‘Missing close parens!’ if paren_depth != 0
return working
end
def parse_parens(tokens)
working = []
i = 0
while i < tokens.length
if tokens[i] == :p_open
i += 1
paren_depth = 0
paren_tokens = []
while (tokens[i] != :p_close) || (paren_depth > 0)
if tokens[i] == :p_open
paren_depth += 1
elsif tokens[i] == :p_close
paren_depth -= 1
end
paren_tokens << tokens[i]
i += 1
end
working << parse(paren_tokens)
else
working << tokens[i]
end
i += 1
end
return working
end
def parse_ops(tokens, regex)
fail “Something broke: len = #{tokens.length}” if tokens.length < 3
|| (tokens.length % 2) == 0
i = 1
working = [ tokens[0] ]
while i < tokens.length
if regex =~ tokens[i].to_s
op = OpClass[tokens[i]]
lindex = working.length-1
working[lindex] = op.new(working[lindex], tokens[i],
tokens[i+1])
else
working << tokens[i]
working << tokens[i+1]
end
i += 2
end
return working
end
#scan for parens, then d, then /, then ±
def parse(tokens)
working = parse_parens(tokens)
fail “Something broke: len = #{working.length}” if (working.length %
2) == 0
working = parse_ops(working, /^d$/) if working.length > 1
fail “Something broke: len = #{working.length}” if (working.length %
2) == 0
working = parse_ops(working, /^[/]$/) if working.length > 1
fail “Something broke: len = #{working.length}” if (working.length %
2) == 0
working = parse_ops(working, /^[±]$/) if working.length > 1
fail “Something broke: len = #{working.length}” if working.length !=
1
return working[0]
end
def parse_dice(str)
tokens = lex(str)
return parse(validate_and_cook(tokens))
end
end
class Dice
def initialize(expression)
@expression = parse_dice(expression)
end
def roll
return @expression.to_i
end
private
include DiceRoller
end
#################################################################
module DiceRoller
RollMethod = ‘.roll’
def lex(str)
tokens = str.scan(/(00)|([-/()+d%0])|([1-9][0-9])|(.+)/)
tokens.each_index { |i|
tokens[i] = tokens[i].compact[0]
if not /^(00)|([-/()+d%0])|([1-9][0-9])$/ =~ tokens[i]
if /^\s+$/ =~ tokens[i]
tokens[i] = nil
else
fail “Found garbage in expression: ‘#{tokens[i]}’”
end
end
}
return tokens.compact
end
def validate_and_cook(tokens)
oper = /[-*/+d]/
num = /(\d+)|%/
last_was_op = true
paren_depth = 0
prev = ‘’
working = []
tokens.each_index { |i|
tok = tokens[i]
if num =~ tok
fail ‘A number cannot follow an expression!’ if not last_was_op
fail ‘Found spurious zero or number starting with zero!’ if tok
== ‘0’
if ( tok == ‘00’ || tok == ‘%’ )
fail ‘Can only use % or 00 after d!’ if prev != RollMethod
tokens[i] = 100
tok = 100
else
tok = tok.to_i
end
if prev == RollMethod
working << “(#{tok})”
else
working << tok
end
last_was_op = false
elsif oper =~ tok
tok = RollMethod if tok == ‘d’
if last_was_op
#handle case of dX meaning 1dX
if tok == RollMethod
fail ‘A d cannot follow a d!’ if prev == RollMethod
working << 1
else
fail ‘An operator cannot follow a operator!’
end
end
working << tok
last_was_op = true
elsif tok == “(”
fail ‘An expression cannot follow an expression!’ if not
last_was_op
paren_depth += 1
working << tok
elsif tok == “)”
fail ‘Incomplete expression at close paren!’ if last_was_op
fail ‘Too many close parens!’ if paren_depth < 1
paren_depth -= 1
last_was_op = false
working << tok
else #what did I miss?
fail “What kind of token is this? ‘#{tok}’”
end
prev = tok
}
fail ‘Missing close parens!’ if paren_depth != 0
return working
end
def parse_dice(str)
tokens = lex(str)
return validate_and_cook(tokens).to_s
end
end
class Fixnum
def roll(die)
fail “Die count must be nonnegative: ‘#{self}’” if self < 0
fail “Die size must be positive: ‘#{die}’” if die < 1
return (1…self).inject(0) { |sum, waste| sum + (rand(die)+1) }
end
end
class Dice
def initialize(expression)
@expression = parse_dice(expression)
end
def roll
return (eval @expression)
end
private
include DiceRoller
end
James Edward G. II wrote:
I think I’d really like to see a production quality parser(generator)
using something like this grammar format.I agree. This is fantastic.
Thanks
So what do we have to do to get you to add the polish and make it
available?
I have put it on my mental to-do list, but that doesn’t necessarily mean
that I actually get around to doing it.
Right now the grammar is subject to quite some restrictions that I would
like to remove, but I’ll need to learn more about parser generators to
do this.
Dennis
Another solution:
#! /usr/bin/ruby
def random(i)
rand(i)+1
end
class DiceExpr
def initialize(rolls, sides)
@rolls, @sides = rolls, sides
end
def to_i
sides = @sides.to_i
([email protected]_i).inject(0) { | sum, i | sum += random(sides) }
end
def to_s
“(#{@rolls}d#{@sides})”
end
end
class Expr
def initialize(lhs, rhs, op)
@lhs, @rhs, @op = lhs, rhs, op
end
def to_i
@lhs.to_i.send(@op, @rhs.to_i)
end
def to_s
“(#{@lhs}#{@op}#{@rhs})”
end
end
class Dice
def initialize(expr)
@expr_org = @expr_str = expr
next_token
@expr = addend()
if @token
raise “parser error: tokens left: >#{@fulltoken}#{@expr_str}<”
end
end
@@regex = Regexp.compile(/^\s*([()±/]|[1-9][0-9]|d%|d)\s*/)
def next_token
@prev_token = @token
return @token = nil if @expr_str.empty?
match = @@regex.match(@expr_str)
if !match
raise “parser error: cannot tokenize input #{@expr_str}”
end
@expr_str = @expr_str[match.to_s.length, @expr_str.length]
@fulltoken = match.to_s # for “tokens left” error message only…
@token = match[1]
end
def number() # number or parenthesized expression
raise “unexpeced >)<” if ( @token == ‘)’ )
if ( @token == ‘(’ )
next_token
val = addend
raise “parser error: parenthesis error, expected ) got #{@token}”
if @token != ‘)’
next_token
return val
end
raise “parse error: number expected, got #{@token}” if @token !~
/^[0-9]*$/
next_token
@prev_token
end
def dice()
if ( @token == ‘d’ )
rolls = 1
else
rolls = number()
end
while ( @token == ‘d’ || @token == ‘d%’ )
if @token == ‘d%’
rolls = DiceExpr.new(rolls, 100)
next_token
else
next_token
sides = number()
raise “parser error: missing sides expression” if !sides
rolls = DiceExpr.new(rolls, sides)
end
end
rolls
end
def factor()
lhs = dice()
while ( @token == ‘*’ || @token == ‘/’ )
op = @token
next_token
rhs = dice()
raise “parser error: missing factor” if !rhs
lhs = Expr.new(lhs, rhs, op)
end
lhs
end
def addend()
lhs = factor()
while ( @token == ‘+’ || @token == ‘-’ )
op = @token
next_token
rhs = factor()
raise “parser error: missing addend” if !rhs
lhs = Expr.new(lhs, rhs, op)
end
lhs
end
def to_s
“#{@expr_org} -> #{@expr.to_s}”
end
def roll
@expr.to_i
end
end
d = Dice.new(ARGV[0])
#puts d.to_s
(ARGV[1] || 1).to_i.times { print "#{d.roll} " }
puts
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.
Sponsor our Newsletter | Privacy Policy | Terms of Service | Remote Ruby Jobs