Day 22
Mix.install([{:kino, "~> 0.5.0"}])
Section
input = """
...#
.#..
#...
....
...#.......#
........#...
..#....#....
..........#.
...#....
.....#..
.#......
......#.
10R5L5R10L4R5L5
"""
input = Kino.Input.textarea("paste input here:")
input = Kino.Input.read(input)
defmodule Board do
def min_max_row(board, at_col \\ :any) do
for {{row, col}, _} <- board,
at_col == :any or col == at_col do
row
end
|> Enum.min_max()
end
def min_max_col(board, at_row \\ :any) do
for {{row, col}, _} <- board,
at_row == :any or row == at_row do
col
end
|> Enum.min_max()
end
def pprint(board, {cur_row, cur_col} \\ {-1, -1}, dir \\ ?>) do
{min_row, max_row} = min_max_row(board)
{min_col, max_col} = min_max_col(board)
IO.puts("")
min_row..max_row
|> Enum.each(fn row ->
min_col..max_col
|> Enum.map(fn col ->
if row == cur_row and col == cur_col do
dir
else
Map.get(board, {row, col}, ?\s)
end
end)
|> IO.puts()
end)
end
def navigate(board, moves) do
{min_row, _} = min_max_row(board)
min_open_col =
for {{^min_row, col}, ?.} <- board do
col
end
|> Enum.min()
navigate(board, moves, {min_row, min_open_col}, ?>)
end
def rotate(dir, l_or_r) do
case l_or_r do
?L ->
case dir do
?> -> ?^
?^ -> ?<
?< -> ?v
?v -> ?>
end
?R ->
case dir do
?> -> ?v
?v -> ?<
?< -> ?^
?^ -> ?>
end
end
end
def move(board, {row, col}, dir) do
{new_row, new_col} =
case dir do
?^ -> {row - 1, col}
?> -> {row, col + 1}
?v -> {row + 1, col}
?< -> {row, col - 1}
end
if Map.has_key?(board, {new_row, new_col}) do
{{new_row, new_col}, dir}
else
{min, max} =
if dir == ?^ or dir == ?v do
min_max_row(board, col)
else
min_max_col(board, row)
end
case dir do
?^ -> {{max, new_col}, dir}
?v -> {{min, new_col}, dir}
?> -> {{new_row, min}, dir}
?< -> {{new_row, max}, dir}
end
end
end
def offset_on_line({row, col}, {line_r1, line_c1}, {line_r2, line_c2}) do
cond do
line_c1 == line_c2 ->
{min_r, max_r} = Enum.min_max([line_r1, line_r2])
if col == line_c1 and row >= min_r and row < max_r do
if min_r == line_r1 do
row - min_r
else
max_r - 1 - row
end
end
line_r1 == line_r2 ->
{min_c, max_c} = Enum.min_max([line_c1, line_c2])
if row == line_r1 and col >= min_c and col < max_c do
if min_c == line_c1 do
col - min_c
else
max_c - 1 - col
end
end
end
end
def reverse_dir(d) do
case d do
?^ -> ?v
?> -> ?<
?v -> ?^
?< -> ?>
end
end
def move_cubed(board, {row, col}, dir) do
{new_row, new_col} =
case dir do
?^ -> {row - 1, col}
?> -> {row, col + 1}
?v -> {row + 1, col}
?< -> {row, col - 1}
end
if Map.has_key?(board, {new_row, new_col}) do
{{new_row, new_col}, dir}
else
size = 50
IO.puts("before:")
pprint(board, {row, col}, dir)
{new_coord, new_dir} =
[
# the line coord1 -> coord2 shares edge on cube with coord3 -> coord4, offset
# and changes from first direction to second
# coord on line if in coord1={row*size, col*size} < coord2
{{0, size}, {0, 2 * size}, {3 * size, 0}, {4 * size, 0}, ?^, ?>},
{{0, 2 * size}, {0, 3 * size}, {4 * size - 1, 0}, {4 * size - 1, size}, ?^, ?^},
{{0, size}, {size, size}, {3 * size, 0}, {2 * size, 0}, ?<, ?>},
{{0, 3 * size - 1}, {size, 3 * size - 1}, {3 * size, 2 * size - 1},
{2 * size, 2 * size - 1}, ?>, ?<},
{{size - 1, 2 * size}, {size - 1, 3 * size}, {size, 2 * size - 1},
{2 * size, 2 * size - 1}, ?v, ?<},
{{size, size}, {2 * size, size}, {2 * size, 0}, {2 * size, size}, ?<, ?v},
{{3 * size - 1, size}, {3 * size - 1, 2 * size}, {3 * size, size - 1},
{4 * size, size - 1}, ?v, ?<}
]
# add the reverse edge mapping also
|> Enum.flat_map(fn {p1, p2, p3, p4, d1, d2} = m ->
[m, {p3, p4, p1, p2, reverse_dir(d2), reverse_dir(d1)}]
end)
|> Enum.find_value(fn {l1_start, l1_end, {l2_r1, l2_c1}, {l2_r2, l2_c2}, d1, d2} ->
offset = offset_on_line({row, col}, l1_start, l1_end)
if dir == d1 and offset do
cond do
l2_c1 == l2_c2 ->
if l2_r1 < l2_r2 do
{{l2_r1 + offset, l2_c1}, d2}
else
{{l2_r1 - 1 - offset, l2_c1}, d2}
end
l2_r1 == l2_r2 ->
if l2_c1 < l2_c2 do
{{l2_r1, l2_c1 + offset}, d2}
else
{{l2_r1, l2_c1 - 1 - offset}, d2}
end
end
end
end)
IO.puts("after:")
pprint(board, new_coord, new_dir)
{new_coord, new_dir}
end
end
def open?(board, coord), do: board[coord] == ?.
def navigate(board, moves, {row, col} = coord, dir) do
# pprint(board, coord, dir)
case moves do
"" ->
# pprint(board, coord, dir)
1000 * (row + 1) + 4 * (col + 1) + Enum.find_index('>v<^', &(&1 == dir))
"L" <> rest ->
navigate(board, rest, coord, rotate(dir, ?L))
"R" <> rest ->
navigate(board, rest, coord, rotate(dir, ?R))
_ ->
{amount, rest} = Integer.parse(moves)
{new_coord, new_dir} =
1..amount
|> Enum.reduce_while({coord, dir}, fn _, {coord, dir} ->
# part 1
# {new_coord, new_dir} = move(board, coord, dir)
# part 2
{new_coord, new_dir} = move_cubed(board, coord, dir)
if open?(board, new_coord) do
{:cont, {new_coord, new_dir}}
else
{:halt, {coord, dir}}
end
end)
navigate(board, rest, new_coord, new_dir)
end
end
end
[board, moves] = String.split(input, "\n\n")
moves = String.trim(moves)
board
|> String.split("\n", trim: true)
|> Enum.with_index()
|> Enum.flat_map(fn {line, row} ->
Enum.with_index(to_charlist(line))
|> Enum.map(fn {c, col} ->
{{row, col}, c}
end)
|> Enum.reject(fn {_, c} -> c == ?\s end)
end)
|> Map.new()
# |> Map.has_key?({8, 7})
# |> Board.pprint({5, 3}, ?v)
# |> Board.move({7, 3}, ?v)
# |> Board.open?({7, 3})
|> Board.navigate(moves)