Powered by AppSignal & Oban Pro

Day 9

2022/day_9.livemd

Day 9

Mix.install(
  [
    {:advent_of_code_utils, "~> 3.1"},
    {:kino, "~> 0.8.0"},
    {:aoc_utils, path: "."}
  ],
  config: [
    advent_of_code_utils: [session: System.fetch_env!("LB_AOC_TOKEN")]
  ]
)

Mix.Tasks.Aoc.Get.run(["--year", "2022", "--day", "9"])

Inputs

# Override example
example_input = "R 4\nU 4\nL 3\nD 1\nR 4\nD 1\nL 5\nR 2\n"
input = AOC.input_string(2022, 9)

Parser

defmodule Parser do
  def convert(input, :pass) do
    input
  end

  def convert(_input, :drop) do
    :to_be_dropped
  end

  def convert(input, :to_integer) do
    String.to_integer(input)
  end

  def convert(input, :to_atom) do
    String.to_atom(input)
  end

  def convert(inputs, converters) when is_list(inputs) and is_list(converters) do
    Enum.zip(inputs, converters)
    |> Enum.map(fn {input, converter} ->
      convert(input, converter)
    end)
    |> Enum.reject(&(&1 == :to_be_dropped))
    |> List.to_tuple()
  end

  def parse_line(line) do
    line
    |> String.split(" ", trim: true)
    |> then(&convert(&1, [:to_atom, :to_integer]))
  end

  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&parse_line/1)
  end
end
Parser.parse(example_input)

Simple SVG Draw

alias AOCUtils.SVGDraw
import AOCUtils.SVGDraw

SVGDraw.new(200, 200)
|> circle(10, 10, 5)
|> set_stroke("green")
|> set_fill("red")
|> circle(100, 100, 5)
|> square(20, 20, 10)
|> set_stroke("black")
|> set_stroke_width("2")
|> line(10, 10, 100, 100)
|> set_fill("white")
|> set_stroke_width(4)
|> square(10, 100, 25)
|> render()
|> then(&Kino.Image.new(&1, :svg))

Rope Grid

defmodule RopeGrid do
  def new() do
    %{
      head: {0, 0},
      tail: {0, 0}
    }
  end

  def move(grid, :R, 1) do
    grid
    |> move_head(&increment_x/1)
    |> move_tail()
  end

  def move(grid, :U, 1) do
    grid
    |> move_head(&increment_y/1)
    |> move_tail()
  end

  def move_head(grid, mover) do
    Map.update!(grid, :head, mover)
  end

  # Head moved horizontally
  # def move_tail(%{head: {hx, hy}, tail: {tx, ty}} = grid) when hx == tx do
  #   grid
  # end

  def move_tail(grid) do
    distance_between =
      grid
      |> distance()
      |> subtract_one_clamp_zero()

    %{grid | tail: add_locations(grid.tail, distance_between)}
  end

  def increment_x({x, y}), do: {x + 1, y}
  def decrement_x({x, y}), do: {x - 1, y}
  def increment_y({x, y}), do: {x, y + 1}
  def decrement_y({x, y}), do: {x, y - 1}

  def distance(%{head: {hx, hy}, tail: {tx, ty}}) do
    {hx - tx, hy - ty}
  end

  def add_locations({x1, y1}, {x2, y2}) do
    {x1 + x2, y1 + y2}
  end

  def subtract_one_clamp_zero({x, y}) do
    {
      max(0, x - 1),
      max(0, y - 1)
    }
  end

  def draw(%{head: {hx, hy}, tail: {tx, ty}}, box_size \\ 10) do
    alias AOCUtils.SVGDraw

    text_location = """
    Head Location: #{hx}, #{hy}<br />
    Tail Location: #{tx}, #{ty}

    """

    width = hx + 3
    height = hy + 3

    border_width = 1
    svg_width = width * box_size + border_width * 2
    svg_height = height * box_size + border_width * 2

    svg =
      SVGDraw.new(
        svg_width,
        svg_height,
        global_style: %{
          transform: "scaleY(-1)",
          transform_origin: "center"
        }
      )

    # Draw one big box around the outside
    svg =
      svg
      |> SVGDraw.set_stroke_width(border_width)
      |> SVGDraw.set_stroke("black")
      |> SVGDraw.set_fill("white")
      |> SVGDraw.rectangle(0, 0, svg_width, svg_height)

    # Draw boxes
    svg =
      for x <- Range.new(0, width), y <- Range.new(0, height), reduce: svg do
        svg ->
          svg
          |> SVGDraw.set_stroke_width(border_width)
          |> SVGDraw.set_stroke("black")
          |> then(fn svg ->
            cond do
              {x, y} == {0, 0} ->
                SVGDraw.set_fill(svg, "blue")

              {x, y} == {hx, hy} ->
                SVGDraw.set_fill(svg, "green")

              {x, y} == {tx, ty} ->
                SVGDraw.set_fill(svg, "red")

              true ->
                SVGDraw.set_fill(svg, "white")
            end
          end)
          |> SVGDraw.square(
            x * box_size + border_width,
            y * box_size + border_width,
            box_size
          )
      end

    Kino.Layout.grid([
      Kino.Markdown.new(text_location),
      svg
      |> SVGDraw.render()
      |> then(&Kino.Image.new(&1, :svg))
    ])
  end
end
RopeGrid.new()
|> RopeGrid.move(:R, 1)
|> RopeGrid.move(:R, 1)
|> RopeGrid.move(:R, 1)
|> RopeGrid.move(:R, 1)
|> RopeGrid.move(:U, 1)
# |> RopeGrid.move(:U, 1)

|> RopeGrid.draw(20)

Part 1

example_input
input

Part 2

example_input
input