Powered by AppSignal & Oban Pro

Aoc 2019 day 11

2019/elixir/day-11.livemd

Aoc 2019 day 11

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

Section

input = Kino.Input.textarea("input")
init_data =
  input
  |> Kino.Input.read()
  |> String.split(",", trim: true)
  |> Enum.map(&String.to_integer/1)
  |> Enum.with_index()
  |> Map.new(fn {v, k} -> {k, v} end)
defmodule Computer.Operator do
  defstruct [:params, :op_code]

  def new(instruction) do
    op_code = rem(instruction, 100)
    params = get_parameters(instruction)

    %__MODULE__{
      op_code: op_code(op_code),
      params: params
    }
  end

  defp get_parameters(instruction) do
    {
      instruction |> div(100) |> rem(10),
      instruction |> div(1_000) |> rem(10),
      instruction |> div(10_000) |> rem(10)
    }
  end

  defp op_code(1), do: :add
  defp op_code(2), do: :mul
  defp op_code(3), do: :read
  defp op_code(4), do: :print
  defp op_code(5), do: :jump_neq
  defp op_code(6), do: :jump_eq
  defp op_code(7), do: :comp_lt
  defp op_code(8), do: :comp_eq
  defp op_code(9), do: :adj_rb
  defp op_code(99), do: :halt
end

defmodule Computer.Memory do
  @addr_mode 0
  @inc_mode 1
  @relative_mode 2

  defstruct memory: %{}, inputs: [], outputs: [], pointer: 0, relative_base: 0

  def new(
        data: %__MODULE__{memory: map, inputs: prev_inputs, pointer: pointer, relative_base: rb},
        inputs: inputs
      ),
      do: %__MODULE__{
        memory: map,
        inputs: prev_inputs ++ inputs,
        pointer: pointer,
        relative_base: rb
      }

  def new(data: map, inputs: inputs) do
    %__MODULE__{memory: map, inputs: inputs}
  end

  def get_instruction(%__MODULE__{memory: map, pointer: pointer}), do: map[pointer]

  def get_value(%__MODULE__{memory: map, pointer: p}, offset, @inc_mode), do: map[p + offset] || 0

  def get_value(%__MODULE__{memory: map, pointer: p}, offset, @addr_mode),
    do: map[map[p + offset]] || 0

  def get_value(%__MODULE__{memory: map, pointer: p, relative_base: rb}, offset, @relative_mode),
    do: map[rb + map[p + offset]] || 0

  def put_value(%__MODULE__{memory: map, pointer: p} = s, offset, value, @addr_mode),
    do: %{s | memory: Map.put(map, map[p + offset], value)}

  def put_value(
        %__MODULE__{memory: map, pointer: p, relative_base: rb} = s,
        offset,
        value,
        @relative_mode
      ),
      do: %{s | memory: Map.put(map, rb + map[p + offset], value)}

  def prepend_outputs(memory, output), do: %{memory | outputs: [output | memory.outputs]}

  def move_pointer(memory, next_ptr), do: %{memory | pointer: next_ptr}

  def pop_input(%__MODULE__{inputs: [input | rest]} = memory),
    do: {input, %{memory | inputs: rest}}

  def put_inputs(memory, inputs), do: %{memory | inputs: memory.inputs ++ inputs}

  def adjust_relative_base(memory, offset),
    do: %{memory | relative_base: memory.relative_base + offset}
end

