Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 15

2024/day15.livemd

Day 15

Mix.install([{:utils, path: "#{__DIR__}/utils"}])

Solution

{:ok, contents} = File.read("#{__DIR__}/inputs/day15.txt")

[raw_grid, raw_moves] =
  contents
  |> String.split("\n\n")
  |> Enum.map(&String.split(&1, "\n", trim: true))

grid = raw_grid |> Utils.parse_grid() |> elem(0) |> Map.new()
moves = raw_moves |> Enum.join() |> String.to_charlist()
robot = fn grid ->
  grid |> Enum.find(fn {_, v} -> v == ?@ end) |> elem(0)
end

display = fn coords ->
  {{xmax, ymax}, _} = Enum.max(coords)

  for i <- 0..xmax do
    for j <- 0..ymax do
      Map.get(coords, {i, j})
    end
  end
  |> Enum.join("\n")
  |> IO.puts()

  coords
end
defmodule Day15 do
  defp step({x, y}, c) do
    case c do
      ?^ -> {x - 1, y}
      ?v -> {x + 1, y}
      ?< -> {x, y - 1}
      ?> -> {x, y + 1}
    end
  end

  defp traverse(move, grid, curr, objects) do
    val = Map.get(grid, curr)

    cond do
      val == ?# ->
        {:blocked, objects}

      val == ?. ->
        {:move, objects}

      true ->
        traverse(move, grid, step(curr, move), [curr | objects])
    end
  end

  # Hacky pattern match on empty queue
  defp traverse_wide(_, _, {[], []}, _, objects), do: {:move, objects}

  defp traverse_wide(move, grid, bfs, seen, objects) do
    {{:value, curr}, bfs} = :queue.out(bfs)
    seen_ = MapSet.put(seen, curr)
    objects_ = [curr | objects]
    next = step(curr, move)
    val = Map.get(grid, next)

    cond do
      curr in seen ->
        traverse_wide(move, grid, bfs, seen, objects)

      val == ?# ->
        {:blocked, objects}

      val == ?. ->
        traverse_wide(move, grid, bfs, seen_, objects_)

      true ->
        {x, y} = next

        extra =
          case val do
            ?[ -> {x, y + 1}
            ?] -> {x, y - 1}
          end

        bfs = :queue.in(next, bfs)
        bfs = :queue.in(extra, bfs)
        traverse_wide(move, grid, bfs, seen_, objects_)
    end
  end

  defp update_grid(move, grid, curr) do
    grid
    |> Map.put(step(curr, move), Map.get(grid, curr))
    |> Map.put(curr, ?.)
  end

  def execute(move, {grid, robot}, wide \\ false) do
    {result, objects} =
      if wide and (move == ?^ or move == ?v) do
        traverse_wide(move, grid, :queue.from_list([robot]), MapSet.new(), [])
      else
        traverse(move, grid, robot, [])
      end

    if result == :blocked do
      {grid, robot}
    else
      grid_ = Enum.reduce(objects, grid, &amp;update_grid(move, &amp;2, &amp;1))
      robot_ = step(robot, move)
      {grid_, robot_}
    end
  end

  def sum_boxes(grid, target) do
    grid
    |> Enum.filter(fn {_, v} -> v == target end)
    |> Enum.map(fn {{x, y}, _} -> 100 * x + y end)
    |> Enum.sum()
  end
end

moves
|> Enum.reduce({grid, robot.(grid)}, &amp;Day15.execute/2)
|> elem(0)
|> Day15.sum_boxes(?O)
expand = fn {{x, y}, v} ->
  [a, b] =
    case v do
      ?# -> ~c"##"
      ?O -> ~c"[]"
      ?. -> ~c".."
      ?@ -> ~c"@."
    end

  [{{x, 2 * y}, a}, {{x, 2 * y + 1}, b}]
end

grid2 = grid |> Enum.flat_map(expand) |> Map.new()

moves
|> Enum.reduce({grid2, robot.(grid2)}, &amp;Day15.execute(&amp;1, &amp;2, true))
|> elem(0)
|> Day15.sum_boxes(?[)