Day 15. That was hard
parent
9d8e3f05a6
commit
19597d95b0
@ -0,0 +1,225 @@
|
||||
defmodule Day15 do
|
||||
def run do
|
||||
input = File.read!("data.txt") |> String.trim() |> String.split("\n\n")
|
||||
map_list = hd(input) |> String.split("\n") |> Enum.map(&String.graphemes/1)
|
||||
|
||||
height = length(map_list)
|
||||
width = length(hd(map_list))
|
||||
|
||||
# convert the map List to a map Map
|
||||
map =
|
||||
Enum.concat(map_list)
|
||||
|> Enum.with_index()
|
||||
|> Enum.reduce(%{}, fn {el, idx}, acc ->
|
||||
Map.put(acc, {div(idx, height), rem(idx, width)}, el)
|
||||
end)
|
||||
|
||||
moves = Enum.at(input, 1) |> String.replace("\n", "") |> String.graphemes()
|
||||
|
||||
# step over each move, reducing to create a map
|
||||
part1 =
|
||||
Enum.reduce(moves, map, fn move, acc ->
|
||||
step(move, acc)
|
||||
end)
|
||||
# grab all the Os, calculate the GPS value for each, and sum them
|
||||
|> Enum.filter(fn {_k, v} -> v == "O" end)
|
||||
|> Enum.map(fn {{x, y}, _} -> 100 * x + y end)
|
||||
|> Enum.sum()
|
||||
|
||||
dbg(part1)
|
||||
|
||||
# expand the map
|
||||
map2 =
|
||||
Enum.reduce(map, %{}, fn {{x, y}, el}, acc ->
|
||||
{el1, el2} =
|
||||
case el do
|
||||
"O" -> {"[", "]"}
|
||||
"@" -> {"@", "."}
|
||||
x -> {x, x}
|
||||
end
|
||||
|
||||
Map.put(acc, {x, y * 2}, el1)
|
||||
|> Map.put({x, y * 2 + 1}, el2)
|
||||
end)
|
||||
|
||||
render(map2, {width * 2, height})
|
||||
|
||||
part2 =
|
||||
Enum.reduce(moves, map2, fn move, acc ->
|
||||
step(move, acc)
|
||||
end)
|
||||
|> render({width * 2, height})
|
||||
|> Enum.filter(fn {_k, v} -> v == "[" end)
|
||||
|> Enum.map(fn {{x, y}, _} -> 100 * x + y end)
|
||||
|> Enum.sum()
|
||||
|
||||
dbg(part2)
|
||||
end
|
||||
|
||||
defp step(move, map) do
|
||||
dir =
|
||||
case move do
|
||||
"^" -> {-1, 0}
|
||||
">" -> {0, 1}
|
||||
"v" -> {1, 0}
|
||||
"<" -> {0, -1}
|
||||
end
|
||||
|
||||
{r_row, r_col} = robot_location(map)
|
||||
next_loc = {r_row + elem(dir, 0), r_col + elem(dir, 1)}
|
||||
next_el = Map.get(map, next_loc, ".")
|
||||
|
||||
case next_el do
|
||||
# if next el is empty, just move the robot there
|
||||
"." -> Map.put(map, {r_row, r_col}, ".") |> Map.put(next_loc, "@")
|
||||
# if it's a wall, don't change the map
|
||||
"#" -> map
|
||||
# if it's a block, push on it
|
||||
"O" -> push(dir, next_loc, map)
|
||||
# if it's a big block, push on it
|
||||
"[" -> push_big(dir, {r_row, r_col}, map)
|
||||
"]" -> push_big(dir, {r_row, r_col}, map)
|
||||
end
|
||||
end
|
||||
|
||||
# pushing big blocks
|
||||
defp push_big(dir, {x, y}, map) do
|
||||
case dir do
|
||||
# pushing left or right
|
||||
{0, dy} ->
|
||||
case next_empty(dir, {x, y}, map) do
|
||||
nil ->
|
||||
map
|
||||
|
||||
{free_x, free_y} ->
|
||||
Enum.reduce(free_y..y, map, fn col, acc ->
|
||||
el = Map.get(acc, {free_x, col}, ".")
|
||||
next_el = if el == "@", do: ".", else: Map.get(acc, {free_x, col - dy}, ".")
|
||||
Map.put(acc, {free_x, col}, next_el)
|
||||
end)
|
||||
end
|
||||
|
||||
# pushing up or down
|
||||
{dx, 0} ->
|
||||
{bx, by} = {x + dx, y}
|
||||
el = Map.get(map, {bx, by})
|
||||
box_location = if el == "[", do: {bx, by}, else: {bx, by - 1}
|
||||
# get list of boxes that can move
|
||||
boxes = move_big_boxes(dir, box_location, map)
|
||||
|
||||
if is_nil(boxes) do
|
||||
map
|
||||
else
|
||||
{rx, ry} = robot_location(map)
|
||||
|
||||
# sort it so we don't overwrite stuff
|
||||
Enum.sort(boxes, fn {x1, _}, {x2, _} ->
|
||||
if dx == -1, do: x1 < x2, else: x1 > x2
|
||||
end)
|
||||
# now actually move all the boxes up or down
|
||||
|> Enum.reduce(map, fn {x, y}, acc ->
|
||||
Map.put(acc, {x, y}, ".")
|
||||
|> Map.put({x, y + 1}, ".")
|
||||
|> Map.put({x + dx, y}, "[")
|
||||
|> Map.put({x + dx, y + 1}, "]")
|
||||
end)
|
||||
|> Map.put({rx, ry}, ".")
|
||||
|> Map.put({rx + dx, ry}, "@")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# we're trying to push a big box in this direction
|
||||
# check all boxes in its path, recursively, until we find an open spot
|
||||
# we only do this up and down so we can ignore dy
|
||||
# if we've found a spot, return all boxes that can move
|
||||
# if there is no spot, return nil
|
||||
defp move_big_boxes({dx, dy}, {x, y}, map) do
|
||||
left_el = Map.get(map, {x + dx, y}, ".")
|
||||
right_el = Map.get(map, {x + dx, y + 1}, ".")
|
||||
|
||||
case {left_el, right_el} do
|
||||
# if the 2 spots above/below are free, we've found a spot
|
||||
{".", "."} ->
|
||||
[{x, y}]
|
||||
|
||||
# if either are walls, this box can't move
|
||||
{l, r} when l == "#" or r == "#" ->
|
||||
nil
|
||||
|
||||
# if it's just 1 block above
|
||||
{"[", "]"} ->
|
||||
res = move_big_boxes({dx, dy}, {x + dx, y}, map)
|
||||
if is_nil(res), do: nil, else: [{x, y} | res]
|
||||
|
||||
{"]", "."} ->
|
||||
res = move_big_boxes({dx, dy}, {x + dx, y - 1}, map)
|
||||
if is_nil(res), do: nil, else: [{x, y} | res]
|
||||
|
||||
{".", "["} ->
|
||||
res = move_big_boxes({dx, dy}, {x + dx, y + 1}, map)
|
||||
if is_nil(res), do: nil, else: [{x, y} | res]
|
||||
|
||||
{"]", "["} ->
|
||||
res = move_big_boxes({dx, dy}, {x + dx, y - 1}, map)
|
||||
|
||||
if is_nil(res) do
|
||||
nil
|
||||
else
|
||||
res2 = move_big_boxes({dx, dy}, {x + dx, y + 1}, map)
|
||||
|
||||
if is_nil(res2) do
|
||||
nil
|
||||
else
|
||||
Enum.concat([{x, y} | res], res2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# if the block is pushable in that direction, push it and move the robot
|
||||
defp push(dir, loc, map) do
|
||||
case next_empty(dir, loc, map) do
|
||||
# if there isn't a next empty spot, do nothing
|
||||
nil ->
|
||||
map
|
||||
|
||||
# if there is a next empty spot, put a O there, move the robot to the next spot, replace robot's position with a .
|
||||
{x, y} ->
|
||||
Map.put(map, loc, "@") |> Map.put({x, y}, "O") |> Map.put(robot_location(map), ".")
|
||||
end
|
||||
end
|
||||
|
||||
# finds the next empty spot in a given direction starting at the given location
|
||||
defp next_empty({dir_x, dir_y}, {loc_x, loc_y}, map) do
|
||||
case Map.get(map, {loc_x, loc_y}, ".") do
|
||||
"#" -> nil
|
||||
"." -> {loc_x, loc_y}
|
||||
_ -> next_empty({dir_x, dir_y}, {loc_x + dir_x, loc_y + dir_y}, map)
|
||||
end
|
||||
end
|
||||
|
||||
# return the {row, col} of the robot
|
||||
defp robot_location(map) do
|
||||
Enum.find(map, fn {_key, val} -> val == "@" end) |> elem(0)
|
||||
end
|
||||
|
||||
defp render(map, {width, height}) do
|
||||
Enum.each(0..(height - 1), fn row ->
|
||||
Enum.each(0..(width - 1), fn col ->
|
||||
el = Map.get(map, {row, col}, ".")
|
||||
color = if el == "@", do: :red_background, else: :default_background
|
||||
formatted = IO.ANSI.format([color, el])
|
||||
IO.write(formatted)
|
||||
end)
|
||||
|
||||
IO.write("\n")
|
||||
end)
|
||||
|
||||
IO.write("\n")
|
||||
|
||||
map
|
||||
end
|
||||
end
|
||||
|
||||
Day15.run()
|
Loading…
Reference in New Issue