# AOC 2022 - Day 9 ```elixir Mix.install([ {:req, "~> 0.3.3"}, {:vega_lite, "~> 0.1.6"}, {:kino_vega_lite, "~> 0.1.7"} ]) alias VegaLite, as: Vl ``` ## Puzzle description [Day 9: Rope Bridge](https://adventofcode.com/2022/day/9). ## Input ```elixir defmodule Load do def input do aoc_session = System.fetch_env!("LB_AOC_SESSION") input_url = "https://adventofcode.com/2022/day/9/input" Req.get!(input_url, headers: [cookie: "session=#{aoc_session}"]).body end end ``` ## Solution ```elixir defmodule Util do def process(input) do input |> String.split() |> Enum.chunk_every(2) |> Enum.map(&parse_motion/1) end defp parse_motion(["U", amount]), do: {{0, 1}, String.to_integer(amount)} defp parse_motion(["D", amount]), do: {{0, -1}, String.to_integer(amount)} defp parse_motion(["L", amount]), do: {{-1, 0}, String.to_integer(amount)} defp parse_motion(["R", amount]), do: {{1, 0}, String.to_integer(amount)} def plot_locations(positions) do data = positions |> Enum.map(fn {x, y} -> %{x: x, y: y} end) chart = Vl.new(width: 500, height: 400) |> Vl.mark(:square) |> Vl.encode_field(:x, "x", type: :ordinal, title: "X-coordinate", axis: [label_angle: 0]) |> Vl.encode_field(:y, "y", type: :ordinal, title: "Y-coordinate", sort: :descending) |> Vl.config(view: [stroke: nil]) |> Kino.VegaLite.new() |> Kino.render() for position <- data do Kino.VegaLite.push(chart, position) Process.sleep(1) end positions end end ``` ```elixir defmodule RopeSimulator do def simulate(instructions, knots \\ 2) do {_final_pos, trail} = for i <- instructions, reduce: {{0, 0}, []} do {pos, trail} -> new_trail = move(pos, i, []) new_pos = hd(new_trail) {new_pos, new_trail ++ trail} end trail = Enum.reverse(trail) for _ <- 2..knots, reduce: trail do trail -> Enum.scan(trail, {0, 0}, fn head, tail -> move_tail(head, tail) end) end |> MapSet.new() end def move({x, y}, {_dir, 0}, trail), do: [{x, y} | trail] def move(pos, {dir, n}, trail) do new_pos = translate(pos, dir) move(new_pos, {dir, n - 1}, [new_pos | trail]) end # overlap, do nothing def move_tail({x, y}, {x, y}), do: {x, y} # on same row def move_tail({hx, y}, {tx, y}) when abs(hx - tx) == 1, do: {tx, y} def move_tail({hx, y}, {tx, y}) when hx > tx, do: {tx + 1, y} def move_tail({hx, y}, {tx, y}) when hx < tx, do: {tx - 1, y} # on same column def move_tail({x, hy}, {x, ty}) when abs(hy - ty) == 1, do: {x, ty} def move_tail({x, hy}, {x, ty}) when hy > ty, do: {x, ty + 1} def move_tail({x, hy}, {x, ty}) when hy < ty, do: {x, ty - 1} # somewhere diagonally => do nothing def move_tail({hx, hy}, {tx, ty}) when abs(hx - tx) + abs(hy - ty) == 2, do: {tx, ty} # move tail closer diagonally def move_tail({hx, hy}, {tx, ty}) do {dx, dy} = {hx - tx, hy - ty} translate({tx, ty}, {round(dx / abs(dx)), round(dy / abs(dy))}) end defp translate({x, y}, {dx, dy}), do: {x + dx, y + dy} end ``` ```elixir defmodule Part1 do def run(input) do input |> Util.process() |> RopeSimulator.simulate() |> Enum.count() end end defmodule Part2 do def run(input) do input |> Util.process() |> RopeSimulator.simulate(10) |> Enum.count() end end ExUnit.start(autorun: false) defmodule Test do use ExUnit.Case, async: true @example_input ~s(R 5 U 8 L 8 D 3 R 17 D 10 L 25 U 20) @input Load.input() test "it loads the input" do assert String.length(@input) > 0 end test "part 1 example" do assert Part1.run(@example_input) === 88 end test "part 1" do assert Part1.run(@input) === 5683 end test "part 2 example" do assert Part2.run(@example_input) === 36 end test "part 2" do assert Part2.run(@input) === 2372 end end ExUnit.run() ```