defmodule MarsExplorer.Exploration do @moduledoc """ Module responsible for managing a exploration """ alias MarsExplorer.{Probe, Exploration, HighlandGrid} defstruct [:grid, :probe] @type placement :: %{ north: integer, east: integer, direction: :west | :north | :south | :east } @type t :: %__MODULE__{ grid: MarsExplorer.HighlandGrid.t(), probe: MarsExplorer.Probe.t() } @doc """ Places a probe on a grid ## Examples When the probe is placed in a valid position iex> alias MarsExplorer.{Probe, Exploration, HighlandGrid} [MarsExplorer.Probe, MarsExplorer.Exploration, MarsExplorer.HighlandGrid] iex> grid = HighlandGrid.build(%{north: 5, east: 5}) %HighlandGrid{north_limit: 5, east_limit: 5} iex> Exploration.place(grid, %{north: 0, east: 0, direction: :north}) { :ok, %Exploration{ grid: grid, probe: %Probe{north: 0, east: 0, direction: :north} } } When the probe is placed in a invalid position iex> alias MarsExplorer.{Exploration, HighlandGrid} [MarsExplorer.Exploration, MarsExplorer.HighlandGrid] iex> grid = HighlandGrid.build(%{north: 5, east: 5}) %HighlandGrid{north_limit: 5, east_limit: 5} iex> Exploration.place(grid, %{north: 6, east: 6, direction: :north}) {:error, :invalid_placement} """ @spec place(HighlandGrid.t(), placement()) :: {:ok, t()} | {:error, :invalid_placement} def place(grid, placement) do if grid |> HighlandGrid.valid_position?(placement) do { :ok, %Exploration{ grid: grid, probe: struct(Probe, placement) } } else {:error, :invalid_placement} end end @doc """ Moves the probe forward one unit in the direction that it is facing, unless that position is past the limits of the grid. ## Examples ### A valid movement iex> alias MarsExplorer.{Probe, HighlandGrid, Exploration} [MarsExplorer.Probe, MarsExplorer.HighlandGrid, MarsExplorer.Exploration] iex> grid = HighlandGrid.build(%{north: 5, east: 5}) %HighlandGrid{north_limit: 5, east_limit: 5} iex> exploration = %Exploration{ ...> grid: grid, ...> probe: %Probe{north: 0, east: 0, direction: :north} ...> } iex> exploration |> Exploration.move {:ok, %Exploration{ grid: grid, probe: %Probe{north: 1, east: 0, direction: :north} }} ### An invalid movement: iex> alias MarsExplorer.{Probe, HighlandGrid, Exploration} [MarsExplorer.Probe, MarsExplorer.HighlandGrid, MarsExplorer.Exploration] iex> grid = HighlandGrid.build(%{north: 5, east: 5}) %HighlandGrid{north_limit: 5, east_limit: 5} iex> exploration = %Exploration{ ...> grid: grid, ...> probe: %Probe{north: 5, east: 0, direction: :north} ...> } iex> exploration |> Exploration.move() {:error, :at_grid_limit} """ @spec move(t()) :: {:ok, t()} | {:error, :at_grid_limit} def move(%{probe: probe, grid: grid} = exploration) do with moved_probe <- probe |> Probe.move(), true <- grid |> HighlandGrid.valid_position?(moved_probe) do {:ok, %{exploration | probe: moved_probe}} else false -> {:error, :at_grid_limit} end end @doc """ Turns the probe left. ## Examples iex> alias MarsExplorer.{Probe, HighlandGrid, Exploration} [MarsExplorer.Probe, MarsExplorer.HighlandGrid, MarsExplorer.Exploration] iex> grid = HighlandGrid.build(%{north: 5, east: 5}) %HighlandGrid{north_limit: 5, east_limit: 5} iex> exploration = %Exploration{ ...> grid: grid, ...> probe: %Probe{north: 0, east: 0, direction: :north} ...> } iex> exploration |> Exploration.turn_left {:ok, %Exploration{ grid: grid, probe: %Probe{north: 0, east: 0, direction: :west} }} """ @spec turn_left(t()) :: {:ok, t()} def turn_left(%Exploration{probe: probe} = exploration) do {:ok, %{exploration | probe: probe |> Probe.turn_left()}} end @doc """ Turns the probe left. ## Examples iex> alias MarsExplorer.{Probe, HighlandGrid, Exploration} [MarsExplorer.Probe, MarsExplorer.HighlandGrid, MarsExplorer.Exploration] iex> grid = HighlandGrid.build(%{north: 5, east: 5}) %HighlandGrid{north_limit: 5, east_limit: 5} iex> exploration = %Exploration{ ...> grid: grid, ...> probe: %Probe{north: 0, east: 0, direction: :north} ...> } iex> exploration |> Exploration.turn_right {:ok, %Exploration{ grid: grid, probe: %Probe{north: 0, east: 0, direction: :east} }} """ @spec turn_right(t()) :: {:ok, t()} def turn_right(%Exploration{probe: probe} = exploration) do {:ok, %{exploration | probe: probe |> Probe.turn_right()}} end @doc """ Returns the probe's current position. ## Examples iex> alias MarsExplorer.{Probe, HighlandGrid, Exploration} [MarsExplorer.Probe, MarsExplorer.HighlandGrid, MarsExplorer.Exploration] iex> grid = HighlandGrid.build(%{north: 5, east: 5}) %HighlandGrid{north_limit: 5, east_limit: 5} iex> exploration = %Exploration{ ...> grid: grid, ...> probe: %Probe{north: 0, east: 0, direction: :north} ...> } iex> exploration |> Exploration.report %Probe{north: 0, east: 0, direction: :north} """ @spec report(t()) :: {:ok, Probe.t()} def report(%Exploration{probe: probe}), do: probe end