lib/point.ex

defmodule Adventurous.Point do

  defmodule Point2D do
    defstruct x: 0, y: 0
  end

  @spec move(atom | %{:x => number, :y => number, optional(any) => any}, map) ::
          %Adventurous.Point.Point2D{x: number, y: number}
  @doc """
  Moves Point coordinates by given values.
  """
  def move(point, adjust_coords) do
    adj = Map.merge(%{x: 0, y: 0}, adjust_coords)
    %Point2D{ x: point.x + adj[:x], y: point.y + adj[:y] }
  end

  @doc """
  Returnes coordinates of adjacent points
  """
  def adjacent(point, type \\ :direct) do
    top = move(point, %{x: -1})
    bottom = move(point, %{x: 1})
    left = move(point, %{y: -1})
    right = move(point, %{y: 1})

    if type == :direct do
      [top, left, right, bottom]
    else
      top_left = move(point, %{x: -1, y: -1})
      top_right = move(point, %{x: -1, y: 1})
      bottom_left = move(point, %{x: 1, y: -1})
      bottom_rigth = move(point, %{x: 1, y: 1})

      [top_left, top, top_right, left, right, bottom_left, bottom, bottom_rigth]
    end
  end

  @spec read_num_grid(binary) :: {map, non_neg_integer, non_neg_integer}
  @doc """
  Reads array of integers and convers it into map of point->value and dimensions of grid.
  """
  def read_num_grid(input) do
    lines = String.split(input, "\n")

    map = Enum.with_index(lines)
    |> Enum.flat_map(fn {line, row_idx} ->
      String.graphemes(line)
      |> Enum.with_index
      |> Enum.map(fn {letter, idx} ->
        { %Point2D{x: row_idx, y: idx}, String.to_integer(letter) }
      end)
    end)
    |> Map.new

    width = String.length(hd(lines))
    height = Enum.count(lines)

    {map, width, height}
  end

  def print_num_grid(map, width, height) do
    IO.puts(num_grid_to_string(map, width, height))
  end

  @doc """
  Converts num grid to array of ints
  """
  def num_grid_to_string(map, width, height) do
    Enum.map(0..(height-1), fn x ->
      Enum.map(0..(width-1), fn y ->
        Map.get(map, %Point2D{x: x, y: y})
        |> Integer.to_string()
      end)
      |> Enum.join()
    end)
    |> Enum.join("\n")
  end

  defimpl Inspect, for: Point2D do
    import Inspect.Algebra

    def inspect(point, _) do
      concat(["(x: #{point.x}, y: #{point.y})"])
    end
  end

end