Powered by AppSignal & Oban Pro

Day 09

2022/day-09.livemd

Day 09

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

example_input =
  Kino.Input.textarea("example input:")
  |> Kino.render()

real_input = Kino.Input.textarea("real input:")

Common

defmodule Rope do
  defp move([], _, _, visits), do: visits

  defp move([{dir, count} | rest], coord, tails, visits) when count > 0 do
    coord = move_head(dir, coord)

    {tails, _} =
      Enum.map_reduce(tails, coord, fn tail, coord ->
        tail = move_tail(coord, tail)
        {tail, tail}
      end)

    move([{dir, count - 1} | rest], coord, tails, MapSet.put(visits, List.last(tails)))
  end

  defp move([_ | rest], coord, tails, visits), do: move(rest, coord, tails, visits)

  defp move_head(?R, {x, y}), do: {x + 1, y}
  defp move_head(?U, {x, y}), do: {x, y + 1}
  defp move_head(?L, {x, y}), do: {x - 1, y}
  defp move_head(?D, {x, y}), do: {x, y - 1}

  defp move_tail({xh, y}, {xt, y}) when xh > xt and xh - xt > 1, do: {xt + 1, y}
  defp move_tail({x, yh}, {x, yt}) when yh > yt and yh - yt > 1, do: {x, yt + 1}
  defp move_tail({xh, y}, {xt, y}) when xt > xh and xt - xh > 1, do: {xt - 1, y}
  defp move_tail({x, yh}, {x, yt}) when yt > yh and yt - yh > 1, do: {x, yt - 1}

  defp move_tail({xh, yh}, {xt, yt}) when xh > xt and xh - xt > 1 and yh > yt,
    do: {xt + 1, yt + 1}

  defp move_tail({xh, yh}, {xt, yt}) when xh > xt and xh - xt > 1 and yh < yt,
    do: {xt + 1, yt - 1}

  defp move_tail({xh, yh}, {xt, yt}) when yh > yt and yh - yt > 1 and xh > xt,
    do: {xt + 1, yt + 1}

  defp move_tail({xh, yh}, {xt, yt}) when yh > yt and yh - yt > 1 and xh < xt,
    do: {xt - 1, yt + 1}

  defp move_tail({xh, yh}, {xt, yt}) when xt > xh and xt - xh > 1 and yh > yt,
    do: {xt - 1, yt + 1}

  defp move_tail({xh, yh}, {xt, yt}) when xt > xh and xt - xh > 1 and yh < yt,
    do: {xt - 1, yt - 1}

  defp move_tail({xh, yh}, {xt, yt}) when yt > yh and yt - yh > 1 and xh > xt,
    do: {xt + 1, yt - 1}

  defp move_tail({xh, yh}, {xt, yt}) when yt > yh and yt - yh > 1 and xh < xt,
    do: {xt - 1, yt - 1}

  defp move_tail(_, tail), do: tail

  def simulate(movements, tails), do: move(movements, {0, 0}, tails, MapSet.new([{0, 0}]))
end

parse = fn input ->
  input
  |> Kino.Input.read()
  |> String.split("\n")
  |> Enum.map(&String.to_charlist/1)
  |> Enum.map(fn [dir, _ | count] -> {dir, List.to_integer(count)} end)
end

Part 1

real_input
|> then(parse)
|> Rope.simulate([{0, 0}])
|> MapSet.size()

Part 2

real_input
|> then(parse)
|> Rope.simulate([{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}])
|> MapSet.size()