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

Day 6: Guard Gallivant

2024/day06.livemd

Day 6: Guard Gallivant

Mix.install([:kino])

Section

input = Kino.Input.textarea("Input", monospace: true)
map =
  input
  |> Kino.Input.read()
  |> String.split()
  |> Enum.map(&String.graphemes/1)
  |> Enum.with_index()
  |> Enum.reduce(%{}, fn {row, y}, map ->
    row
    |> Enum.with_index()
    |> Enum.reduce(map, fn {col, x}, map ->
      Map.put(map, {x, y}, col)
    end)
  end)

guard = Enum.find(map, fn {_p, g} -> g == "^" end)
defmodule Guard do
  def step(guard, map) do
    case Map.get(map, guard |> forward() |> elem(0)) do
      nil -> :out_of_bounds
      "#" -> guard |> rotate() |> step(map)
      _ -> forward(guard)
    end
  end

  def forward({{x, y}, "^"}), do: {{x, y - 1}, "^"}
  def forward({{x, y}, ">"}), do: {{x + 1, y}, ">"}
  def forward({{x, y}, "v"}), do: {{x, y + 1}, "v"}
  def forward({{x, y}, "<"}), do: {{x - 1, y}, "<"}

  def rotate({p, "^"}), do: {p, ">"}
  def rotate({p, ">"}), do: {p, "v"}
  def rotate({p, "v"}), do: {p, "<"}
  def rotate({p, "<"}), do: {p, "^"}

  def walk(guard, map) do
    Stream.unfold(guard, fn
      :out_of_bounds -> nil
      guard -> {guard, step(guard, map)}
    end)
  end
end
patrol = Guard.walk(guard, map) |> Map.new() |> Map.keys()

Enum.count(patrol)
Enum.count(patrol, fn p ->
  guard
  |> Guard.walk(Map.put(map, p, "#"))
  |> Enum.reduce_while(MapSet.new(), fn guard, path ->
    if MapSet.member?(path, guard) do
      {:halt, :loop}
    else
      {:cont, MapSet.put(path, guard)}
    end
  end) == :loop
end)