Day 22
Mix.install([
{:kino, "~> 0.8.0"}
])
Puzzle Input
area = Kino.Input.textarea("Puzzle Input")
puzzle_input = Kino.Input.read(area)
example_input = """
...#
.#..
#...
....
...#.......#
........#...
..#....#....
..........#.
...#....
.....#..
.#......
......#.
10R5L5R10L4R5L5
"""
input = example_input
Common
[map, navigation] = String.split(input, "\n\n", parts: 2)
tiles =
for {line, row} <- map |> String.split("\n", trim: true) |> Stream.with_index(1),
{tile, column} <- line |> String.codepoints() |> Stream.with_index(1) do
{{column, row}, tile}
end
|> Map.new()
instructions =
navigation
|> String.split(~r{(L|R)}, include_captures: true, trim: true)
|> Enum.map(fn instruction ->
case instruction do
"L" -> {:turn, "L"}
"R" -> {:turn, "R"}
steps -> {:step, steps |> Integer.parse() |> elem(0)}
end
end)
defmodule Player do
defstruct [:position, :direction]
def turn(%Player{direction: direction} = player, opts \\ [clockwise?: true]) do
clockwise? = Keyword.fetch!(opts, :clockwise?)
direction =
case direction do
:up -> if clockwise?, do: :right, else: :left
:left -> if clockwise?, do: :up, else: :down
:down -> if clockwise?, do: :left, else: :right
:right -> if clockwise?, do: :down, else: :up
end
%{player | direction: direction}
end
def next_position(%Player{direction: direction, position: {x, y}}) do
case direction do
:left -> {x - 1, y}
:right -> {x + 1, y}
:up -> {x, y - 1}
:down -> {x, y + 1}
end
end
def password(%Player{direction: direction, position: {x, y}}) do
directions = %{
right: 0,
down: 1,
left: 2,
up: 3
}
1000 * y + 4 * x + directions[direction]
end
end
Part One
12512 is too low
defmodule Board do
def start_position(tiles) do
tiles
|> Stream.filter(fn {{_x, y}, tile} -> tile == "." && y == 1 end)
|> Stream.map(&elem(&1, 0))
|> Enum.min()
end
def next_tile(tiles, %Player{} = player) do
next = Player.next_position(player)
case Map.get(tiles, next, " ") do
" " ->
wrap(tiles, player)
tile ->
{next, tile}
end
end
def wrap(tiles, %Player{} = player) do
%{position: {x, y}, direction: direction} = player
horizontal? = direction in [:left, :right]
matcher? =
if horizontal? do
&match?({_, ^y}, &1)
else
&match?({^x, _}, &1)
end
axis_tiles =
tiles
|> Stream.filter(fn {position, tile} ->
matcher?.(position) && tile != " "
end)
max_side? = direction in [:left, :up]
if max_side? do
Enum.max_by(axis_tiles, &elem(&1, 0))
else
Enum.min_by(axis_tiles, &elem(&1, 0))
end
end
def execute(tiles, %Player{} = player, instruction) do
case instruction do
{:turn, "L"} ->
Player.turn(player, clockwise?: false)
{:turn, "R"} ->
Player.turn(player, clockwise?: true)
{:step, steps} ->
Enum.reduce_while(1..steps, player, fn _, player ->
{next_position, next_tile} = next_tile(tiles, player)
case next_tile do
"#" -> {:halt, player}
"." -> {:cont, %{player | position: next_position}}
end
end)
end
end
end
player = %Player{position: Board.start_position(tiles), direction: :right}
player = Enum.reduce(instructions, player, &Board.execute(tiles, &2, &1))
Player.password(player)
Part Two
defmodule Cube do
@side 4
@sides %{
{2, 0} => 1,
{0, 1} => 2,
{1, 1} => 3,
{2, 1} => 4,
{2, 2} => 5,
{3, 2} => 6
}
def start_position(tiles) do
tiles
|> Stream.filter(fn {{_x, y}, tile} -> tile == "." && y == 1 end)
|> Stream.map(&elem(&1, 0))
|> Enum.min()
end
def next_tile(tiles, %Player{} = player) do
next = Player.next_position(player)
case Map.get(tiles, next, " ") do
" " ->
wrap(tiles, player)
tile ->
{next, tile}
end
end
def wrap(tiles, %Player{} = player) do
%{position: {x, y}, direction: direction} = player
horizontal? = direction in [:left, :right]
matcher? =
if horizontal? do
&match?({_, ^y}, &1)
else
&match?({^x, _}, &1)
end
axis_tiles =
tiles
|> Stream.filter(fn {position, tile} ->
matcher?.(position) && tile != " "
end)
max_side? = direction in [:left, :up]
if max_side? do
Enum.max_by(axis_tiles, &elem(&1, 0))
else
Enum.min_by(axis_tiles, &elem(&1, 0))
end
end
def side({x, y}) do
u = floor((x - 1) / @side)
v = floor((y - 1) / @side)
Map.fetch!(@sides, {u, v})
end
def execute(tiles, %Player{} = player, instruction) do
case instruction do
{:turn, "L"} ->
Player.turn(player, clockwise?: false)
{:turn, "R"} ->
Player.turn(player, clockwise?: true)
{:step, steps} ->
Enum.reduce_while(1..steps, player, fn _, player ->
{next_position, next_tile} = next_tile(tiles, player)
case next_tile do
"#" -> {:halt, player}
"." -> {:cont, %{player | position: next_position}}
end
end)
end
end
end
Cube.side({1, 9})