ExDiceRoller

Provides functionality around calculating both simple and complex dice rolling equations.

Features

Installation

Add :ex_dice_roller to your list of dependencies in mix.exs:

def deps do
  [
    {:ex_dice_roller, "~> 0.2.0"}
  ]
end

Next, run:

$ mix deps.get

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")
  12

  iex> ExDiceRoller.roll("1d20-(5*6)")
  -28

  iex> ExDiceRoller.roll("1d4d6")
  10

  iex> ExDiceRoller.roll("(1d4+2)d8")
  28

  iex> ExDiceRoller.roll("(1d4+2)d(1d20)")
  16

  iex> ExDiceRoller.roll("(1d4+2)d((5*6)d20-5)")
  677

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 - (3d6)d5 + (1d4)/5")
  {:ok, #Function<6.11371143/0 in ExDiceRoller.Compiler.compile_op/5>}

  iex> ExDiceRoller.execute(roll_fun)
  21.6

  iex> ExDiceRoller.execute(roll_fun)
  34.4

  iex> ExDiceRoller.execute(roll_fun)
  37.8

How It Works

  1. ExDiceRoller utilizes Erlang's leex library to tokenize a given dice roll string.
  2. The tokens are then passed to yecc which parses the tokens into an abstract syntax tree.
  3. The syntax tree is then interpreted through recursive navigation.
  4. During interpretation:
  5. Any basic numerical values are calculated.
  6. Any dice rolls are converted into anonymous functions.
  7. Any portion of the expression or equation that use both base values and dice rolls are converted into anonymous functions.
  8. The results of interpretation are then wrapped by a final anonymous function.
  9. 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, &#39;(&#39;},
    {:digit, 1, &#39;1&#39;},
    {:roll, 1, &#39;d&#39;},
    {:digit, 1, &#39;4&#39;},
    {:basic_operator, 1, &#39;+&#39;},
    {:digit, 1, &#39;2&#39;},
    {:")", 1, &#39;)&#39;},
    {:roll, 1, &#39;d&#39;},
    {:"(", 1, &#39;(&#39;},
    {:"(", 1, &#39;(&#39;},
    {:digit, 1, &#39;5&#39;},
    {:complex_operator, 1, &#39;*&#39;},
    {:digit, 1, &#39;6&#39;},
    {:")", 1, &#39;)&#39;},
    {:roll, 1, &#39;d&#39;},
    {:digit, 1, &#39;20&#39;},
    {:basic_operator, 1, &#39;-&#39;},
    {:digit, 1, &#39;5&#39;},
    {:")", 1, &#39;)&#39;}
  ]}

  iex(5)> {:ok, ast} = ExDiceRoller.parse(tokens)
  {:ok,
  {:roll,
    {{:operator, &#39;+&#39;},
      {:roll, {:digit, &#39;1&#39;}, {:digit, &#39;4&#39;}},
      {:digit, &#39;2&#39;}},
    {{:operator, &#39;-&#39;},
      {:roll, 
        {{:operator, &#39;*&#39;}, {:digit, &#39;5&#39;}, {:digit, &#39;6&#39;}},
        {:digit, &#39;20&#39;}},
      {:digit, &#39;5&#39;}}}}

  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

Test Coverage and More