defmodule PipeMaze do
def number_of_steps_to_farthest_point(input) do
map = parse_map(input)
[_, start_location] = initial_path = get_initial_path(map)
initial_path
|> find_loop_path(start_location, map)
|> length()
|> div(2)
end
defp get_initial_path(map) do
start_location = get_start_location(map)
[connected_location_1, _connected_location_2] =
start_location
|> get_neighbours(map)
|> Enum.filter(&(start_location in get_reachable_neighbours(map, &1)))
[connected_location_1, start_location]
end
@neighbour_offsets [{-1, 0}, {0, 1}, {1, 0}, {0, -1}]
defp get_neighbours(location, map) do
@neighbour_offsets
|> Enum.map(&apply_offset(location, &1))
|> Enum.filter(&is_within_bounds?(&1, map))
end
defp is_within_bounds?({row, col}, map),
do: row >= 0 and row <= length(map) and col >= 0 and col <= length(hd(map))
defp find_loop_path(acc_path, start_location, map) do
[hd | rest] = new_path = advance_path(map, acc_path)
case hd do
^start_location -> rest
_ -> find_loop_path(new_path, start_location, map)
end
end
@direction_offsets %{
"J" => [{0, -1}, {-1, 0}],
"7" => [{0, -1}, {1, 0}],
"L" => [{-1, 0}, {0, 1}],
"F" => [{1, 0}, {0, 1}],
"-" => [{0, -1}, {0, 1}],
"|" => [{-1, 0}, {1, 0}],
"." => []
}
defp advance_path(map, [last, previous | _] = path) do
next = get_reachable_neighbours(map, last) |> Enum.find(&(&1 != previous))
[next | path]
end
defp get_reachable_neighbours(map, location) do
symbol = get_symbol_at_location(map, location)
@direction_offsets[symbol]
|> Enum.map(&apply_offset(location, &1))
|> Enum.filter(&is_within_bounds?(&1, map))
end
defp apply_offset({row, col}, {row_offset, col_offset}),
do: {row + row_offset, col + col_offset}
defp get_start_location(map) do
start_row = Enum.find_index(map, &("S" in &1))
start_col = Enum.find_index(Enum.at(map, start_row), &(&1 == "S"))
{start_row, start_col}
end
defp parse_map(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(&String.split(&1, "", trim: true))
end
defp get_symbol_at_location(map, {row, col}), do: map |> Enum.at(row) |> Enum.at(col)
end