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

Day 10

2022/elixir/day10.livemd

Day 10

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

Puzzle Input

area = Kino.Input.textarea("Puzzle Input")
puzzle_input = Kino.Input.read(area)
example_input = """
addx 15
addx -11
addx 6
addx -3
addx 5
addx -1
addx -8
addx 13
addx 4
noop
addx -1
addx 5
addx -1
addx 5
addx -1
addx 5
addx -1
addx 5
addx -1
addx -35
addx 1
addx 24
addx -19
addx 1
addx 16
addx -11
noop
noop
addx 21
addx -15
noop
noop
addx -3
addx 9
addx 1
addx -3
addx 8
addx 1
addx 5
noop
noop
noop
noop
noop
addx -36
noop
addx 1
addx 7
noop
noop
noop
addx 2
addx 6
noop
noop
noop
noop
noop
addx 1
noop
noop
addx 7
addx 1
noop
addx -13
addx 13
addx 7
noop
addx 1
addx -33
noop
noop
noop
addx 2
noop
noop
noop
addx 8
noop
addx -1
addx 2
addx 1
noop
addx 17
addx -9
addx 1
addx 1
addx -3
addx 11
noop
noop
addx 1
noop
addx 1
noop
noop
addx -13
addx -19
addx 1
addx 3
addx 26
addx -30
addx 12
addx -1
addx 3
addx 1
noop
noop
noop
addx -9
addx 18
addx 1
addx 2
noop
noop
addx 9
noop
noop
noop
addx -1
addx 2
addx -37
addx 1
addx 3
noop
addx 15
addx -21
addx 22
addx -6
addx 1
noop
addx 2
addx 1
noop
addx -10
noop
noop
addx 20
addx 1
addx 2
addx 2
addx -6
addx -11
noop
noop
noop
"""

Common

defmodule CPU do
  defstruct x: 1, cycle: 0

  def new(), do: %CPU{}

  def execute(%CPU{} = cpu, instruction) do
    case instruction do
      :noop ->
        [noop(cpu)]

      {:addx, number} ->
        %{end: end_of_first_cycle_cpu} = first_cycle = noop(cpu)
        second_cycle = add(end_of_first_cycle_cpu, number)
        [first_cycle, second_cycle]
    end
  end

  def noop(%CPU{} = cpu) do
    cpu = bump(cpu)
    %{start: cpu, during: cpu, end: cpu}
  end

  def add(%CPU{} = cpu, number) do
    cpu = bump(cpu)
    %{start: cpu, during: cpu, end: %{cpu | x: cpu.x + number}}
  end

  def bump(%CPU{} = cpu) do
    %{cpu | cycle: cpu.cycle + 1}
  end
end
ExUnit.start(autorun: false)

defmodule CPUTests do
  use ExUnit.Case, async: true

  test "executes noop" do
    cpu = CPU.new()

    assert CPU.execute(cpu, :noop) == [
             %{
               start: %CPU{x: 1, cycle: 1},
               during: %CPU{x: 1, cycle: 1},
               end: %CPU{x: 1, cycle: 1}
             }
           ]
  end

  test "executes addx" do
    cpu = %CPU{x: 1, cycle: 1}

    assert CPU.execute(cpu, {:addx, 3}) == [
             %{
               during: %CPU{x: 1, cycle: 2},
               end: %CPU{x: 1, cycle: 2},
               start: %CPU{x: 1, cycle: 2}
             },
             %{
               during: %CPU{x: 1, cycle: 3},
               end: %CPU{x: 4, cycle: 3},
               start: %CPU{x: 1, cycle: 3}
             }
           ]
  end
end

ExUnit.run()
instructions =
  puzzle_input
  |> String.split("\n", trim: true)
  |> Enum.map(fn
    "noop" -> :noop
    "addx " <> number -> {:addx, number |> Integer.parse() |> elem(0)}
  end)
cpu = CPU.new()
initial = %{start: cpu, during: cpu, end: cpu}

timeline =
  instructions
  |> Stream.scan([initial], fn instruction, timeline ->
    timeline
    |> List.last()
    |> Map.fetch!(:end)
    |> CPU.execute(instruction)
  end)
  |> Enum.flat_map(&amp; &amp;1)

timeline = [initial | timeline]

Part One

signal_prefix = Enum.at(timeline, 20)
signal_rest = timeline |> Enum.drop(60) |> Enum.take_every(40)

signal = [signal_prefix | signal_rest]
signal
|> Stream.map(fn %{during: %CPU{} = cpu} -> cpu.x * cpu.cycle end)
|> Enum.sum()

Part Two

defmodule CRT do
  @line_width 40
  @sprite_offset 1

  defstruct column: 0, pixels: []

  def new(), do: %CRT{}

  def tick(%CRT{} = crt, %CPU{} = cpu) do
    pixel = if sprite_visible?(crt.column, cpu.x), do: "#", else: "."

    crt
    |> draw_pixel(pixel)
    |> next_column()
  end

  def sprite_visible?(column, x) do
    x - @sprite_offset <= column and column <= x + @sprite_offset
  end

  def draw_pixel(crt, pixel) do
    %{crt | pixels: [pixel | crt.pixels]}
  end

  def next_column(crt) do
    next_column = crt.column + 1

    column =
      if next_column < @line_width do
        next_column
      else
        0
      end

    %{crt | column: column}
  end

  def print(crt) do
    crt.pixels
    |> Enum.reverse()
    |> Stream.chunk_every(@line_width)
    |> Stream.map(&amp;Enum.join/1)
    |> Enum.join("\n")
  end
end
crt = CRT.new()

timeline
|> tl()
|> Enum.reduce(crt, fn %{during: cpu}, crt ->
  CRT.tick(crt, cpu)
end)
|> CRT.print()
|> IO.puts()