README.md

Rollex
======

[![Latest Release](https://gitlab.com/r2815/rollex/-/badges/release.svg)](https://gitlab.com/r2815/rollex/-/releases)
[![coverage report](https://gitlab.com/r2815/rollex/badges/develop/coverage.svg)](https://gitlab.com/r2815/rollex/0/commits/develop)
[![pipeline status](https://gitlab.com/r2815/rollex/badges/develop/pipeline.svg)](https://gitlab.com/r2815/rollex/-/commits/develop)

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](https://hexdocs.pm/rollex).

Installation
============

Pre-requisites
--------------

* Elixir 1.12 or greater

Adding as a mix dependency
--------------------------

In your mix.exs file:

```elixir
  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](http://lukaszwrobel.pl/) for his [short series on RD parsing](http://lukaszwrobel.pl/blog/math-parser-part-1-introduction), as well as [Eli Bendersy's post on TDOP](http://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing)!