[QUIZ] Dice Roller (#61)

G

Gavin Kistner

--Apple-Mail-1--566424476
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed

Min: (5*1-4)*(1)+3 = 1*1+3 = 5

Wow. That's some brilliant math.

1+3, of course, equals 4. No matter how many 1's are multiplied to
result in 1. :p
--Apple-Mail-1--566424476--
 
G

Gavin Kistner

Max: (5*5+4)*(16/4)+3 = 29*4+3 = 119

Man, *and* I flipped the sign on that -4. *shakes head sadly*

One more shot.

Min: (5*1-4)*(1)+3 = 1*1+3 = 4
Max: (5*5-4)*(16/4)+3 = 21*4+3 = 87
 
G

Gavin Kistner

--Apple-Mail-2--566218505
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed

([1,21])d(16/[1,4])+3 = (c/[x,y] = [c/y,c/x] if x and y > 0)

([1,21])d([4,16])+3 = ([a,b]d[c,d] = [a*c,b*d] if a,b,c, and d

I'm agree with you on that top line, but I'm not sure how you got to
the second line from there.
I think you meant:

([1,21])d([1,4])+3 = ...

which results in:

[1,84]+3
[4,87]
--Apple-Mail-2--566218505--
 
C

Christer Nilsson

Gavin said:
([1,21])d(16/[1,4])+3 = (c/[x,y] = [c/y,c/x] if x and y > 0)

([1,21])d([4,16])+3 = ([a,b]d[c,d] = [a*c,b*d] if a,b,c, and d

I'm agree with you on that top line, but I'm not sure how you got to
the second line from there.
I think you meant:

([1,21])d([1,4])+3 = ...

which results in:

[1,84]+3
[4,87]

d(16/[1,4]) must be [16/4,16/1] => [4,16]

[1,21] d [4,16] must be [1*4, 16*21] => [4, 336]

I would like to introduce distribution. Using one normal dice we have an
even distribution
distr("d6") = [0,1,1,1,1,1,1] Sum=6

Using two normal dices we have the following distribution
distr("2d6")=[0,0,1,2,3,4,5,6,5,4,3,2,1] Sum=36 (min:max) =
(2:12)

P("2d6",12) = 1/36 = 2.8%


I have a question regarding the distribution for "(d2)d6".
In words, I'm first throwing a coin, to decide how many times I will
throw a the dice.

[1,2] d [1,2,3,4,5,6]

My guess:

distr("d6") = [0,1,1,1,1,1,1] Sum=6
distr("2d6") = [0,0,1,2,3,4,5,6,5,4,3,2,1] Sum=36

Probability merge
distr("d6") [0,6,6,6,6, 6, 6] Sum=36
distr("2d6") [0,0,1,2,3, 4, 5,6,5,4,3,2,1] Sum=36

distr("(d2)d6") [0,6,7,8,9,10,11,6,5,4,3,2,1] Sum=72

The probability of having one point is P("(d2)d6",1) = 6/72 = 8.3%

Can somebody agree or disagree on this?

Christer
 
M

Matthew Smillie

I claim 4 and 339.

Both (4, 87) and (4, 339) are correct depending on your definition of
'max'. You get the former if you assume maximum rolls for each die,
and the latter if you want the maximum possible result of the
expression.

In any case, the extremal results aren't particularly useful from a
testing point of view, as different distributions can have the same
extremal points, and you could potentially erroneously pass an
incorrect case. For example: 2d2d6

left-associative:
(2d2)d6 -> 4d6 -> 24 max

right-associative (oops!):
2d(2d6) -> 2d12 -> 24 max

But the distributions are different in each case (the latter result
could include 2 and 3, for instance).

matthew smillie.
 
M

Matthew Moss

With the game systems I know, and I admit I haven't played for a couple
of years, 5d6d7 would not be a legal expression, and would raise an
exception.

Ahhh.... this must be a game system you don't know. =3D)
 
M

Matthew Moss

Wow... You guys are just having too much fun with
"(5d5-4)d(16/d4)+3", I think. Heck, if y'all know Ruby so well (more
than me, cause I'm still such a n00b), you'd be able to swap in loaded
dice for random dice and get your min's and max's. =3D)

Anyway, I just wanted to add a couple of notes. It was made aware to
me that the simplified BNF (in the original post) is slightly in
error, in that it allows expressions like this:

3dddd6

which is invalid.

A couple possible fixes:

1. Use the expanded BNF I posted, which doesn't have this fault.
2. Implement your dice parser using right-associativity for 'd'
operators (but maintain left-assoc for the other binary operators).

Feel free to do what you like.
 
M

Matthew Moss

I have a question regarding the distribution for "(d2)d6".
In words, I'm first throwing a coin, to decide how many times I will
throw a the dice.

[1,2] d [1,2,3,4,5,6]

My guess:

distr("d6") =3D [0,1,1,1,1,1,1] Sum=3D6
distr("2d6") =3D [0,0,1,2,3,4,5,6,5,4,3,2,1] Sum=3D36

Probability merge
distr("d6") [0,6,6,6,6, 6, 6] Sum=3D36
distr("2d6") [0,0,1,2,3, 4, 5,6,5,4,3,2,1] Sum=3D36

distr("(d2)d6") [0,6,7,8,9,10,11,6,5,4,3,2,1] Sum=3D72

The probability of having one point is P("(d2)d6",1) =3D 6/72 =3D 8.3%

Can somebody agree or disagree on this?

Off the top of my head, that seems right.
 
R

Ron M

Austin said:
Novelty dice created in the past include:

d30, d100

The latter is quite unwieldy.

Strictly speaking, it is not possible to make a die (polyhedron) with an
odd number of faces,

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.
 
P

Pierre Barbier de Reuille

Matthew Moss a écrit :
Wow... You guys are just having too much fun with
"(5d5-4)d(16/d4)+3", I think. Heck, if y'all know Ruby so well (more
than me, cause I'm still such a n00b), you'd be able to swap in loaded
dice for random dice and get your min's and max's. =)

Anyway, I just wanted to add a couple of notes. It was made aware to
me that the simplified BNF (in the original post) is slightly in
error, in that it allows expressions like this:

3dddd6

which is invalid.

A couple possible fixes:

1. Use the expanded BNF I posted, which doesn't have this fault.
2. Implement your dice parser using right-associativity for 'd'
operators (but maintain left-assoc for the other binary operators).

Feel free to do what you like.

Well, why do you say it's invalid ? Given the simplified BNF it must be
read as :

3d(d(d(d6)))

and it is perfectly valid as there is no other way to understand that ...

Pierre
 
R

Robert Retzbach

Pierre said:
Matthew Moss a =E9crit :
=20


Well, why do you say it's invalid ? Given the simplified BNF it must be
read as :

3d(d(d(d6)))

and it is perfectly valid as there is no other way to understand that ..=
 
G

Gregory Seidman

} > Wow... You guys are just having too much fun with
} > "(5d5-4)d(16/d4)+3", I think. Heck, if y'all know Ruby so well (more
} > than me, cause I'm still such a n00b), you'd be able to swap in loaded
} > dice for random dice and get your min's and max's. =)
} >
} > Anyway, I just wanted to add a couple of notes. It was made aware to
} > me that the simplified BNF (in the original post) is slightly in
} > error, in that it allows expressions like this:
} >
} > 3dddd6
} >
} > which is invalid.
} >
} > A couple possible fixes:
} >
} > 1. Use the expanded BNF I posted, which doesn't have this fault.
} > 2. Implement your dice parser using right-associativity for 'd'
} > operators (but maintain left-assoc for the other binary operators).
} >
} > Feel free to do what you like.
} >
} >
}
} Well, why do you say it's invalid ? Given the simplified BNF it must be
} read as :
}
} 3d(d(d(d6)))
}
} and it is perfectly valid as there is no other way to understand that ...

