Current section

Files

Jump to
islands_engine lib islands engine island.ex
Raw

lib/islands/engine/island.ex

defmodule Islands.Engine.Island do
@moduledoc false
use PersistConfig
alias __MODULE__
alias Islands.Engine.{Coord, Offsets}
@enforce_keys [:type, :coords, :hits]
defstruct [:type, :coords, :hits]
@type coords :: MapSet.t(Coord.t())
@type t :: %Island{type: type, coords: coords, hits: coords}
@type type :: :atoll | :dot | :l_shape | :s_shape | :square
@types Application.get_env(@app, :island_types)
@spec new(type, Coord.t()) :: {:ok, t} | {:error, atom}
def new(type, %Coord{} = origin) when type in @types do
with [_ | _] = coords <- type |> Offsets.for() |> coords(origin) do
{:ok, %Island{type: type, coords: MapSet.new(coords), hits: MapSet.new()}}
else
:error -> {:error, :invalid_island_location}
end
end
def new(_type, _origin), do: {:error, :invalid_island_args}
@spec overlaps?(t, t) :: boolean
def overlaps?(%Island{} = island, %Island{} = new_island) do
not MapSet.disjoint?(island.coords, new_island.coords)
end
@spec guess(t, Coord.t()) :: {:hit, t} | :miss
def guess(%Island{} = island, %Coord{} = guess) do
if MapSet.member?(island.coords, guess),
do: {:hit, update_in(island.hits, &MapSet.put(&1, guess))},
else: :miss
end
@spec forested?(t) :: boolean
def forested?(%Island{} = island) do
MapSet.equal?(island.coords, island.hits)
end
## Private functions
@spec coords([tuple], Coord.t()) :: [Coord.t()] | :error
defp coords(offsets, %Coord{row: row, col: col} = _origin) do
Enum.reduce_while(offsets, [], fn {row_offset, col_offset}, coords ->
case Coord.new(row + row_offset, col + col_offset) do
{:ok, coord} -> {:cont, [coord | coords]}
{:error, _reason} -> {:halt, :error}
end
end)
end
end