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

AoC 2022 Day 9

2022/day09.livemd

AoC 2022 Day 9

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

Input

input_field = Kino.Input.textarea("Puzzle input")
input = Kino.Input.read(input_field)

Part 1

defmodule Rope do
  @moves [
    up: {-1, 0},
    down: {1, 0},
    left: {0, -1},
    right: {0, 1}
  ]

  def moves(), do: @moves

  def tail_to_head_distance({tx, ty} = _tail, {hx, hy} = _head), do: {tx - hx, ty - hy}

  def get_tail_move(tail, head) do
    distance = tail_to_head_distance(tail, head)

    case distance do
      {2, y} ->
        {1, y}

      {-2, y} ->
        {-1, y}

      {x, 2} ->
        {x, 1}

      {x, -2} ->
        {x, -1}

      move ->
        {0, 0}
    end
  end

  def apply_move({x, y} = _pos, {dx, dy} = _move), do: {x + dx, y + dy}

  def step_rope(_, 0, head, tail, tail_visits), do: {head, tail, tail_visits}

  def step_rope(direction, amount, head, tail, tail_visits) do
    new_head = apply_move(head, @moves[direction])
    tail_move = get_tail_move(new_head, tail)
    new_tail = apply_move(tail, tail_move)

    step_rope(direction, amount - 1, new_head, new_tail, [new_tail | tail_visits])
  end
end
moves =
  input
  |> String.split("\n")
  |> Enum.map(fn <> <> " " <> int ->
    dir =
      case dir do
        ?U -> :up
        ?D -> :down
        ?L -> :left
        ?R -> :right
      end

    {dir, String.to_integer(int)}
  end)

%{visited: visited} =
  Enum.reduce(
    moves,
    %{head: {0, 0}, tail: {0, 0}, visited: []},
    fn {move, amount}, %{head: head, tail: tail, visited: visited} ->
      {head, tail, visited} = Rope.step_rope(move, amount, head, tail, visited)
      %{head: head, tail: tail, visited: visited}
    end
  )

visited |> Enum.uniq() |> Enum.count()

Part 2

defmodule Rope2 do
  def update_tail(head, [last], visited) do
    move = Rope.get_tail_move(head, last)
    tail = Rope.apply_move(last, move)

    IO.puts("Tail end moved from #{inspect(last)} to #{inspect(tail)}")

    {[tail], [tail | visited]}
  end

  def update_tail(head, [current | tail], visited) do
    move = Rope.get_tail_move(head, current)
    new_current = Rope.apply_move(current, move)

    {tail, visited} = update_tail(new_current, tail, visited)

    {[new_current | tail], visited}
  end

  # TODO
  def step_rope(_, 0, head, tail, tail_visits), do: {head, tail, tail_visits}

  def step_rope(direction, amount, head, tail, tail_visits) do
    new_head = Rope.apply_move(head, Rope.moves()[direction])
    {tail, tail_visits} = update_tail(new_head, tail, tail_visits)

    step_rope(direction, amount - 1, new_head, tail, tail_visits)
  end
end
tail = List.duplicate({0, 0}, 9)

%{visited: visited} =
  Enum.reduce(
    moves,
    %{head: {0, 0}, tail: tail, visited: []},
    fn {move, amount}, %{head: head, tail: tail, visited: visited} ->
      {head, tail, visited} = Rope2.step_rope(move, amount, head, tail, visited)
      %{head: head, tail: tail, visited: visited}
    end
  )

visited |> Enum.uniq() |> Enum.count()