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

Day 17: Chronospatial Computer

day17.livemd

Day 17: Chronospatial Computer

Mix.install([:kino])

Section

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

Part 1

defmodule ChronospatialComputer do
  import Bitwise

  def solve(input) do
    parse_input(input)
    |> execute(0, [])
    |> Enum.reverse()
    |> Enum.join(",")
  end

  defp execute({_reg_a, _reg_b, _reg_c, code}, code_pointer, output)
       when code_pointer >= length(code) do
    output
  end

  defp execute({reg_a, reg_b, reg_c, code}, code_pointer, output) do
    [opcode, operand] = Enum.slice(code, code_pointer..(code_pointer + 1))

    case run_instruction(opcode, operand, {reg_a, reg_b, reg_c, code}, output, code_pointer) do
      {:jump, {program_memory, output, new_pointer}} ->
        execute(program_memory, new_pointer, output)

      {program_memory, output, pointer} ->
        execute(program_memory, pointer + 2, output)
    end
  end

  defp run_instruction(0, operand, {reg_a, reg_b, reg_c, code}, output, pointer) do
    reg_a = div(reg_a, Integer.pow(2, combo_value(operand, {reg_a, reg_b, reg_c})))
    {{reg_a, reg_b, reg_c, code}, output, pointer}
  end

  defp run_instruction(1, operand, {reg_a, reg_b, reg_c, code}, output, pointer) do
    reg_b = bxor(reg_b, operand)
    {{reg_a, reg_b, reg_c, code}, output, pointer}
  end

  defp run_instruction(2, operand, {reg_a, reg_b, reg_c, code}, output, pointer) do
    {{reg_a, rem(combo_value(operand, {reg_a, reg_b, reg_c}), 8), reg_c, code}, output, pointer}
  end

  defp run_instruction(3, operand, {reg_a, reg_b, reg_c, code}, output, pointer) do
    if reg_a == 0 do
      {{reg_a, reg_b, reg_c, code}, output, pointer}
    else
      {:jump, {{reg_a, reg_b, reg_c, code}, output, operand}}
    end
  end

  defp run_instruction(4, _operand, {reg_a, reg_b, reg_c, code}, output, pointer) do
    reg_b = bxor(reg_b, reg_c)
    {{reg_a, reg_b, reg_c, code}, output, pointer}
  end

  defp run_instruction(5, operand, {reg_a, reg_b, reg_c, code}, output, pointer) do
    {{reg_a, reg_b, reg_c, code}, [rem(combo_value(operand, {reg_a, reg_b, reg_c}), 8) | output],
     pointer}
  end

  defp run_instruction(6, operand, {reg_a, reg_b, reg_c, code}, output, pointer) do
    reg_b = div(reg_a, Integer.pow(2, combo_value(operand, {reg_a, reg_b, reg_c})))
    {{reg_a, reg_b, reg_c, code}, output, pointer}
  end

  defp run_instruction(7, operand, {reg_a, reg_b, reg_c, code}, output, pointer) do
    reg_c = div(reg_a, Integer.pow(2, combo_value(operand, {reg_a, reg_b, reg_c})))
    {{reg_a, reg_b, reg_c, code}, output, pointer}
  end

  defp combo_value(value, {reg_a, reg_b, reg_c}) when value in [0, 1, 2, 3, 4, 5, 6] do
    case value do
      0 -> 0
      1 -> 1
      2 -> 2
      3 -> 3
      4 -> reg_a
      5 -> reg_b
      6 -> reg_c
    end
  end

  def find_correct_a(input) do
    {_reg_a, _reg_b, _reg_c, program} = parse_input(input)
    solve_backward(program) |> Enum.min()
  end

  defp solve_backward(program) do
    Enum.reduce(1..length(program), [0], fn outputs, possible_as ->
      target_output =
        program
        |> Enum.reverse()
        |> Enum.slice(0, outputs)

      result =
        possible_as
        |> Enum.map(fn possible_a ->
          Enum.reduce(0..7, [], fn a, new_possibles ->
            new_a = possible_a * 8 + a

            output =
              execute({new_a, 0, 0, program}, 0, [])
              |> Enum.take(outputs)

            if output == target_output do
              [new_a | new_possibles]
            else
              new_possibles
            end
          end)
        end)
        |> List.flatten()

      result
    end)
  end

  defp parse_input(input) do
    [reg_a, reg_b, reg_c, code] =
      Regex.run(
        ~r/Register A: (\d+)\nRegister B: (\d+)\nRegister C: (\d+)\n\nProgram: ([\d,]+)/,
        input,
        capture: :all_but_first
      )

    {String.to_integer(reg_a), String.to_integer(reg_b), String.to_integer(reg_c),
     String.split(code, ",", trim: true) |> Enum.map(&String.to_integer(&1))}
  end
end

ChronospatialComputer.solve(input) |> IO.inspect()
ChronospatialComputer.find_correct_a(input) |> IO.inspect()