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

Day 9: Rope Bridge

2022/day_09.livemd

Day 9: Rope Bridge

Mix.install([:kino])

input = Kino.Input.textarea("Please paste your input:")

Part 1

Run in Livebook

https://adventofcode.com/2022/day/9

motions =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(fn <> <> " " <> step_str ->
    direction =
      case direction_str do
        "U" -> :up
        "D" -> :down
        "L" -> :left
        "R" -> :right
      end

    step = step_str |> String.to_integer()

    direction |> List.duplicate(step)
  end)
  |> List.flatten()

defmodule Solver.Part1 do
  def run(motions) do
    do_run(motions, {{0, 0}, {0, 0}}, [{{0, 0}, {0, 0}}])
    |> Enum.reverse()
    |> Enum.map(fn {_h_pos, t_pos} -> t_pos end)
    |> Enum.uniq()
    |> Enum.count()
  end

  defp do_run([], _pos, visited) do
    visited
  end

  defp do_run([motion | rest_motions], {h_pos, t_pos}, visited) do
    new_h_pos = calc_h_pos(h_pos, motion)
    new_t_pos = calc_t_pos(new_h_pos, t_pos)

    do_run(rest_motions, {new_h_pos, new_t_pos}, [{new_h_pos, new_t_pos} | visited])
  end

  defp calc_h_pos({x, y}, direction) do
    case direction do
      :up -> {x + 1, y}
      :down -> {x - 1, y}
      :left -> {x, y - 1}
      :right -> {x, y + 1}
    end
  end

  defp calc_t_pos({h_x, h_y}, {t_x, t_y}) do
    case {h_x - t_x, h_y - t_y} do
      {x, _} when x >= 2 -> {h_x - 1, h_y}
      {x, _} when x <= -2 -> {h_x + 1, h_y}
      {_, y} when y >= 2 -> {h_x, h_y - 1}
      {_, y} when y <= -2 -> {h_x, h_y + 1}
      _ -> {t_x, t_y}
    end
  end
end

Solver.Part1.run(motions)

Part 2

https://adventofcode.com/2022/day/9#part2

defmodule Solver.Part2 do
  @knot_count 10

  def run(motions) do
    init_poss = {0, 0} |> List.duplicate(@knot_count)

    do_run(motions, init_poss, [init_poss])
    |> Enum.reverse()
    |> Enum.map(fn poss -> poss |> List.last() end)
    |> Enum.uniq()
    |> Enum.count()
  end

  defp do_run([], _poss, visited) do
    visited
  end

  defp do_run([motion | rest_motions], poss, visited) do
    new_poss = calc_new_poss(poss, motion)

    do_run(rest_motions, new_poss, [new_poss | visited])
  end

  defp calc_new_poss([h_pos | rest_poss], motion) do
    new_h_pos = calc_new_pos(motion, h_pos)

    rest_poss
    |> Enum.reduce([new_h_pos], fn pos, [prev_pos | _] = new_poss ->
      new_pos = calc_new_pos(prev_pos, pos)

      [new_pos | new_poss]
    end)
    |> Enum.reverse()
  end

  defp calc_new_pos(direction, {x, y}) when is_atom(direction) do
    case direction do
      :up -> {x, y + 1}
      :down -> {x, y - 1}
      :left -> {x - 1, y}
      :right -> {x + 1, y}
    end
  end

  defp calc_new_pos({h_x, h_y}, {t_x, t_y}) do
    case {h_x - t_x, h_y - t_y} do
      {2, 2} -> {h_x - 1, h_y - 1}
      {-2, 2} -> {h_x + 1, h_y - 1}
      {2, -2} -> {h_x - 1, h_y + 1}
      {-2, -2} -> {h_x + 1, h_y + 1}
      {2, _} -> {h_x - 1, h_y}
      {-2, _} -> {h_x + 1, h_y}
      {_, 2} -> {h_x, h_y - 1}
      {_, -2} -> {h_x, h_y + 1}
      _ -> {t_x, t_y}
    end
  end
end

Solver.Part2.run(motions)