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

Day Twenty Two

day22.livemd

Day Twenty Two

Mix.install([
  {:kino, "~> 0.7.0"}
])

Section

input = Kino.Input.textarea("Input")
defmodule DayTwentyTwo do
  def solve1(input) do
    {map, path} = parse(input)

    # wrap vs wrap2
    follow(map, path)
  end

  defp follow(map, path) do
    start_at =
      map
      |> Map.keys()
      |> Enum.filter(fn {_x, y} -> y == 1 end)
      |> Enum.min_by(fn {x, _y} -> x end)

    {{x, y}, dir} = follow(start_at, {1, 0}, map, path)

    1000 * y + 4 * x + facing(dir)
  end

  defp follow(pos, dir, _, []) do
    {pos, dir}
  end

  defp follow(pos, dir, map, [:left | path]) do
    follow(pos, turn(dir, :left), map, path)
  end

  defp follow(pos, dir, map, [:right | path]) do
    follow(pos, turn(dir, :right), map, path)
  end

  defp follow(pos, dir, map, [0 | path]) do
    follow(pos, dir, map, path)
  end

  defp follow(pos, dir, map, [steps | path]) do
    case step(pos, dir, map) do
      {:ok, npos, ndir} ->
        follow(npos, ndir, map, [steps - 1 | path])

      {:halt, npos, ndir} ->
        follow(npos, ndir, map, path)
    end
  end

  defp step({px, py} = pos, {dx, dy} = dir, map) do
    npos = {px + dx, py + dy}

    case map[npos] do
      :open ->
        {:ok, npos, dir}

      :wall ->
        {:halt, pos, dir}

      nil ->
        {npos, ndir} = wrap2(pos, dir)

        case map[npos] do
          :open ->
            {:ok, npos, ndir}

          :wall ->
            {:halt, pos, dir}
        end
    end
  end

  # (pos, dir) -> npos
  defp wrap({_x, y}, {1, 0}, map) do
    map
    |> Map.keys()
    |> Enum.filter(fn {_, my} -> my == y end)
    |> Enum.min_by(fn {mx, _} -> mx end)
  end

  defp wrap({_x, y}, {-1, 0}, map) do
    map
    |> Map.keys()
    |> Enum.filter(fn {_, my} -> my == y end)
    |> Enum.max_by(fn {mx, _} -> mx end)
  end

  defp wrap({x, _y}, {0, 1}, map) do
    map
    |> Map.keys()
    |> Enum.filter(fn {mx, _} -> mx == x end)
    |> Enum.min_by(fn {_, my} -> my end)
  end

  defp wrap({x, _y}, {0, -1}, map) do
    map
    |> Map.keys()
    |> Enum.filter(fn {mx, _} -> mx == x end)
    |> Enum.max_by(fn {_, my} -> my end)
  end

  @faces %{
    front: {51..100, 1..50},
    right: {101..150, 1..50},
    bottom: {51..100, 51..100},
    back: {51..100, 101..150},
    left: {1..50, 101..150},
    top: {1..50, 151..200}
  }

  @right {1, 0}
  @down {0, 1}
  @left {-1, 0}
  @up {0, -1}

  # (pos, dir) -> {npos, ndir}
  defp wrap2({x, y} = pos, dir) do
    case {face(pos), dir} do
      # front
      {:front, @right} -> nil
      {:front, @down} -> nil
      {:front, @left} -> {{1, mmap(y, 1..50, 150..101)}, @right}
      {:front, @up} -> {{1, mmap(x, 51..100, 151..200)}, @right}
      # right
      {:right, @right} -> {{100, mmap(y, 1..50, 150..101)}, @left}
      {:right, @down} -> {{100, mmap(x, 101..150, 51..100)}, @left}
      {:right, @left} -> nil
      {:right, @up} -> {{mmap(x, 101..150, 1..50), 200}, @up}
      # back
      {:back, @right} -> {{150, mmap(y, 101..150, 50..1)}, @left}
      {:back, @down} -> {{50, mmap(x, 51..100, 151..200)}, @left}
      {:back, @left} -> nil
      {:back, @up} -> nil
      # left
      {:left, @right} -> nil
      {:left, @down} -> nil
      {:left, @left} -> {{51, mmap(y, 101..150, 50..1)}, @right}
      {:left, @up} -> {{51, mmap(x, 1..50, 51..100)}, @right}
      # top
      {:top, @right} -> {{mmap(y, 151..200, 51..100), 150}, @up}
      {:top, @down} -> {{mmap(x, 1..50, 101..150), 1}, @down}
      {:top, @left} -> {{mmap(y, 151..200, 51..100), 1}, @down}
      {:top, @up} -> nil
      # bottom
      {:bottom, @right} -> {{mmap(y, 51..100, 101..150), 50}, @up}
      {:bottom, @down} -> nil
      {:bottom, @left} -> {{mmap(y, 51..100, 1..50), 101}, @down}
      {:bottom, @up} -> nil
    end
  end

  def mmap(v, r1, r2) do
    i = Enum.find_index(r1, fn w -> w == v end)
    Enum.at(r2, i)
  end

  def mmapy(v, {_, r1}, {_, r2}) do
    i = Enum.find_index(r1, fn w -> w == v end)
    Enum.at(r2, i)
  end

  defp face({x, y}) do
    @faces
    |> Enum.find(fn {_, {xa..xb, ya..yb}} ->
      xa <= x &amp;&amp; x <= xb &amp;&amp; ya <= y &amp;&amp; y <= yb
    end)
    |> elem(0)
  end

  defp turn({1, 0}, :left), do: {0, -1}
  defp turn({0, -1}, :left), do: {-1, 0}
  defp turn({-1, 0}, :left), do: {0, 1}
  defp turn({0, 1}, :left), do: {1, 0}

  defp turn({1, 0}, :right), do: {0, 1}
  defp turn({0, -1}, :right), do: {1, 0}
  defp turn({-1, 0}, :right), do: {0, -1}
  defp turn({0, 1}, :right), do: {-1, 0}

  defp facing({1, 0}), do: 0
  defp facing({0, 1}), do: 1
  defp facing({-1, 0}), do: 2
  defp facing({0, -1}), do: 3

  defp parse(input) do
    [map, path] = String.split(input, "\n\n", trim: true)

    {
      map
      |> String.split("\n")
      |> Enum.with_index(1)
      |> Enum.reduce(%{}, fn {line, y}, acc ->
        line
        |> String.to_charlist()
        |> Enum.with_index(1)
        |> Enum.reduce(acc, fn {c, x}, acc ->
          case c do
            ?\s -> acc
            ?# -> Map.put(acc, {x, y}, :wall)
            ?. -> Map.put(acc, {x, y}, :open)
          end
        end)
      end),
      parse_path(path, [])
    }
  end

  defp parse_path("", acc) do
    Enum.reverse(acc)
  end

  defp parse_path("L" <> rest, acc) do
    parse_path(rest, [:left | acc])
  end

  defp parse_path("R" <> rest, acc) do
    parse_path(rest, [:right | acc])
  end

  defp parse_path(s, acc) do
    r = Regex.named_captures(~r/(?\d+)(?.*)/, s)
    parse_path(r["rest"], [String.to_integer(r["len"]) | acc])
  end
end

DayTwentyTwo.solve1(Kino.Input.read(input))

# 139166 too low