That is only true if the d operator is right-associative. According to the
original spec, it is left-associative and, therefore, 3dddd6 is a syntax
error. Mind you, the second option Matthew gave is to make it
right-associative, which is what you have done. I chose to treat it as a
syntax error.

} Pierre
--Greg
 
P

Pierre Barbier de Reuille

Gregory Seidman a écrit :
} > Wow... You guys are just having too much fun with
} > "(5d5-4)d(16/d4)+3", I think. Heck, if y'all know Ruby so well (more
} > than me, cause I'm still such a n00b), you'd be able to swap in loaded
} > dice for random dice and get your min's and max's. =)
} >
} > Anyway, I just wanted to add a couple of notes. It was made aware to
} > me that the simplified BNF (in the original post) is slightly in
} > error, in that it allows expressions like this:
} >
} > 3dddd6
} >
} > which is invalid.
} >
} > A couple possible fixes:
} >
} > 1. Use the expanded BNF I posted, which doesn't have this fault.
} > 2. Implement your dice parser using right-associativity for 'd'
} > operators (but maintain left-assoc for the other binary operators).
} >
} > Feel free to do what you like.
} >
} >
}
} Well, why do you say it's invalid ? Given the simplified BNF it must be
} read as :
}
} 3d(d(d(d6)))
}
} and it is perfectly valid as there is no other way to understand that ...

