ExDiceRoller
Provides functionality around calculating both simple and complex dice rolling equations.
Features
- Simulates dice rolls with any number of dice and sides of dice.
-
Supports common math operators:
+,-,*,/ -
Supports
+and-unary operators. -
Supports creating common expressions and parenthetically grouped expressions
into complex dice-roll equations, such as
(1d4)d(3d6)-(1d4+7). -
Supports using single-letter variables in expressions that can be given values
upon invocation, such as
1dx+y. - Supports compiling dice rolls into reuseable anonymous functions that be passed around and invoked again and again.
- Supports caching compiled rolls. This optional feature can be especially useful in an application that generates various rolls during runtime.
- Supports exploding dice.
-
Introduces a new sigil,
~a. This can be used as a shorthand for compiling and/or rolling dice rolls, such as~a/1d6+2-(1d4)d6/.
Installation
Add :ex_dice_roller to your list of dependencies in mix.exs:
def deps do
[
{:ex_dice_roller, "~> 0.4.0-alpha"}
]
endNext, run:
$ mix deps.getGeneral Usage
ExDiceRoller supports a variety of possible dice roll permutations that can be used in your application.
iex> ExDiceRoller.roll("1")
#=> 1
iex> ExDiceRoller.roll("1+2")
#=> 3
iex> ExDiceRoller.roll("1d6")
#=> 1
iex> ExDiceRoller.roll("1d20-(5*6)")
#=> -28
iex> ExDiceRoller.roll("1d4d6")
#=> 10
iex> ExDiceRoller.roll("(1d4+2)d(1d20)")
#=> 22
iex> ExDiceRoller.roll("(1d4+2)d((5*6)d20-5)", [])
#=> 566
iex> ExDiceRoller.roll("1dx+y", [x: 20, y: 13])
#=> 16
Sigil Usage
iex> import ExDiceRoller.Sigil
#=> ExDiceRoller.Sigil
iex> fun = ~a/1d6+3/
#=> #Function<1.86580672/2 in ExDiceRoller.Compiler.compile/1>
iex> ExDiceRoller.execute(fun)
#=> 6
iex> ~a/1d2+3/r # compiles the roll and invokes it
#=> 4
iex> ~a/1d2+2/re # compiles the roll and invokes it with exploding dice
#=> 9
iex> ExDiceRoller.roll(~a/2d8-2/)
#=> 3Cache Usage
iex> ExDiceRoller.start_cache()
#=> {:ok, ExDiceRoller.Cache}
iex> ExDiceRoller.roll("xdy-2d4", [x: 10, y: 5], [:cache])
#=> 34
iex> ExDiceRoller.Cache.all()
#=> [{"xdy-2d4", #Function<1.86580672/2 in ExDiceRoller.Compiler.compile/1>}]
iex> ExDiceRoller.roll("xdy-2d4", [x: 10, y: "2d6"], [:cache])
#=> 29
iex> ExDiceRoller.Cache.all()
#=> [{"xdy-2d4", #Function<1.86580672/2 in ExDiceRoller.Compiler.compile/1>}]
iex> ExDiceRoller.roll("1d6+3d4", [], [:cache])
#=> 10
iex> ExDiceRoller.Cache.all()
#=> [
#=> {"xdy-2d4", #Function<1.86580672/2 in ExDiceRoller.Compiler.compile/1>},
#=> {"1d6+3d4", #Function<1.86580672/2 in ExDiceRoller.Compiler.compile/1>}
#=> ]Compiled Expressions
Parsed expressions can be compiled into a single, executable anonymous function. This function can be reused again and again, with any dice rolls being randomized and calculated for each call.
Note that while ExDiceRoller.roll/1 always returns integers,
ExDiceRoller.execute/1 will return either floats or integers.
iex> {:ok, roll_fun} = ExDiceRoller.compile("1d6 - (3d10)d5 + (1d50)/5")
#=> {:ok, #Function<1.86580672/2 in ExDiceRoller.Compiler.compile/1>}
iex> ExDiceRoller.execute(roll_fun)
#=> -16
iex> roll_fun.([], [])
#=> -43
iex> {:ok, roll_fun} = ExDiceRoller.compile("1dx+10")
#=> {:ok, #Function<8.36233920/1 in ExDiceRoller.Compiler.compile_op/5>}
iex> ExDiceRoller.execute(roll_fun, [x: 5])
#=> 12
iex> ExDiceRoller.execute(roll_fun, x: "10d100")
#=> 523How It Works
- ExDiceRoller utilizes Erlang's leex library to tokenize a given dice roll string.
- The tokens are then passed to yecc which parses the tokens into an abstract syntax tree of expressions.
- The syntax tree is then interpreted through recursive navigation.
-
During interpretation:
- Any basic numerical values are calculated.
- Any dice rolls are converted into anonymous functions.
- Any mathematical operations using numbers are calculated.
- Any mathematical operations using expressions are converted into anonymous functions. dice rolls are converted into anonymous functions.
- The results of interpretation are then wrapped by a final anonymous function.
- This final anonymous function is then executed and the value returned.
iex(3)> expr = "(1d4+2)d((5*6)d20-5)"
"(1d4+2)d((5*6)d20-5)"
iex(4)> {:ok, tokens} = ExDiceRoller.tokenize(expr)
{:ok,
[
{:"(", 1, '('},
{:digit, 1, '1'},
{:roll, 1, 'd'},
{:digit, 1, '4'},
{:basic_operator, 1, '+'},
{:digit, 1, '2'},
{:")", 1, ')'},
{:roll, 1, 'd'},
{:"(", 1, '('},
{:"(", 1, '('},
{:digit, 1, '5'},
{:complex_operator, 1, '*'},
{:digit, 1, '6'},
{:")", 1, ')'},
{:roll, 1, 'd'},
{:digit, 1, '20'},
{:basic_operator, 1, '-'},
{:digit, 1, '5'},
{:")", 1, ')'}
]}
iex(5)> {:ok, ast} = ExDiceRoller.parse(tokens)
{:ok,
{:roll,
{{:operator, '+'},
{:roll, {:digit, '1'}, {:digit, '4'}},
{:digit, '2'}},
{{:operator, '-'},
{:roll,
{{:operator, '*'}, {:digit, '5'}, {:digit, '6'}},
{:digit, '20'}},
{:digit, '5'}}}}
iex(6)> {:ok, roll_fun} = ExDiceRoller.compile(ast)
{:ok, #Function<12.11371143/0 in ExDiceRoller.Compiler.compile_roll/4>}
iex(7)> roll_fun.([], [])
739
iex(8)> roll_fun.([], [])
905
iex(9)> ExDiceRoller.Compiler.fun_info(roll_fun)
{#Function<9.16543174/1 in ExDiceRoller.Compiler.compile_roll/4>,
:"-compile_roll/4-fun-0-",
[
{#Function<1.16543174/1 in ExDiceRoller.Compiler.compile_add/4>,
:"-compile_add/4-fun-1-",
[
{#Function<12.16543174/1 in ExDiceRoller.Compiler.compile_roll/4>,
:"-compile_roll/4-fun-3-", [1, 4]},
2
]},
{#Function<14.16543174/1 in ExDiceRoller.Compiler.compile_sub/4>,
:"-compile_sub/4-fun-1-",
[
{#Function<12.16543174/1 in ExDiceRoller.Compiler.compile_roll/4>,
:"-compile_roll/4-fun-3-", [30, 20]},
5
]}
]}Test Coverage and More
- ex_coveralls provides test coverage metrics.
- credo is used for static code analysis.
License
ExDiceRoller source code is released under Apache 2 License.