Rollex

Latest Releasecoverage reportpipeline status

Rollex is a dice expression evaluator written in Elixir.

It supports standard dice notation including arithmetic operators, parentheses, and dice selection modifiers (e.g. keep or drop N highest/lowest, accept rolls above/below/equal to one or more target numbers, etc).

Dice roll definitions may be passed as simple strings to Rollex.roll/1, or they may be pre-compiled using Rollex.compile/2 before being passed to Rollex.evaluate/1 to calculate a new result.

Evaluation of a roll produces the tokenized output (including each individual dice roll) as well as the final total sum and success count (useful for e.g. dice pools). On failure, an error tuple is returned.

Rollex can also build histograms of these same expressions, useful in calculating and displaying odds of various dice rolls.

Suggestions and merge requests for performance tweaks, bug fixes, feature enhancements, etc. are greatly appreciated!

Documentation

Full documentation can be found on hexdocs.

Installation

Pre-requisites

Adding as a mix dependency

In your mix.exs file:

  defp deps do
    [{:rollex, "0.6.0"}]
  end

Running in Elixir's interactive shell

iex -S mix

From here, execute rolls like this:

iex(1)> Rollex.roll("1+2d6+3d4")

Usage Examples

Roll a d6, add 2, then add the results of 4d6 after dropping the lowest die roll:

iex(1)> Rollex.roll("(1d6+2)+4d6d1")
{:ok,
 %{
   tokens: [
     %Rollex.Tokens.LeftParenthesis{regex: ~r/\A\(/},
     %Rollex.Tokens.RegularDice{
       arithmetic: 4,
       is_dice: true,
       operation: nil,
       quantity: 1,
       raw_token: "1d6",
       regex: ~r/\A(\d*)[dD](\d+)/,
       rejected_rolls: [],
       sides: 6,
       valid_rolls: [4]
     },
     %Rollex.Tokens.Addition{regex: ~r/\A\+/},
     %Rollex.Tokens.Number{raw_token: "2", regex: ~r/\A\d+(\.\d+)?/, value: 2},
     %Rollex.Tokens.RightParenthesis{regex: ~r/\A\)/},
     %Rollex.Tokens.Addition{regex: ~r/\A\+/},
     %Rollex.Tokens.RegularDice{
       arithmetic: 15,
       is_dice: true,
       operation: {:drop_bottom, 1},
       quantity: 4,
       raw_token: "4d6d1",
       regex: ~r/\A(\d*)[dD](\d+)/,
       rejected_rolls: [2],
       sides: 6,
       valid_rolls: [6, 5, 4]
     },
     %Rollex.Tokens.End{regex: ~r/\A\z/}
   ],
   totals: %{arithmetic: 21, successes: 4}
 }}

Roll a pool of 4d6 and count 5s or 6s as a success:

iex(1)> Rollex.roll("4d6>4") |> then(fn {:ok, %{totals: %{successes: count}}} -> count end)
1

Separate compilation from evaluation:

iex(1)> Rollex.compile("1d6") |> Rollex.evaluate()
{:ok,
 %{
   tokens: [
     %Rollex.Tokens.RegularDice{
       arithmetic: 5,
       is_dice: true,
       operation: nil,
       quantity: 1,
       raw_token: "1d6",
       regex: ~r/\A(\d*)[dD](\d+)/,
       rejected_rolls: [],
       sides: 6,
       valid_rolls: [5]
     },
     %Rollex.Tokens.End{regex: ~r/\A\z/}
   ],
   totals: %{arithmetic: 5, successes: 1}
 }}

Calculate the odds distribution for 2d6:

iex(1)> Rollex.histogram(Rollex.compile("2d6"))
{:ok,
 %{
   histogram: %{
     2 => 2.778,
     3 => 5.556,
     4 => 8.333,
     5 => 11.111,
     6 => 13.889,
     7 => 16.667,
     8 => 13.889,
     9 => 11.111,
     10 => 8.333,
     11 => 5.556,
     12 => 2.778
   },
   tokens: [
     %Rollex.Tokens.RegularDice{
       arithmetic: 0.0,
       is_dice: true,
       operation: nil,
       quantity: 2,
       raw_token: "2d6",
       regex: ~r/\A(\d*)[dD](\d+)/,
       rejected_rolls: [],
       sides: 6,
       valid_rolls: []
     },
     %Rollex.Tokens.End{regex: ~r/\A\z/}
   ]
 }}

Thanks

Rollex uses the Pratt Parser (AKA Top-Down Operator Precedence) to parse and evaluate dice notation. Thanks to Lukasz Wrobel for his short series on RD parsing, as well as Eli Bendersy's post on TDOP!