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
@spec string_grid_with_marks(list(), {integer(), integer()}, binary()) :: binary()
@doc """
Creates string representation of grid with marks (#) on given points. Blanks are filled with gap_char.
"""
def string_grid_with_marks(points, {max_x, max_y}, gap_char \\ ".") do
sorted_points = MapSet.new(points)
|> Enum.sort_by(fn %{x: x, y: y} -> {y, x} end)
{string_arr, last_point} = Enum.reduce(sorted_points, {"", %Point2D{x: -1,y: 0}}, fn point, {string, prev_point} ->
new_string_arr = string <> fill_gaps(prev_point, point, max_x, gap_char) <> "#"
{new_string_arr, point}
end)
string_arr <> fill_gaps(last_point, %Point2D{x: max_x + 1, y: max_y}, max_x, gap_char)
end
@spec print_grid(map(), integer(), integer()) :: :ok
@doc """
Prints point grid where map is a Map and every point has assigned integer value.
"""
def print_grid(map, width, height) do
IO.puts(grid_to_string(map, width, height))
end
@doc """
Converts num grid to array of ints
"""
def 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})
end)
|> Enum.join()
end)
|> Enum.join("\n")
end
defp fill_gaps(prev_point, next_point, max_x, gap_char) do
%{x: x, y: y} = next_point
cond do
y <= prev_point.y and x > prev_point.x + 1 ->
String.duplicate(gap_char,x-(prev_point.x+1))
y > prev_point.y ->
String.duplicate(gap_char, (if prev_point.x == max_x, do: 0, else: max_x - prev_point.x))
<> "\n"
<> fill_empty_lines(y - prev_point.y - 1, max_x, gap_char)
<> String.duplicate(gap_char, x)
true ->
""
end
end
defp fill_empty_lines(number, max_x, gap_char) do
empty_line = String.duplicate(gap_char, max_x + 1) <> "\n"
String.duplicate(empty_line, number)
end
defimpl Inspect, for: Point2D do
import Inspect.Algebra
def inspect(point, _) do
concat(["(x: #{point.x}, y: #{point.y})"])
end
end
end