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

2022 - day 9

2022/elixir/day-9.livemd

2022 - day 9

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

Section

input = Kino.Input.textarea("")
input_2 = Kino.Input.textarea("")
input_1 = Kino.Input.textarea("")
defmodule Part1 do
  def simulate(motions) do
    state = %{head: {0, 0}, tail: {0, 0}, motions: motions, tail_trails: [{0, 0}]}
    do_simulate(state)
  end

  def count_trails(%{tail_trails: trails} = _state) do
    Enum.uniq(trails)
    |> Enum.count()
  end

  # state -> state
  defp do_simulate(%{motions: []} = state), do: state

  defp do_simulate(%{motions: [m | rest]} = state) do
    {{head, tail}, trails} = move(state.head, state.tail, m)

    state
    |> Map.merge(%{
      motions: rest,
      head: head,
      tail: tail,
      tail_trails: trails ++ state.tail_trails
    })
    |> do_simulate()
  end

  # move head, follow tail
  # dir UDRL, number
  defp move(head, tail, {d, n}) do
    1..n
    |> Enum.reduce({{head, tail}, []}, fn _, {{h, t}, trails} ->
      move_head(h, dir(d))
      |> then(&follow_tail(t, &1, need_follow?(&1, t)))
      |> maybe_append_trail(trails, t)
    end)
  end

  defp move_head(head, {dx, dy}) do
    {x, y} = head
    {x + dx, y + dy}
  end

  defp follow_tail(tail, head, false = _need_follow?), do: {head, tail}

  defp follow_tail(tail, head, true) do
    {tx, ty} = tail
    {dx, dy} = fdir(head, tail)
    {head, {tx + dx, ty + dy}}
  end

  defp maybe_append_trail({head, tail}, trails, tail), do: {{head, tail}, trails}

  defp maybe_append_trail({head, tail}, [tail | _] = trails, _old_tail) do
    {{head, tail}, trails}
  end

  defp maybe_append_trail({head, tail}, trails, _) do
    {{head, tail}, [tail | trails]}
  end

  defp dir("R"), do: {1, 0}
  defp dir("L"), do: {-1, 0}
  defp dir("U"), do: {0, 1}
  defp dir("D"), do: {0, -1}

  # which direction to follow head?
  defp fdir(head, tail)

  defp fdir({x, hy}, {x, ty}) do
    if hy > ty do
      {0, 1}
    else
      {0, -1}
    end
  end

  defp fdir({hx, y}, {tx, y}) do
    if hx > tx do
      {1, 0}
    else
      {-1, 0}
    end
  end

  defp fdir({hx, hy}, {tx, ty}) do
    cond do
      hx > tx and hy > ty ->
        {1, 1}

      hx > tx ->
        {1, -1}

      hy > ty ->
        {-1, 1}

      true ->
        {-1, -1}
    end
  end

  def need_follow?({hx, hy}, {tx, ty}) do
    abs(hx - tx) > 1 or abs(hy - ty) > 1
  end
end

input
|> Kino.Input.read()
|> String.split([" ", "\n"])
|> Enum.chunk_every(2)
|> Enum.map(fn [d, n] -> {d, String.to_integer(n)} end)
|> Part1.simulate()
|> Part1.count_trails()
|> dbg()
defmodule Part2 do
  def simulate(motions) do
    state = %{
      head: {0, 0},
      tails: List.duplicate({0, 0}, 9),
      motions: motions,
      tail_trails: [{0, 0}]
    }

    do_simulate(state)
  end

  # state -> integer
  def count_trails(%{tail_trails: trails} = _state) do
    Enum.uniq(trails)
    |> Enum.count()
  end

  # state -> state
  defp do_simulate(%{motions: []} = state), do: state

  defp do_simulate(%{motions: [m | rest]} = state) do
    {{head, tails}, trails} = move(state.head, state.tails, m)

    state
    |> Map.merge(%{
      motions: rest,
      head: head,
      tails: tails,
      tail_trails: trails ++ state.tail_trails
    })
    |> do_simulate()
  end

  # move head, follow tail
  # dir UDRL, number
  defp move(head, tails, {d, n}) do
    1..n
    |> Enum.reduce({{head, tails}, []}, fn _, {{h, ts}, trails} ->
      move_one(h, dir(d))
      |> then(&{&1, follow_tails(ts, &1)})
      |> maybe_append_trail(trails, List.last(ts))
    end)
  end

  defp move_one(head, {dx, dy}) do
    {x, y} = head
    {x + dx, y + dy}
  end

  defp follow_tails(tails, head) do
    tails
    |> Enum.map_reduce(head, fn
      t, h ->
        if need_follow?(h, t) do
          move_one(t, fdir(h, t))
          |> Tuple.duplicate(2)
        else
          {t, t}
        end
    end)
    |> elem(0)
  end

  defp maybe_append_trail({head, [_, _, _, _, _, _, _, _, tail] = tails}, trails, tail),
    do: {{head, tails}, trails}

  defp maybe_append_trail(
         {head, [_, _, _, _, _, _, _, _, tail] = tails},
         [tail | _] = trails,
         _old_tail
       ) do
    {{head, tails}, trails}
  end

  defp maybe_append_trail({head, [_, _, _, _, _, _, _, _, tail] = tails}, trails, _) do
    {{head, tails}, [tail | trails]}
  end

  defp dir("R"), do: {1, 0}
  defp dir("L"), do: {-1, 0}
  defp dir("U"), do: {0, 1}
  defp dir("D"), do: {0, -1}

  # which direction to follow head?
  defp fdir(head, tail)

  defp fdir({x, hy}, {x, ty}) do
    if hy > ty do
      {0, 1}
    else
      {0, -1}
    end
  end

  defp fdir({hx, y}, {tx, y}) do
    if hx > tx do
      {1, 0}
    else
      {-1, 0}
    end
  end

  defp fdir({hx, hy}, {tx, ty}) do
    cond do
      hx > tx and hy > ty ->
        {1, 1}

      hx > tx ->
        {1, -1}

      hy > ty ->
        {-1, 1}

      true ->
        {-1, -1}
    end
  end

  def need_follow?({hx, hy}, {tx, ty}) do
    abs(hx - tx) > 1 or abs(hy - ty) > 1
  end
end

input
|> Kino.Input.read()
|> String.split([" ", "\n"])
|> Enum.chunk_every(2)
|> Enum.map(fn [d, n] -> {d, String.to_integer(n)} end)
|> Part2.simulate()
|> Part2.count_trails()
|> dbg()