lib/rollex/tokens/fudge_dice.ex

defmodule Rollex.Tokens.FudgeDice do
  @regex ~r/\G(\d*)[dD][fF]/

  defstruct regex: @regex,
            is_dice: true,
            quantity: 0,
            sides: 3,
            valid_rolls: [],
            rejected_rolls: [],
            arithmetic: 0.0

  @type t :: %__MODULE__{}

  defimpl Rollex.Token, for: Rollex.Tokens.FudgeDice do
    @left_binding_precedence 0

    def nud(token, rest) do
      {:ok,
       %{
         arithmetic: token.arithmetic,
         successes: Enum.count(token.valid_rolls)
       }, rest}
    end

    def led(_token, _left, _rest) do
      {:error, "Unexpected dice token"}
    end

    def lbp(_token) do
      @left_binding_precedence
    end

    def create(_token, roll_expr, [
          {_token_start, token_length},
          {quantity_start, quantity_length}
        ]) do
      {
        %Rollex.Tokens.FudgeDice{
          quantity:
            roll_expr
            |> String.slice(quantity_start, quantity_length)
            |> Rollex.Utilities.simple_integer_parse()
        },
        token_length
      }
    end
  end

  defimpl Rollex.Dice, for: Rollex.Tokens.FudgeDice do
    def roll(token) do
      {fails, successes, total} =
        Enum.reduce(1..token.quantity, {[], [], 0}, fn _, {fails, successes, total} ->
          case :rand.uniform(3) - 2 do
            1 -> {fails, [1 | successes], total + 1}
            0 -> {[0 | fails], successes, total}
            -1 -> {[-1 | fails], successes, total - 1}
          end
        end)

      %{
        token
        | rejected_rolls: fails,
          valid_rolls: successes,
          arithmetic: total
      }
    end

    def min(token), do: -token.quantity
    def max(token), do: token.quantity
  end
end