Packages

Add a dice-rolling DSL with sigil support and reusable compiled functions to your application.

Current section

Files

Jump to
ex_dice_roller lib compilers separator.ex
Raw

lib/compilers/separator.ex

defmodule ExDiceRoller.Compilers.Separator do
@moduledoc """
Handles the `,` separator for rolls.
The separator allows for multiple, separate dice expressions to be evaluated
and only one returned based on provided options:
* `:highest`: returns the highest calculated value and is the default option
* `:lowest`: returns the lowest calculated value
Examples:
iex> ExDiceRoller.roll("1,2", [], [])
2
iex> ExDiceRoller.roll("1,1", [], [])
1
iex> ExDiceRoller.roll("1,2", [], [])
2
iex> ExDiceRoller.roll("1,2", [], [:highest])
2
iex> ExDiceRoller.roll("1,2", [], [:lowest])
1
iex> ExDiceRoller.roll("1d6,10d8", [], [:highest])
46
iex> ExDiceRoller.roll("1d6,10d8", [], [:lowest])
6
Seperator expressions can be wrapped in parentheses to be utilized it as a
subexpression in a larger expression.
Examples:
iex> ExDiceRoller.roll("(5d1,2d1)+5", [], [:highest])
10
iex> ExDiceRoller.roll("(5d1,2d1)+5", [], [:lowest])
7
## Separator Use And Keeping Dice
The separator can be used alongside kept dice rolls, provided both sides of
the expression are lists of equal length. Otherwise, it obeys the same rules
of kept roll list comprehension, where each die roll is compared to its
counterpart.
iex> ExDiceRoller.roll("5d6,5d100", [], [:keep, :lowest])
[2, 2, 6, 4, 5]
iex> ExDiceRoller.roll("5d6,5d100", [], [:keep, :highest])
[47, 6, 49, 91, 54]
iex> ExDiceRoller.roll("(5d2,5d6)+5", [], [:highest, :keep])
[7, 9, 9, 11, 6]
iex> ExDiceRoller.roll("(5d1,5d100)+5", [], [:lowest, :keep])
[6, 6, 6, 6, 6]
"""
@behaviour ExDiceRoller.Compiler
alias ExDiceRoller.Compiler
@error_both_sides "separator can only be used when both sides are a list or both sides are a number"
@impl true
def compile({:sep, left_expr, right_expr}) do
compile_sep(Compiler.delegate(left_expr), Compiler.delegate(right_expr))
end
@spec compile_sep(Compiler.compiled_val(), Compiler.compiled_val()) :: Compiler.compiled_val()
defp compile_sep(l, r) when is_function(l) and is_function(r),
do: fn args, opts -> high_low(l.(args, opts), r.(args, opts), opts) end
defp compile_sep(l, r) when is_function(l),
do: fn args, opts -> high_low(l.(args, opts), r, opts) end
defp compile_sep(l, r) when is_function(r),
do: fn args, opts -> high_low(l, r.(args, opts), opts) end
defp compile_sep(l, r), do: fn _args, opts -> high_low(l, r, opts) end
@spec high_low(Compiler.calculated_val(), Compiler.calculated_val(), :highest | :lowest) ::
Compiler.calculated_val()
defp high_low(l, l, _), do: l
defp high_low(l, r, opts) when is_list(opts) do
case Enum.find(opts, &(&1 in [:highest, :lowest])) do
:highest -> high_low(l, r, :highest)
:lowest -> high_low(l, r, :lowest)
_ -> high_low(l, r, :highest)
end
end
defp high_low(l, r, level) when is_list(l) and is_list(r) do
if length(l) != length(r) do
raise ArgumentError, "cannot use separator on lists of differing lengths"
end
Enum.map(0..(length(l) - 1), &high_low(Enum.at(l, &1), Enum.at(r, &1), level))
end
defp high_low(l, _, _) when is_list(l), do: raise(ArgumentError, @error_both_sides)
defp high_low(_, r, _) when is_list(r), do: raise(ArgumentError, @error_both_sides)
defp high_low(l, r, :highest) when l > r, do: l
defp high_low(_, r, :highest), do: r
defp high_low(l, r, :lowest) when l < r, do: l
defp high_low(_, r, :lowest), do: r
end