187 lines
4.3 KiB
Elixir
187 lines
4.3 KiB
Elixir
defmodule Lightmap do
|
|
defstruct grid: %{}, height: 0, width: 0
|
|
|
|
|
|
@char_map %{
|
|
?. => :space,
|
|
?/ => :fw_mirror,
|
|
|
|
?\\ => :bk_mirror,
|
|
?| => :vert_split,
|
|
?- => :horz_split
|
|
}
|
|
def out_of_bounds?(%Lightmap{height: height, width: width}, {x, y}) do
|
|
x < 0 || y < 0 || y >= height || x >= width
|
|
end
|
|
|
|
def parse(input) do
|
|
grid =
|
|
for ent <-
|
|
|
|
input
|
|
|> String.split("\n")
|
|
|> Stream.with_index()
|
|
|> Stream.flat_map(fn {line, row_index} ->
|
|
line
|
|
|> String.to_charlist()
|
|
|> Stream.with_index()
|
|
|> Enum.map(fn {char, col_index} ->
|
|
|
|
{{col_index, row_index}, @char_map[char]}
|
|
end)
|
|
end),
|
|
into: %{} do
|
|
ent
|
|
end
|
|
|
|
%Lightmap{
|
|
grid: grid,
|
|
height: input |> String.trim() |> String.split("\n") |> Enum.count(),
|
|
width: input |> String.trim() |> String.split("\n") |> Enum.at(0) |> String.length()
|
|
}
|
|
end
|
|
end
|
|
|
|
defmodule Lightbeam do
|
|
def trace([], _, positions) do
|
|
positions
|
|
end
|
|
|
|
def trace(heads, map, positions) do
|
|
new_heads =
|
|
heads
|
|
|> Stream.flat_map(fn {pos, dir} ->
|
|
next_position(pos, dir, Map.get(map.grid, pos))
|
|
end)
|
|
|> Stream.reject(fn {new_pos, _} ->
|
|
Lightmap.out_of_bounds?(map, new_pos)
|
|
end)
|
|
|> Enum.reject(fn {new_pos, new_dir} ->
|
|
MapSet.member?(positions, {new_pos, new_dir})
|
|
end)
|
|
|
|
positions =
|
|
for {pos, dir} <- new_heads,
|
|
reduce: positions do
|
|
acc -> MapSet.put(acc, {pos, dir})
|
|
end
|
|
|
|
trace(new_heads, map, positions)
|
|
end
|
|
|
|
def solve_for_pos_and_direction(pos, direction, map) do
|
|
initial_pos_dir = {pos, direction}
|
|
|
|
|
|
Lightbeam.trace([initial_pos_dir], map, MapSet.new([initial_pos_dir]))
|
|
|> Stream.map(&elem(&1, 0))
|
|
|> Stream.uniq()
|
|
|> Enum.count()
|
|
end
|
|
|
|
|
|
def solve_for_max_along_edges(puzzle_input) do
|
|
map = Lightmap.parse(puzzle_input)
|
|
rows = puzzle_input |> String.trim() |> String.split("\n") |> Enum.with_index()
|
|
|
|
{first_row, _} = rows |> Enum.at(0)
|
|
{_, last_row_idx} = rows |> Enum.at(-1)
|
|
|
|
last_col_idx = (first_row |> String.length()) - 1
|
|
|
|
|
|
for row <- 0..last_row_idx, reduce: 0 do
|
|
acc -> max(acc, Lightbeam.solve_for_pos_and_direction({0, row}, :right, map))
|
|
end
|
|
|> max(
|
|
for row <- 0..last_row_idx, reduce: 0 do
|
|
acc -> max(acc, Lightbeam.solve_for_pos_and_direction({last_col_idx, row}, :left, map))
|
|
end
|
|
)
|
|
|> max(
|
|
|
|
for col <- 0..last_col_idx, reduce: 0 do
|
|
acc -> max(acc, Lightbeam.solve_for_pos_and_direction({col, 0}, :down, map))
|
|
end
|
|
)
|
|
|> max(
|
|
for col <- 0..last_col_idx, reduce: 0 do
|
|
acc -> max(acc, Lightbeam.solve_for_pos_and_direction({col, last_row_idx}, :up, map))
|
|
end
|
|
)
|
|
|
|
end
|
|
|
|
|
|
@direction_tuple %{
|
|
:up => {0, -1},
|
|
:down => {0, 1},
|
|
:left => {-1, 0},
|
|
|
|
:right => {1, 0}
|
|
}
|
|
|
|
@direction_map %{
|
|
:vert_split => %{
|
|
:up => [:up],
|
|
:down => [:down],
|
|
:left => [:up, :down],
|
|
:right => [:up, :down]
|
|
|
|
},
|
|
:horz_split => %{
|
|
:left => [:left],
|
|
:right => [:right],
|
|
:up => [:left, :right],
|
|
:down => [:left, :right]
|
|
},
|
|
:fw_mirror => %{
|
|
:left => [:down],
|
|
:up => [:right],
|
|
:right => [:up],
|
|
:down => [:left]
|
|
},
|
|
:bk_mirror => %{
|
|
:left => [:up],
|
|
:up => [:left],
|
|
:right => [:down],
|
|
:down => [:right]
|
|
},
|
|
:space => %{
|
|
:left => [:left],
|
|
|
|
:right => [:right],
|
|
:up => [:up],
|
|
|
|
:down => [:down]
|
|
}
|
|
}
|
|
|
|
def next_position(current_position, direction, tile) do
|
|
@direction_map[tile][direction]
|
|
|> Enum.map(fn dir ->
|
|
|
|
{
|
|
AoC.Util.Tuple.pairwise_sum(current_position, @direction_tuple[dir]),
|
|
dir
|
|
}
|
|
end)
|
|
end
|
|
|
|
def visualize_trace(trace, input) do
|
|
for {row, r_idx} <- input |> String.trim() |> String.split("\n") |> Stream.with_index(),
|
|
{_, col} <- row |> String.to_charlist() |> Stream.with_index(),
|
|
max_col = row |> String.length() do
|
|
if trace |> Enum.any?(&(elem(&1, 0) === {col, r_idx})) do
|
|
IO.write("#")
|
|
else
|
|
IO.write(".")
|
|
|
|
end
|
|
|
|
if col === max_col - 1 do
|
|
IO.write("\n")
|
|
end
|
|
end
|
|
end
|
|
end
|