defmodule ExTTRPGDev.RuleSystems.Abilities.ModifierCalculation do
alias ExTTRPGDev.RuleSystems.Abilities.ModifierCalculation
@moduledoc """
Module handling calculation of ability modifiers based on ability scores
"""
defstruct [:steps, :mapping]
defmodule Step do
@moduledoc """
A step is generally part of a series of step which taken in order
can calculate from the modifier from the starting ability score
"""
defstruct [:order, :method, :value]
end
defmodule Mapping do
@moduledoc """
Defines direct mapping of ability scores to their modifer values
"""
defstruct [:ability_value, :modifier_value]
end
@doc """
Takes in a ModifierCalculation struct and a score, and calculates the scores modifier
## Examples
iex> modifier_for_score(%ModifierCalculation{steps: [%Step{}, %Step{}, ...]}, 13)
1
modifier_for_score(%ModifierCalculation{mapping: [%Mapping{}, %Mapping, ...], 6)
-2
"""
def modifier_for_score(
%ModifierCalculation{steps: [%ModifierCalculation.Step{} | _tail] = steps, mapping: nil},
score
) do
steps
|> ordered_steps()
|> Enum.reduce(score, fn step, acc -> ModifierCalculation.modify_score_by_step(step, acc) end)
end
def modifier_for_score(
%ModifierCalculation{
steps: nil,
mapping: [%ModifierCalculation.Mapping{} | _tail] = mappings
},
score
) do
mappings
|> map_mappings()
|> Map.get(score)
end
defp map_mappings([%ModifierCalculation.Mapping{} | _tail] = mappings) do
mappings
|> Enum.reduce(%{}, fn mapping, acc ->
Map.put(acc, Map.get(mapping, :ability_value), Map.get(mapping, :modifier_value))
end)
end
def modify_score_by_step(%ModifierCalculation.Step{} = step, score) do
case step do
%ModifierCalculation.Step{order: _, method: "add", value: value} ->
score + value
%ModifierCalculation.Step{order: _, method: "subtract", value: value} ->
score - value
%ModifierCalculation.Step{order: _, method: "multiply", value: value} ->
score * value
%ModifierCalculation.Step{order: _, method: "divide", value: value} ->
score / value
%ModifierCalculation.Step{order: _, method: "round_down", value: nil} ->
Float.floor(score) |> Kernel.trunc()
%ModifierCalculation.Step{order: _, method: "round_up", value: nil} ->
Float.ceil(score) |> Kernel.trunc()
end
end
defp ordered_steps([%ModifierCalculation.Step{} | _tail] = steps) do
steps
|> Enum.sort(fn a, b -> Map.get(a, :order) <= Map.get(b, :order) end)
end
end