lib/islands/guesses.ex

# ┌────────────────────────────────────────────────────────────────────┐
# │ Based on the book "Functional Web Development" by Lance Halvorsen. │
# └────────────────────────────────────────────────────────────────────┘
defmodule Islands.Guesses do
  @moduledoc """
  A guesses struct and functions for the _Game of Islands_.

  The guesses struct contains the fields `hits` and `misses` representing the
  guesses of an opponent in the _Game of Islands_.

  ##### Based on the book [Functional Web Development](https://pragprog.com/titles/lhelph/functional-web-development-with-elixir-otp-and-phoenix/) by Lance Halvorsen.
  """

  alias __MODULE__
  alias Islands.{Coord, Island}

  @derive Jason.Encoder
  @enforce_keys [:hits, :misses]
  defstruct [:hits, :misses]

  @typedoc "A guesses struct for the Game of Islands"
  @type t :: %Guesses{hits: Island.coords(), misses: Island.coords()}
  @typedoc "Type of guess"
  @type type :: :hit | :miss

  @doc """
  Creates a new guesses struct.

  ## Examples

      iex> Islands.Guesses.new()
      %Islands.Guesses{
        hits: MapSet.new(),
        misses: MapSet.new()
      }
  """
  @spec new :: t
  def new, do: %Guesses{hits: MapSet.new(), misses: MapSet.new()}

  @doc """
  Adds a hit `guess` to the `:hits` set or a miss `guess` to the `:misses` set.
  """
  @spec add(t, type, Coord.t()) :: t | {:error, atom}
  def add(guesses, type, guess)

  def add(%Guesses{} = guesses, :hit, %Coord{} = guess) do
    update_in(guesses.hits, &MapSet.put(&1, guess))
  end

  def add(%Guesses{} = guesses, :miss, %Coord{} = guess) do
    update_in(guesses.misses, &MapSet.put(&1, guess))
  end

  def add(_guesses, _type, _guess), do: {:error, :invalid_guesses_args}

  @doc """
  Returns a map assigning to :squares the list of square numbers
  from the `guesses`'s hits.
  """
  @spec hit_squares(t) :: %{:squares => [Coord.square()]}
  def hit_squares(%Guesses{hits: hits} = _guesses) do
    %{squares: Enum.map(hits, &Coord.to_square/1)}
  end

  @doc """
  Returns a map assigning to :squares the list of square numbers
  from the `guesses`'s misses.
  """
  @spec miss_squares(t) :: %{:squares => [Coord.square()]}
  def miss_squares(%Guesses{misses: misses} = _guesses) do
    %{squares: Enum.map(misses, &Coord.to_square/1)}
  end
end