lib/islands/board/cache/generator.ex

defmodule Islands.Board.Cache.Generator do
  @moduledoc """
  Generates a random list of boards.
  """

  use PersistConfig

  alias Islands.Board.Cache.Log
  alias Islands.{Board, Coord, Island}

  @boards get_env(:dense_boards)
  @board_count length(@boards)
  @goal 99
  @range 1..10
  @types [:atoll, :dot, :l_shape, :s_shape, :square]

  @doc """
  Generates a random list of boards.
  """
  @spec gen_boards :: [Board.t()]
  def gen_boards do
    :ok = Log.info(:generating_boards, {@goal, __ENV__})
    MapSet.new(@boards) |> gen_set(@board_count) |> MapSet.to_list()
  end

  ## Private functions

  # Generates a set of random boards.
  @spec gen_set(MapSet.t(), non_neg_integer) :: MapSet.t(Board.t())
  defp gen_set(set, size) when size >= @goal, do: set

  defp gen_set(set, _size) do
    set = MapSet.put(set, gen_board())
    gen_set(set, MapSet.size(set))
  end

  # Generates a random board.
  @spec gen_board :: Board.t()
  defp gen_board do
    Enum.reduce(@types, Board.new(), &gen_board(&2, &1))
  end

  # Position a new island on the board.
  @spec gen_board(Board.t(), Island.type()) :: Board.t()
  defp gen_board(board, type) do
    case Board.position_island(board, gen_island(type)) do
      %Board{} = board -> board
      {:error, _} -> gen_board(board, type)
    end
  end

  # Generates a randomly positioned island.
  @spec gen_island(Island.type()) :: Island.t()
  defp gen_island(type) do
    case Island.new(type, gen_origin()) do
      {:ok, island} -> island
      {:error, _} -> gen_island(type)
    end
  end

  # Generates a random coordinates struct.
  @spec gen_origin :: Coord.t()
  defp gen_origin do
    {:ok, origin} = Coord.new(Enum.random(@range), Enum.random(@range))
    origin
  end
end