Rolling Dice
Thursday, July 1, 2010
Update: I noticed that
random is
[0,b)
(from 0
to n-1
). The code has been fixed to make it [1..b]
(e.g., return a number from 1 to n).
I was curious about the ability to define new syntax for Factor, and to learn a little about how the lexer works. Inspired by a recent project that I’ve been working on, I thought it would be interesting to define a simple DSL for calculating dice rolls.
We need a way to describe a dice roll. There are various methods used in
Python
or
Ruby.
A common example of a common short-hand description is 4d8
–
specifying how many times (4
) to roll a dice with a number (8
) of
sides.
Let’s setup a vocabulary and a list of dependencies that will be used to implement this functionality:
USING: fry kernel lexer math math.parser peg.ebnf random
sequences strings ;
IN: dice
We will be using
EBNF parsers
similar to what was used in building a
calculator.
First we will implement support for basic (e.g., 4d8
) rolls.
EBNF: parse-roll
number = ([0-9])+ => [[ >string string>number ]]
dice = "d" number => [[ second '[ _ random ] ]]
roll = number dice => [[ first2 '[ 0 _ [ @ + 1 + ] times ] ]]
error = .* => [[ "unknown dice" throw ]]
total = roll | error
;EBNF
We can see how this works by trying it out:
IN: scratchpad "4d8" parse-roll .
[ 0 4 [ 8 random + 1 + ] times ]
IN: scratchpad "4d8" parse-roll call .
15
IN: scratchpad "foo" parse-roll
unknown dice
We can now define a piece of SYNTAX: for dice rolls:
SYNTAX: ROLL: scan parse-roll append ;
And using it:
IN: scratchpad ROLL: 4d8 .
13
This could be extended to support other things such as “base” numbers
(e.g., 4d8+10
), negative rolls, ranges, and better handling of parse
errors. The code for this is available on my
GitHub.