Powered by AppSignal & Oban Pro

Advent of Code 2022

2022/day09.livemd

Advent of Code 2022

Mix.install([
  {:req, "~> 0.3.2"},
  {:kino, "~> 0.8.0"}
])

Day 9

input =
  "https://adventofcode.com/2022/day/9/input"
  |> Req.get!(headers: [cookie: "session=#{System.get_env("AOC_COOKIE")}"])
  |> Map.get(:body)
sample = """
R 4
U 4
L 3
D 1
R 4
D 1
L 5
R 2
"""

sample2 = """
R 5
U 8
L 8
D 3
R 17
D 10
L 25
U 20
"""
select = Kino.Input.select("data", input: "input", sample: "sample", sample2: "sample2")
motions =
  Kino.Input.read(select)
  |> case do
    :input -> input
    :sample -> sample
    :sample2 -> sample2
  end
  |> String.split("\n", trim: true)
  |> Enum.flat_map(fn line ->
    line
    |> String.split(" ")
    |> then(fn [d, n] ->
      List.duplicate(d, String.to_integer(n))
    end)
  end)

Part 1

state = %{head: {0, 0}, tail: {0, 0}, visited: MapSet.new([{0, 0}])}

directions = %{
  "R" => {1, 0},
  "U" => {0, 1},
  "L" => {-1, 0},
  "D" => {0, -1}
}

motions
|> Enum.reduce(state, fn d, %{head: {hx, hy}, tail: {tx, ty}} = acc ->
  {dx, dy} = directions[d]

  {hx1, hy1} = head1 = {hx + dx, hy + dy}

  ax = abs(hx1 - tx)
  ay = abs(hy1 - ty)

  tail1 =
    cond do
      ty == hy1 && ax > 1 ->
        {tx + dx, ty}

      tx == hx1 && ay > 1 ->
        {tx, ty + dy}

      ax + ay > 2 ->
        mx = if hx1 > tx, do: 1, else: -1
        my = if hy1 > ty, do: 1, else: -1
        {tx + mx, ty + my}

      true ->
        {tx, ty}
    end

  acc
  |> Map.put(:head, head1)
  |> Map.put(:tail, tail1)
  |> Map.put(:visited, MapSet.put(acc.visited, tail1))

  # |> tap(fn s ->
  #   head1 |> IO.inspect(label: "head")
  #   tail1 |> IO.inspect(label: "tail")
  #   Enum.map(5..0, fn y ->
  #     Enum.map(0..5, fn x ->
  #       cond do
  #         x == hx1 && y == hy1 ->
  #           "H"
  #         x == elem(tail1, 0) && y == elem(tail1, 1) ->
  #           "T"
  #         true ->
  #           "."
  #       end
  #       |> IO.write()
  #     end)
  #     IO.puts("")
  #   end)
  #   IO.puts("")
  # end)
end)
|> Map.get(:visited)
|> MapSet.size()

Part 2

n = 10
rope = 1..n |> Enum.map(fn i -> {i, {0, 0}} end) |> Map.new()
state = %{rope: rope, visited: MapSet.new([{0, 0}])}

directions = %{
  "R" => {1, 0},
  "U" => {0, 1},
  "L" => {-1, 0},
  "D" => {0, -1}
}

motions
|> Enum.reduce(state, fn d, %{rope: r} = acc ->
  {dx, dy} = directions[d]

  r = Map.update!(r, 1, fn {x, y} -> {x + dx, y + dy} end)

  r =
    2..n
    |> Enum.reduce_while(r, fn i, r ->
      {hx1, hy1} = r[i - 1]
      {tx, ty} = r[i]

      ax = abs(hx1 - tx)
      ay = abs(hy1 - ty)

      cond do
        ty == hy1 && ax > 1 ->
          mx = if hx1 > tx, do: 1, else: -1
          {tx + mx, ty}

        tx == hx1 && ay > 1 ->
          my = if hy1 > ty, do: 1, else: -1
          {tx, ty + my}

        ax + ay > 2 ->
          mx = if hx1 > tx, do: 1, else: -1
          my = if hy1 > ty, do: 1, else: -1
          {tx + mx, ty + my}

        true ->
          :no_change
      end
      |> case do
        :no_change ->
          {:halt, r}

        updated ->
          {:cont, Map.put(r, i, updated)}
      end
    end)

  acc
  |> Map.put(:rope, r)
  |> Map.put(:visited, MapSet.put(acc.visited, r[n]))
end)
|> Map.get(:visited)
|> MapSet.size()