Powered by AppSignal & Oban Pro

Day 10

2022/day10.livemd

Day 10

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

Section

input = Kino.Input.textarea("file input")

Part 1

# noop takes 1 cycle
# addx takes 2 cycle (process the increment of X once the cycle is finished)
defmodule Day10 do
  def process_command(%{:x => x, :cycle => cycle}, "noop") do
    %{x: x, cycle: cycle + 1}
  end

  def process_command(%{:x => x, :cycle => cycle}, "addx " <> num_str) do
    %{x: x + String.to_integer(num_str), cycle: cycle + 2}
  end

  def do_calculations(_, [], _, _) do
    %{remaining_cycles_to_check: [], strength_value: 0}
  end

  def do_calculations(
        current_cycle,
        [cycle_to_check | remaining_cycles_to_check],
        _x_during_cycle,
        x_after_cycle
      )
      when current_cycle == cycle_to_check do
    %{
      remaining_cycles_to_check: remaining_cycles_to_check,
      strength_value: cycle_to_check * x_after_cycle
    }
  end

  def do_calculations(
        current_cycle,
        [cycle_to_check | remaining_cycles_to_check],
        _x_during_cycle,
        _x_after_cycle
      )
      when current_cycle < cycle_to_check do
    %{remaining_cycles_to_check: [cycle_to_check | remaining_cycles_to_check], strength_value: 0}
  end

  def do_calculations(
        _current_cycle,
        [cycle_to_check | remaining_cycles_to_check],
        x_during_cycle,
        _x_after_cycle
      ) do
    %{
      remaining_cycles_to_check: remaining_cycles_to_check,
      strength_value: cycle_to_check * x_during_cycle
    }
  end
end

cycles_to_check = [20, 60, 100, 140, 180, 220]

input
|> Kino.Input.read()
|> String.split("\n", trim: true)
|> Enum.reduce(
  %{x: 1, cycle: 1, remaining_cycles_to_check: cycles_to_check, signal_strength_sum: 0},
  fn op, acc ->
    %{:x => x, :cycle => cycle} = Day10.process_command(acc, op)

    %{:remaining_cycles_to_check => new_cycles_to_check, :strength_value => strength_value} =
      Day10.do_calculations(cycle, acc.remaining_cycles_to_check, acc.x, x)

    %{
      acc
      | x: x,
        cycle: cycle,
        signal_strength_sum: acc.signal_strength_sum + strength_value,
        remaining_cycles_to_check: new_cycles_to_check
    }
  end
)

Part 2

# CRT: 40 wide 6 high

defmodule Day10 do
  def process_command(%{:x => x, :cycle => cycle}, "noop") do
    %{x: x, cycle: cycle + 1}
  end

  def process_command(%{:x => x, :cycle => cycle}, "addx " <> num_str) do
    %{x: x + String.to_integer(num_str), cycle: cycle + 2}
  end
end

instructions =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.reduce(%{x: 1, cycle: 1, crt_screen: []}, fn op, acc ->
    sprite_position = [acc.x - 1, acc.x, acc.x + 1]

    %{:x => x, :cycle => cycle} = Day10.process_command(acc, op)

    additional_crt_screen =
      acc.cycle..(cycle - 1)
      |> Enum.reduce([], fn c, acc ->
        case Enum.member?(sprite_position, rem(c - 1, 40)) do
          true ->
            acc ++ ['#']

          false ->
            acc ++ ['.']
        end
      end)

    %{x: x, cycle: cycle, crt_screen: acc.crt_screen ++ additional_crt_screen}
  end)
  |> then(fn val ->
    val.crt_screen
  end)
  |> Enum.chunk_every(40)
  |> Enum.map(fn line ->
    line
    |> Enum.map_join(fn
      '#' -> IO.ANSI.format([:green_background, :green, "#"], true)
      '.' -> IO.ANSI.format([:black_background, :black, "."], true)
    end)
  end)
  |> Enum.each(&IO.puts/1)