That is only true if the d operator is right-associative. According to the
original spec, it is left-associative and, therefore, 3dddd6 is a syntax
error. Mind you, the second option Matthew gave is to make it
right-associative, which is what you have done. I chose to treat it as a
syntax error.

Well, I disagree ...
As I see the things, there are two "d" operators :
- one left-associative infix operator (i.e. 3d6)
- one prefix operator (i.e. d6)

The first "d" in your example is the infix one, thus left-associative,
while the other ones are prefix ... and there is nothing to tell about
association as they get only one argument ...

If I'm not clear enough, I hope it will be when we will be allowed to
disclose our solutions ;)

Pierre
 
L

Luke Blanshard

--------------020201010307080107070209
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Attached is my submission. It looks pretty cool to me, but then this is
only my second-ever Ruby program.

Meta-comment: if [QUIZ] opens the quiz, then surely [/QUIZ] should close it.

Luke Blanshard

--------------020201010307080107070209
Content-Type: text/plain;
name="roll.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="roll.rb"

#!/usr/bin/ruby

# = Ruby-talk Quiz #61: Dice Roller
# Usage: roll.rb <expr> [n]
# Evaluates <expr>; if n is given, evaluates n times. Expressions
# are "dice expressions" as in D&D. For example, 3d6 means the
# total of 3 6-sided dice.
#
# === Design
#
# We use a recursive-descent parser, Dice#parse, to convert the
# expression into a tree. The leaves of the tree are of class
# Integer, and the interior nodes, of class Dice::BinOp, represent the
# binary operations of the expression. We are able to treat all nodes
# of the tree equally by augmenting Integer with the diceEval method.


# Add the #diceEval and #diceRoll methods to Integer. #diceEval makes
# an Integer look like a Dice::BinOp, so it can function as a node in
# the parse tree. And #diceRoll is our implementation of the basic
# dice rolling operation, which sums n rolls of an m-sided die, where
# n is the self Integer and m is the "sides" argument.
class Integer
# The parse-tree evaluation method. For integers, the result is
# always self.
def diceEval
self
end
# The binary operator that rolls n m-sided dice and returns the sum.
def diceRoll sides
raise "Dice can't have #{sides} sides" if sides <= 0
(1..self).inject(0) {|sum, i| sum + 1 + rand(sides)}
end
end

# Add some methods to help with using an array as our token stream.
class Array
# Shifts first element off, returns self
def consume
shift; self
end
# Checks for empty, raises parse error
def ensureNotEmpty desc
raise "Parse error: expected "+desc+" at end of input" if empty?
end
end

# The Dice module contains the BinOp class and the #parse method, a
# recursive-descent parser. The parser returns a tree representing
# the parsed expression that responds to the #diceEval method by
# evaluating the expression.
module Dice

# Represents a binary operation in the parse tree returned by
# #parse. Contains the synbol of a binary operator on Integer and
# two other nodes in the parse tree.
class BinOp
# Creates the binary operation node with the given "op" symbol and
# two children nodes.
def initialize op, nodeA, nodeB
@op, @a, @b = op, nodeA, nodeB
end
# Evaluates the two children nodes, then executes the binary
# operator.
def diceEval
@a.diceEval.send @op, @b.diceEval
end
end

# A recursive-descent parser that understands "dice expressions."
# Produces an object that evaluates the given dice expression in
# response to the #diceEval method.
def Dice::parse str
tokens = str.scan /(?:[1-9][0-9]*)|(?:\S)/
answer = expr tokens
raise "Parse error: extra tokens at end of expression: #{tokens}" if not tokens.empty?
answer
end

