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

Day 06

2024/day_06.livemd

Day 06

Mix.install([:kino_aoc])

Kino.configure(inspect: [charlists: :as_lists])

Input

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2024", "6", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION"))
test = "....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#..."

parsed = 
  puzzle_input
  |> String.split("\n")
  |> Enum.with_index()
  |> Enum.flat_map(fn {line, iy} -> 
    String.graphemes(line)
    |> Enum.with_index()
    |> Enum.map(fn {char, ix} -> {{iy, ix}, char} end)
  end)
  |> Map.new()

{{guard_y, guard_x} = guard_pos, _} = Enum.find(parsed, fn {_pos, char} -> char == "^" end)

Part 1

defmodule Part1 do
  def walk(map, {guard_d, guard_y, guard_x}) do
    {new_y, new_x} = case guard_d do
      :up -> {guard_y - 1, guard_x}
      :down -> {guard_y + 1, guard_x}
      :left -> {guard_y, guard_x - 1}
      :right -> {guard_y, guard_x + 1}
    end

    case map[{new_y, new_x}] do
      nil -> map
      "#" -> walk(map, {rotate_right(guard_d), guard_y, guard_x})
      _ -> 
        put_in(map, [{new_y, new_x}], "X") |> walk({guard_d, new_y, new_x})
    end
  end

  defp rotate_right(direction) do
    case direction do
      :up -> :right
      :right -> :down
      :down -> :left
      :left -> :up
    end
  end

  def count_visited(map) do
    map
    |> Map.values()
    |> Enum.count(fn state -> state in ["X", "^"] end)
  end
end

Part1.walk(parsed, {:up, guard_y, guard_x}) |> Part1.count_visited()

Part 2

defmodule Part2 do
  def walk(map, {guard_d, guard_y, guard_x}, visited) do
    {new_y, new_x} = case guard_d do
      :up -> {guard_y - 1, guard_x}
      :down -> {guard_y + 1, guard_x}
      :left -> {guard_y, guard_x - 1}
      :right -> {guard_y, guard_x + 1}
    end

    if MapSet.member?(visited, {guard_d, new_y, new_x}) do
      :loop
    else
      new_visited = MapSet.put(visited, {guard_d, new_y, new_x})
      
      case map[{new_y, new_x}] do
        nil -> MapSet.new(visited, fn {_d, y, x} -> {y, x} end)
        "#" -> walk(map, {rotate_right(guard_d), guard_y, guard_x}, new_visited)
        _ -> 
          put_in(map, [{new_y, new_x}], "X") |> walk({guard_d, new_y, new_x}, new_visited)
      end
    end
  end

  defp rotate_right(direction) do
    case direction do
      :up -> :right
      :right -> :down
      :down -> :left
      :left -> :up
    end
  end
end

parsed
|> Part2.walk({:up, guard_y, guard_x}, MapSet.new([]))
|> Enum.reject(fn pos -> pos == guard_pos end)
|> Task.async_stream(fn {y, x} -> 
    map = put_in(parsed, [{y, x}], "#")
    Part2.walk(map, {:up, guard_y, guard_x}, MapSet.new([]))
  end, ordered: false)
|> Enum.count(&match?({:ok, :loop}, &1))