d15
Section
defmodule D15 do
defstruct grid: %{}, robot: nil, moves: []
def widen(map) do
for line <- String.split(map) do
for char <- String.to_charlist(line) do
case char do
?# -> "##"
?O -> "[]"
?@ -> "@."
?. -> ".."
end
end
|> Enum.join()
end
|> Enum.join("\n")
end
def parse(input, part \\ :part1) do
[map, moves] = String.split(input, "\n\n")
map = if part == :part2, do: D15.widen(map), else: map
{grid, robot} =
for {line, row} <- Enum.with_index(String.split(map)),
{char, col} <- Enum.with_index(String.to_charlist(line)),
reduce: {%{}, nil} do
{grid, robot} ->
case char do
?# -> {Map.put(grid, {row, col}, :wall), robot}
?O -> {Map.put(grid, {row, col}, :box), robot}
?[ -> {Map.put(grid, {row, col}, :left), robot}
?] -> {Map.put(grid, {row, col}, :right), robot}
?@ -> {grid, {row, col}}
?. -> {grid, robot}
end
end
%D15{grid: grid, robot: robot, moves: String.to_charlist(moves) |> Enum.filter(&(&1 != ?\n))}
end
defp vec_add({r, c}, {dr, dc}), do: {r + dr, c + dc}
defp move(map, src, dest) do
{value, map} = Map.pop!(map, src)
Map.put(map, dest, value)
end
def displace(grid, pos, delta) do
case Map.get(grid, pos) do
:wall ->
nil
nil ->
grid
side when side != :box and elem(delta, 1) == 0 ->
dest = vec_add(pos, delta)
case displace(grid, dest, delta) do
nil ->
nil
grid ->
cdelta =
case side do
:left -> {0, 1}
:right -> {0, -1}
end
cpos = vec_add(pos, cdelta)
cdest = vec_add(cpos, delta)
case displace(grid, cdest, delta) do
nil ->
nil
grid ->
grid
|> move(pos, dest)
|> move(cpos, cdest)
end
end
box ->
dest = vec_add(pos, delta)
case displace(grid, dest, delta) do
nil ->
nil
grid ->
grid
|> Map.delete(pos)
|> Map.put(dest, box)
end
end
end
def step(%D15{grid: grid, robot: robot, moves: [move | moves]} = world) do
delta =
case move do
?< -> {0, -1}
?> -> {0, 1}
?^ -> {-1, 0}
?v -> {1, 0}
end
dest = vec_add(robot, delta)
case displace(grid, dest, delta) do
nil -> %D15{world | moves: moves}
grid -> %D15{world | grid: grid, robot: dest, moves: moves}
end
end
def run(%D15{moves: []} = world), do: world
def run(world), do: D15.run(D15.step(world))
def gps(%D15{grid: grid}) do
for {{row, col}, object} <- grid do
if object == :box or object == :left, do: row * 100 + col, else: 0
end
|> Enum.sum()
end
end
ExUnit.start()
defmodule D15.Test do
use ExUnit.Case
test "small" do
input = "########
#..O.O.#
##@.O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
<^^>>>vv>v<<
"
assert 2028 == D15.parse(input) |> D15.run() |> D15.gps()
end
test "large" do
input = "##########
#..O..O.O#
#......O.#
#.OO..O.O#
#..O@..O.#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########
^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv<
<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^><
^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><
v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^
"
assert 10092 == D15.parse(input) |> D15.run() |> D15.gps()
assert 9021 == D15.parse(input, :part2) |> D15.run() |> D15.gps()
end
end
ExUnit.run()
input = File.read!(__DIR__ <> "/input")
D15.parse(input) |> D15.run() |> D15.gps()
D15.parse(input, :part2) |> D15.run() |> D15.gps()