private
def Dice::expr tokens
answer = factor tokens
until tokens.empty?
case tokens[0]
when "+"; answer = BinOp.new( :+, answer, factor(tokens.consume) )
when "-"; answer = BinOp.new( :-, answer, factor(tokens.consume) )
else break
end
end
answer
end
def Dice::factor tokens
answer = term tokens
until tokens.empty?
case tokens[0]
when "*"; answer = BinOp.new( :*, answer, term(tokens.consume) )
when "/"; answer = BinOp.new( :/, answer, term(tokens.consume) )
else break
end
end
answer
end
def Dice::term tokens
tokens.ensureNotEmpty "number, (, or d"
answer = (if tokens[0] == "d" then 1 else primary tokens end)
until tokens.empty?
case tokens[0]
when "d"; answer = BinOp.new( :diceRoll, answer, diceArg(tokens.consume) );
else break
end
end
answer
end
def Dice::diceArg tokens
tokens.ensureNotEmpty "number, (, or %"
if tokens[0] == "%" then tokens.consume; 100 else primary tokens end
end
def Dice::primary tokens
tokens.ensureNotEmpty "number or ("
case tokens[0]
when "("
answer = expr tokens.consume
raise "Parse error: expected )" if tokens.empty? or tokens.shift != ")"
when /^[1-9]/
answer = tokens.shift.to_i
else
raise "Parse error: unexpected token '#{tokens[0]}'"
end
answer
end
end

# Main program
d = Dice::parse ARGV[0]
$,, $\ = " ", "\n" # Set the field and record terminators
print (1..(ARGV[1] || 1).to_i).collect { d.diceEval }

# Uncomment to dump the structure in readable form
#require "yaml"
#print YAML::dump(d)

--------------020201010307080107070209--
 
B

Bill Kelly

------=_NextPart_000_07D6_01C61444.02651B30
Content-Type: text/plain;
format=flowed;
charset="Windows-1252";
reply-type=original
Content-Transfer-Encoding: 7bit

Hi,

I finally finished a Ruby Quiz! Albeit by means of a goofy
method_missing hack. <grin> But it was fun.

Here 'tis:
------------------------------------------------------------------

#!/usr/bin/env ruby

expr = ARGV[0] || abort('Please specify expression, such as "(5d5-4)d(16/d4)+3"')
expr = expr.dup # unfreeze

class Object
def method_missing(name, *args)
# Intercept dieroll-method calls, like :_5d5, and compute
# their value:
if name.to_s =~ /^_(\d*)d(\d+)$/
rolls = [1, $1.to_i].max
nsides = $2.to_i
(1..rolls).inject(0) {|sum,n| sum + (rand(nsides) + 1)}
else
raise NameError, [name, *args].inspect
end
end
end

class String
def die_to_meth
# Prepend underscore to die specs, like (5d5-4) -> (_5d5-4)
# making them grist for our method_missing mill:
self.gsub(/\b([0-9]*d[0-9]*)\b/, '_\1')
end
end

expr.gsub!(/d%/,"d100") # d% support
# inner->outer reduce
true while expr.gsub!(/\(([^()]*)\)/) {eval($1.die_to_meth)}
p eval(expr.die_to_meth)


------=_NextPart_000_07D6_01C61444.02651B30
Content-Type: application/octet-stream;
name="61_dice_roller.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="61_dice_roller.rb"

#!/usr/bin/env ruby

expr = ARGV[0] || abort('Please specify expression, such as "(5d5-4)d(16/d4)+3"')
expr = expr.dup # unfreeze

class Object
def method_missing(name, *args)
# Intercept dieroll-method calls, like :_5d5, and compute
# their value:
if name.to_s =~ /^_(\d*)d(\d+)$/
rolls = [1, $1.to_i].max
nsides = $2.to_i
(1..rolls).inject(0) {|sum,n| sum + (rand(nsides) + 1)}
else
raise NameError, [name, *args].inspect
end
end
end

class String
def die_to_meth
# Prepend underscore to die specs, like (5d5-4) -> (_5d5-4)
# making them grist for our method_missing mill:
self.gsub(/\b([0-9]*d[0-9]*)\b/, '_\1')
end
end