defmodule Computer do
  alias Computer.{Memory, Operator}

  @type mode :: :until_halt | :until_output

  @spec init_state(map(), list()) :: %Memory{}
  def init_state(data, inputs \\ []) do
    Memory.new(data: data, inputs: inputs)
  end

  @spec process(%Memory{} | map(), keyword()) ::
          {%Memory{}, :halt} | {%Memory{}, :hold} | {%Memory{}, list()}
  def process(memory, inputs, opts \\ []) do
    mode = Keyword.get(opts, :mode, :until_halt)

    compute_fn =
      case mode do
        :until_halt -> &compute_until_halt/1
        :until_output -> &compute_until_output/1
      end

    Memory.new(data: memory, inputs: inputs)
    |> compute_fn.()
  end

  @spec compute_until_halt(%Memory{}) :: %Memory{}
  def compute_until_halt(memory) do
    Memory.get_instruction(memory)
    |> Operator.new()
    |> case do
      %Operator{op_code: :halt} ->
        memory

      op ->
        case move_operation(op, memory) do
          {_memory, :hold} -> raise "unexpcted hold error"
          {memory, _outputs} -> memory
          memory -> memory
        end
        |> compute_until_halt()
    end
  end

  @spec compute_until_output(%Memory{}) ::
          {%Memory{}, :halt} | {%Memory{}, :hold} | {%Memory{}, list()}
  def compute_until_output(memory) do
    Memory.get_instruction(memory)
    |> Operator.new()
    |> case do
      %Operator{op_code: :halt} ->
        {memory, :halt}

      op ->
        move_operation(op, memory)
        |> case do
          {memory, :hold} ->
            {memory, :hold}

          {memory, outputs} ->
            {memory, outputs}

          memory ->
            compute_until_output(memory)
        end
    end
  rescue
    e ->
      IO.inspect(e)
      {memory, :halt}
  end

  defp move_operation(%Operator{op_code: op_code, params: {p1, p2, p3}}, memory)
       when op_code in [:add, :mul] do
    v1 = Memory.get_value(memory, 1, p1)
    v2 = Memory.get_value(memory, 2, p2)
    res = calc(op_code).(v1, v2)

    next_pointer = memory.pointer + 4

    Memory.put_value(memory, 3, res, p3)
    |> Memory.move_pointer(next_pointer)
  end

  defp move_operation(%Operator{op_code: :read}, %{inputs: []} = memory) do
    {memory, :hold}
  end

  defp move_operation(%Operator{op_code: :read, params: {p1, _, _}}, memory) do
    {input, memory} = Memory.pop_input(memory)

    next_pointer = memory.pointer + 2

    Memory.put_value(memory, 1, input, p1)
    |> Memory.move_pointer(next_pointer)
  end

  defp move_operation(%Operator{op_code: :print, params: {p1, _, _}}, memory) do
    output = Memory.get_value(memory, 1, p1)
    next_pointer = memory.pointer + 2

    Memory.prepend_outputs(memory, output)
    |> Memory.move_pointer(next_pointer)
    |> then(&{&1, &1.outputs})
  end

  defp move_operation(%Operator{op_code: :jump_neq, params: {p1, p2, _}}, memory) do
    predicate = Memory.get_value(memory, 1, p1)
    goto = Memory.get_value(memory, 2, p2)

    next_pointer = if predicate != 0, do: goto, else: memory.pointer + 3
    Memory.move_pointer(memory, next_pointer)
  end

  defp move_operation(%Operator{op_code: :jump_eq, params: {p1, p2, _}}, memory) do
    predicate = Memory.get_value(memory, 1, p1)
    goto = Memory.get_value(memory, 2, p2)

    next_pointer = if predicate == 0, do: goto, else: memory.pointer + 3
    Memory.move_pointer(memory, next_pointer)
  end

  defp move_operation(%Operator{op_code: :comp_lt, params: {p1, p2, p3}}, memory) do
    v1 = Memory.get_value(memory, 1, p1)
    v2 = Memory.get_value(memory, 2, p2)
    value = if v1 < v2, do: 1, else: 0
    next_pointer = memory.pointer + 4

    Memory.put_value(memory, 3, value, p3)
    |> Memory.move_pointer(next_pointer)
  end

  defp move_operation(%Operator{op_code: :comp_eq, params: {p1, p2, p3}}, memory) do
    v1 = Memory.get_value(memory, 1, p1)
    v2 = Memory.get_value(memory, 2, p2)
    value = if v1 == v2, do: 1, else: 0
    next_pointer = memory.pointer + 4

    Memory.put_value(memory, 3, value, p3)
    |> Memory.move_pointer(next_pointer)
  end

  defp move_operation(%Operator{op_code: :adj_rb, params: {p1, _, _}}, memory) do
    v = Memory.get_value(memory, 1, p1)

    Memory.adjust_relative_base(memory, v)
    |> Memory.move_pointer(memory.pointer + 2)
  end

  defp calc(:add), do: &Kernel.+/2
  defp calc(:mul), do: &Kernel.*/2
end

Part 1

0 = black
1 = white

0 = left 90
1 = right 90

defmodule Robot do
  def process(init_data, init_grid \\ %{}) do
    init_data
    |> Computer.init_state()
    |> do_process({0, 0}, :up, init_grid, :paint)
  end

  defp do_process(data, cur_pos, cur_dir, grid, :paint) do
    cur_color = grid |> Map.get(cur_pos, 0)

    case Computer.process(data, [cur_color], mode: :until_output) do
      {next_data, [color]} ->
        next_grid = paint(grid, cur_pos, color)
        do_process(next_data, cur_pos, cur_dir, next_grid, :rotate)

      {data, :halt} ->
        {grid, cur_pos, cur_dir, data}
    end
  end

  defp do_process(data, cur_pos, cur_dir, grid, :rotate) do
    case Computer.process(data, [], mode: :until_output) do
      {next_data, [direction]} ->
        # IO.puts("color: #{color} dir: #{direction}")
        next_dir = rotate(cur_dir, direction)
        next_pos = move(cur_pos, next_dir)
        do_process(next_data, next_pos, next_dir, grid, :paint)

      {data, :halt} ->
        {grid, cur_pos, cur_dir, data}
    end
  end

  defp paint(grid, pos, color) do
    Map.put(grid, pos, color)
  end

  defp rotate(:up, 0), do: :left
  defp rotate(:up, 1), do: :right
  defp rotate(:right, 0), do: :up
  defp rotate(:right, 1), do: :down
  defp rotate(:down, 0), do: :right
  defp rotate(:down, 1), do: :left
  defp rotate(:left, 0), do: :down
  defp rotate(:left, 1), do: :up

  defp move({x, y}, :up), do: {x, y - 1}
  defp move({x, y}, :right), do: {x + 1, y}
  defp move({x, y}, :left), do: {x - 1, y}
  defp move({x, y}, :down), do: {x, y + 1}
end
{grid, p, d, mem} = Robot.process(init_data)
grid |> Map.keys() |> Enum.count()

Part 2

{grid, p, d, mem} = Robot.process(init_data, %{{0, 0} => 1})
defmodule Drawer do
  @min -10
  @max 50
  @max_y 7

  def draw(grid) do
    @min..@max_y
    |> Enum.map(fn y ->
      @min..@max
      |> Enum.map(fn x ->
        if grid[{x, y}] == 1, do: "⬛️", else: "⬜️"
      end)
      |> Enum.join()
    end)
    |> Enum.join("\n")
  end
end

Drawer.draw(grid)
|> IO.puts()