229 lines
5.1 KiB
Elixir
229 lines
5.1 KiB
Elixir
defmodule PipeModule do
|
|
@pipe_types %{
|
|
"|" => {:n, :s},
|
|
"-" => {:e, :w},
|
|
"L" => {:n, :e},
|
|
"J" => {:n, :w},
|
|
"7" => {:s, :w},
|
|
"F" => {:s, :e},
|
|
"." => :ground,
|
|
"S" => :animal
|
|
}
|
|
|
|
def parse(input) do
|
|
for u <-
|
|
input
|
|
|> String.split("\n")
|
|
|> Stream.with_index()
|
|
|> Enum.flat_map(fn {line, line_idx} ->
|
|
line
|
|
|> String.graphemes()
|
|
|> Stream.with_index()
|
|
|> Enum.map(fn {pipe_char, index} ->
|
|
{{index, line_idx}, Map.get(@pipe_types, pipe_char)}
|
|
end)
|
|
end),
|
|
into: %{} do
|
|
u
|
|
end
|
|
end
|
|
|
|
def connects?(pipe1, pipe2, _) when pipe1 == :ground or pipe2 == :ground do
|
|
false
|
|
end
|
|
|
|
def connects?(pipe1, pipe2, orientation) do
|
|
can_connect?(pipe1, orientation) and can_connect?(pipe2, flip(orientation))
|
|
end
|
|
|
|
def flip(orientation) do
|
|
case orientation do
|
|
:n -> :s
|
|
:s -> :n
|
|
:w -> :e
|
|
:e -> :w
|
|
end
|
|
end
|
|
|
|
def can_connect?(:animal, _) do
|
|
true
|
|
end
|
|
|
|
def can_connect?({d1, d2}, orientation) do
|
|
d1 == orientation or d2 == orientation
|
|
end
|
|
|
|
def to_orientation({x1, x2}, {y1, y2}) do
|
|
{z1, z2} = {y1 - x1, y2 - x2}
|
|
|
|
case z1 do
|
|
0 ->
|
|
case z2 do
|
|
-1 -> :n
|
|
1 -> :s
|
|
end
|
|
|
|
1 ->
|
|
case z2 do
|
|
0 -> :e
|
|
end
|
|
|
|
-1 ->
|
|
case z2 do
|
|
0 -> :w
|
|
end
|
|
end
|
|
end
|
|
|
|
def furthest_from_animal(loop) do
|
|
loop
|
|
|> Enum.reduce(
|
|
%{},
|
|
fn {pipe, steps}, acc ->
|
|
Map.update(acc, pipe, [steps], fn p -> [steps | p] end)
|
|
end
|
|
)
|
|
|> Stream.filter(fn {_, a} -> a |> Enum.count() == 2 end)
|
|
|> Enum.reject(fn {_, [a, b]} -> a != b or a == 0 end)
|
|
|> Enum.at(0)
|
|
end
|
|
|
|
def find_animal(grid) do
|
|
grid
|
|
|> Enum.find(nil, fn {_, pipe} -> pipe == :animal end)
|
|
|> elem(0)
|
|
end
|
|
def find_loop({i, j}, pipes, grid) do
|
|
pipe = Map.get(grid, {i, j})
|
|
connected_neighbors =
|
|
for ii <- -1..1,
|
|
jj <- -1..1,
|
|
(ii == 0 or jj == 0) and !(ii == 0 and jj == 0),
|
|
{z1, z2} = {i + ii, j + jj},
|
|
z1 != -1 and z2 != -1,
|
|
neighbor = Map.get(grid, {z1, z2}),
|
|
connects?(pipe, neighbor, to_orientation({i, j}, {z1, z2})) do
|
|
{z1, z2}
|
|
end
|
|
|
|
connected_pipes_checked =
|
|
Enum.filter(
|
|
connected_neighbors,
|
|
fn n -> Enum.any?(pipes, fn {p, _} -> n == p end) end
|
|
)
|
|
|
|
if connected_pipes_checked
|
|
|> Enum.count() == 2 do
|
|
pipes
|
|
else
|
|
step = pipes |> Enum.count()
|
|
|
|
Enum.flat_map(
|
|
connected_neighbors
|
|
|> Enum.reject(fn n -> Enum.any?(pipes, fn {p, _} -> p == n end) end),
|
|
fn {ii, jj} ->
|
|
find_loop({ii, jj}, [{{i, j}, step} | pipes], grid)
|
|
end
|
|
)
|
|
end
|
|
end
|
|
|
|
defp loop_as_set(loop) do
|
|
loop |> Stream.map(&elem(&1, 0)) |> MapSet.new()
|
|
end
|
|
|
|
def to_string(pipe) do
|
|
case pipe do
|
|
:ground -> "."
|
|
:animal -> "S"
|
|
# List.to_string([])
|
|
{:n, :s} -> <<0x2551::8>>
|
|
{:e, :w} -> List.to_string([0x2550])
|
|
{:n, :e} -> List.to_string([0x255A])
|
|
{:n, :w} -> List.to_string([0x255D])
|
|
{:s, :w} -> List.to_string([0x2557])
|
|
{:s, :e} -> List.to_string([0x2554])
|
|
_ -> ""
|
|
end
|
|
end
|
|
|
|
def get_tile_under_animal(loop) do
|
|
# TODO
|
|
end
|
|
|
|
def get_inside_count(grid, loop) do
|
|
loop_set = loop_as_set(loop)
|
|
|
|
for room <-
|
|
grid
|
|
|> Enum.sort_by(fn {{x, y}, _} -> {y, x} end)
|
|
|> Enum.map_reduce(
|
|
%{n: false, s: false, o: 0, i: 0},
|
|
fn {{x, y}, tile}, %{n: n, s: s, o: o, i: i} ->
|
|
tile =
|
|
if tile == :animal do
|
|
get_tile_under_animal(loop)
|
|
tile
|
|
else
|
|
tile
|
|
end
|
|
|
|
{n, s, o, i} =
|
|
if loop_set |> MapSet.member?({x, y}) do
|
|
n = if PipeModule.opens_north?(tile), do: !n, else: n
|
|
s = if PipeModule.opens_south?(tile), do: !s, else: s
|
|
{n, s, o, i}
|
|
else
|
|
{i, o} =
|
|
if n || s do
|
|
i = i + 1
|
|
{i, o}
|
|
else
|
|
o = o + 1
|
|
{i, o}
|
|
end
|
|
|
|
{n, s, o, i}
|
|
end
|
|
|
|
tile_location =
|
|
if loop_set |> MapSet.member?({x, y}),
|
|
do: :loop,
|
|
else:
|
|
(case n || s do
|
|
true -> :inside
|
|
false -> :outside
|
|
end)
|
|
|
|
{{{x, y}, {tile, tile_location}}, %{n: n, s: s, o: o, i: i}}
|
|
end
|
|
)
|
|
|> elem(0),
|
|
into: %{} do
|
|
room
|
|
end
|
|
|> Map.values()
|
|
|> Enum.filter(&(elem(&1, 1) == :inside))
|
|
|> Enum.count()
|
|
end
|
|
|
|
def opens_north?({a, b}) when a == :n or b == :n do
|
|
true
|
|
end
|
|
|
|
def opens_north?(_) do
|
|
false
|
|
end
|
|
|
|
def opens_south?({a, b}) when a == :s or b == :s do
|
|
true
|
|
end
|
|
|
|
def opens_south?(:animal) do
|
|
true
|
|
end
|
|
|
|
def opens_south?(_) do
|
|
false
|
|
end
|
|
end
|