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

Advent 2022 - Day 9

day9.livemd

Advent 2022 - Day 9

Mix.install([
  {:kino, github: "livebook-dev/kino"}
])
:ok

Setup

defmodule Parse do
  def kino_textarea(input) do
    input
    |> Kino.Input.read()
    |> String.split("\n")
    |> Enum.map(&String.split(&1, " "))
    |> Enum.map(fn [d, n] -> {String.to_atom(d), String.to_integer(n)} end)
  end
end
{:module, Parse, <<70, 79, 82, 49, 0, 0, 8, ...>>, {:kino_textarea, 1}}
part1_input = Kino.Input.textarea("Please paste your input file:")
part1_steps = Parse.kino_textarea(part1_input)
[R: 4, U: 4, L: 3, D: 1, R: 4, D: 1, L: 5, R: 2]
part2_input = Kino.Input.textarea("Please paste your input file:")
part2_steps = Parse.kino_textarea(part2_input)
[R: 5, U: 8, L: 8, D: 3, R: 17, D: 10, L: 25, U: 20]

Drawing

defmodule Draw do
  def sim(visited, head \\ nil, tail \\ nil)

  def sim(visited, head, tail) do
    keys = Map.keys(visited)
    min_x = keys |> Enum.map(&amp;elem(&amp;1, 0)) |> Enum.min()
    max_x = keys |> Enum.map(&amp;elem(&amp;1, 0)) |> Enum.max()
    min_y = keys |> Enum.map(&amp;elem(&amp;1, 1)) |> Enum.min()
    max_y = keys |> Enum.map(&amp;elem(&amp;1, 1)) |> Enum.max()
    sim(visited, min_x, max_x, min_y, max_y, head, tail)
  end

  def sim(visited, min_x, max_x, min_y, max_y, head, tail) do
    for y <- max_y..min_y do
      row =
        for x <- min_x..max_x do
          loc = {x, y}

          cond do
            loc == head -> "H"
            loc == tail -> "T"
            Map.has_key?(visited, loc) -> "#"
            true -> "."
          end
        end

      "#{row}\n"
    end
    |> Enum.join()
  end
end
{:module, Draw, <<70, 79, 82, 49, 0, 0, 14, ...>>, {:sim, 7}}

Utils

defmodule RopeSim do
  def follow(a, b) when is_number(a) and is_number(b) do
    cond do
      a > b + 1 -> a - 1
      a < b - 1 -> a + 1
      true -> a
    end
  end

  def follow({tx, ty}, {hx, hy}) do
    if abs(tx - hx) == 1 and abs(ty - hy) == 1 do
      {tx, ty}
    else
      cond do
        tx < hx and ty < hy -> {tx + 1, ty + 1}
        tx > hx and ty > hy -> {tx - 1, ty - 1}
        tx > hx and ty < hy -> {tx - 1, ty + 1}
        tx < hx and ty > hy -> {tx + 1, ty - 1}
        true -> {follow(tx, hx), follow(ty, hy)}
      end
    end
  end

  def knots_follow([knot | knots], head) do
    knot = follow(knot, head)

    if knot == head do
      [knot | knots]
    else
      [knot | knots_follow(knots, knot)]
    end
  end

  def knots_follow([], _head), do: []

  def apply_moves([{mx, my} | moves], {hx, hy}, knots, visited) do
    head = {hx + mx, hy + my}

    knots = knots_follow(knots, head)
    tail = List.last(knots)
    visited = Map.put(visited, tail, true)

    apply_moves(moves, head, knots, visited)
  end

  def apply_moves([], head, tail, visited), do: {head, tail, visited}

  def apply_step(step, head, knots, visited) do
    moves =
      case step do
        {:U, amount} -> List.duplicate({0, 1}, amount)
        {:R, amount} -> List.duplicate({1, 0}, amount)
        {:D, amount} -> List.duplicate({0, -1}, amount)
        {:L, amount} -> List.duplicate({-1, 0}, amount)
      end

    apply_moves(moves, head, knots, visited)
  end

  def run([step | steps], head, knots, visited) do
    {head, knots, visited} = apply_step(step, head, knots, visited)
    run(steps, head, knots, visited)
  end

  def run([], _head, _knots, visited), do: visited

  def run(steps, num_knots) do
    head = {0, 0}
    knots = List.duplicate({0, 0}, num_knots)
    visited = Map.put(%{}, {0, 0}, true)
    run(steps, head, knots, visited)
  end
end
{:module, RopeSim, <<70, 79, 82, 49, 0, 0, 18, ...>>, {:run, 2}}

Part 1

visited = RopeSim.run(part1_steps, 1)

Draw.sim(visited) |> IO.write()
..##.
...##
.####
....#
####.
:ok
Map.keys(visited) |> Enum.count()
13

Part 2

visited = RopeSim.run(part2_steps, 9)

Draw.sim(visited) |> IO.write()
#.....................
#.............###.....
#............#...#....
.#..........#.....#...
..#..........#.....#..
...#........#.......#.
....#......#.........#
.....#..............#.
......#............#..
.......#..........#...
........#........#....
.........########.....
:ok
Map.keys(visited) |> Enum.count()
36