lib/rule_systems/abilities/assignment.ex

defmodule ExTTRPGDev.RuleSystems.Abilities.Assignment do
  alias ExTTRPGDev.RuleSystems.Abilities.Assignment
  alias ExTTRPGDev.Dice

  @moduledoc """
  This module handles the different ways of assigning ability scores
  """

  defstruct [:rolling_methods, :point_buy]

  defmodule PointBuy do
    @moduledoc """
    PointBuy specification for ability score assignment
    """
    defstruct [:points, :score_costs]

    defmodule ScoreCost do
      @moduledoc """
      Mapping of specific ability scores to cost for PointBuy ability assignment
      """
      defstruct [:score, :cost]
    end
  end

  defmodule RollingMethod do
    @moduledoc """
    Specificiation for different rolling methods for ability assignment
    """
    defstruct [:name, :dice, :special, :default]
  end

  @doc """
  Returns the default assignment method for the defined assignment methods

  ## Examples

      iex> Assignment.default_assignment()
  """
  def default_assignment(%Assignment{
        rolling_methods: [%Assignment.RollingMethod{} = first | _tail] = rolling_methods
      }) do
    Enum.find(rolling_methods, first, fn method -> method.default == true end)
  end

  @doc """
  Generates an ability score using the Assignment.RollingMethod

  ## Examples

      # Although not necessary, let's seed the random algorithm
      iex> :rand.seed(:exsplus, 1337)
      iex> Assignment.roll_via_method!(%Assignment.RollingMethod{dice: "3d6"})
      [6, 2, 3]
  """
  def roll_via_method!(%Assignment.RollingMethod{} = method) do
    Dice.roll(method.dice)
    |> Assignment.apply_method_special!(method.special)
  end

  @doc """
  Applies a method special to the rolled values

  ## Examples

      iex> Assignment.apply_method_special!([4, 3, 2, 1], "drop_lowest")
      [2, 3, 4]

  """
  def apply_method_special!([head | _tail] = rolls, special) when is_integer(head) do
    case special do
      "drop_lowest" ->
        [_lowest | the_rest] = Enum.sort(rolls)
        the_rest

      nil ->
        rolls

      unhandled ->
        raise "Special '#{unhandled}' is not handled for rolling methods"
    end
  end
end