expr.gsub!(/d%/,"d100") # d% support
# inner->outer reduce
true while expr.gsub!(/\(([^()]*)\)/) {eval($1.die_to_meth)}
p eval(expr.die_to_meth)

------=_NextPart_000_07D6_01C61444.02651B30--
 
M

Matthew Smillie

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.
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.
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.


I used the same approach, but found that ** is right-associative (as
it's generally defined outside of Ruby). To confirm the
associativity for yourself, try this: 2**3**4. If it's left
associative, it should equal 8**4 (4096), right-associativity gives
2**81 (a lot). I ended up doing a lot more redefining and mucking
about:

Dice Ruby
d *
* +
/ -
+ <<
- >>

Interestingly, the difference between a left-associating and a right-
associating 'd' operator isn't particularly visible from the 'loaded-
dice' testing common on the list. For example, 2d2d6 gives a maximum
of 24 whichever associativity is used, but the distributions of the
two solutions are vastly different; the left-associative result has a
minimum value of 4, the right-associative result has a minimum of 2.

Here's my solution, which maintains correct associativity for 'd'
according to the initial quiz, but does a lot more mucking about with
Fixnum:

matthew smillie.

#!/usr/local/bin/ruby

class Fixnum
alias old_mult *
alias old_div /
alias old_plus +
alias old_minus -

def >>(arg) old_minus(arg) end
def <<(arg) old_plus(arg) end
def -(arg) old_div(arg) end
def +(arg) old_mult(arg) end

def *(arg)
sum = 0
self.times do
sum = sum.old_plus(rand(arg).old_plus(1))
end
sum
end
end

class Dice
def initialize(str)
# make assumed '1's explicit - do it twice to cover cases
# like '3ddd6' which would otherwise miss one match.
@dice = str.gsub(/([+\-*\/d])(d)/) { |s| "#{$1}1#{$2}" }
@dice = @dice.gsub(/([+\-*\/d])(d)/) { |s| "#{$1}1#{$2}" }
# sub all the operators.
@dice = @dice.gsub(/\+/, "<<")
@dice = @dice.gsub(/-/, ">>")
@dice = @dice.gsub(/\*/, "+")
@dice = @dice.gsub(/\//, "-")
@dice = @dice.gsub(/d/, "*")
end

def roll
eval(@dice)
end
end

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

Paul Novak

Good catch. I felt uncomfortable building this without unit testing.=20
It should be possible to write good repeatable tests using srand in
place of rand...
 
A

Andrew McGuinness

Ruby said:
The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Matthew D Moss

Time to release your inner nerd.

The task for this Ruby Quiz is to write a dice roller. You should write a
program that takes two arguments: a dice expression followed by the number of
times to roll it (being optional, with a default of 1). So to calculate those
stats for your AD&D character, you would do this:

> roll.rb "3d6" 6
72 64 113 33 78 82

Or, for something more complicated:

> roll.rb "(5d5-4)d(16/d4)+3"
31

My submission isn't going to win points for brevity - at 600+ lines it's
maybe a bit long to post here.

It's got a few extras in there, though:

$ ./dice.rb "(5d5-4)d(16/d4)+3"
45

$ ./dice.rb "3d6" 6
11 7 10 13 9 14

$ ./dice.rb -dist "2d5 + 1dd12"
Distribution:
3 0.0103440355940356
4 0.0276987734487735
5 0.0503975468975469
6 0.0773292448292448
7 0.107660533910534
8 0.120036676286676
9 0.120568783068783
10 0.112113997113997
11 0.096477873977874
12 0.07495670995671
13 0.0588945406445407
14 0.0457661135161135
15 0.0345793650793651
16 0.0250565175565176
17 0.0171049783549784
18 0.0107247474747475
19 0.00596632996632997
20 0.00290909090909091
21 0.00113636363636364
22 0.000277777777777778
Check total: 1.0
Mean 9.75 std. dev 3.37782803325187

$ ./dice.rb -cheat "2d5 + 1dd12" 19
19 : D5=2 D5=5 D12=12 D12=12 p=0.000277777777777778

$ ./dice.rb -cheat "2d5 + 1dd12" 25
Cannot get 25

I've shoved it on http://homepage.ntlworld.com/a.mcguinness/files/dice.rb
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top