defmodule Day9 do def read_program do File.read!("lib/day9/input.txt") |> String.trim() |> String.split(",") |> Enum.map(&String.to_integer/1) end def run_program do read_program() |> expand_memory() |> run_cli() end def test do # prog = [109, 1, 204, -1, 1001, 100, 1, 100, 1008, 100, 16, 101, 1006, 101, 0, 99] # prog = [1102, 34_915_192, 34_915_192, 7, 4, 7, 99, 0] prog = [104, 1_125_899_906_842_624, 99] expanded = expand_memory(prog, 150) run_cli(expanded) end def run_cli(memory, opts \\ []) do parent = self() proc = spawn(fn -> run(memory, parent) end) handle_cli_messages(proc, opts) end def handle_cli_messages(proc, opts) do receive do {:halt, _, memory} -> if Keyword.get(opts, :memory, false) do memory else :ok end {:ok, _, memory} -> if Keyword.get(opts, :memory, false) do memory else :ok end {:in, _} -> res = IO.gets("intcode> ") |> String.trim() |> String.to_integer() send(proc, {:in, res}) handle_cli_messages(proc, opts) {:out, _, out} -> IO.puts("Output: #{out}") handle_cli_messages(proc, opts) end end def expand_memory(memory, length \\ nil) do memory ++ repeat(0, length || length(memory) * 10) end def repeat(_, 0), do: [] def repeat(val, count), do: [val | repeat(val, count - 1)] def run(state, parent, ip \\ 0) def run(memory, parent, ip) when is_list(memory) do run({memory, 0}, parent, ip) end def run({memory, _relative_base} = state, parent, ip) when ip < length(memory) do # IO.puts("IP: #{ip}") # IO.inspect(memory) case eval(Enum.drop(memory, ip), state, parent) do {memory, :halt} -> send(parent, {:halt, self(), memory}) {state, :cont, offset} -> run(state, parent, ip + offset) {state, :jump, ip} -> run(state, parent, ip) end end def run(state, parent, _) do send(parent, {:ok, self(), state}) end defmacro opcode(op, expected) do quote do rem(unquote(op), 100) == unquote(expected) end end def get_param(op, param, val, {memory, relative_base}) do place = param + 2 case op |> div(pow(10, place)) |> rem(10) do 0 -> Enum.at(memory, val) 1 -> val 2 -> Enum.at(memory, relative_base + val) end end def get_write_param(op, param, val, {memory, relative_base}) do place = param + 2 case op |> div(pow(10, place)) |> rem(10) do 2 -> relative_base + val _ -> val end end def pow(base, 1), do: base def pow(base, exp) do base * pow(base, exp - 1) end # halt def eval([99 | _], memory, _) do {memory, :halt} end # add def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 1) do a_val = get_param(op, 0, a, state) b_val = get_param(op, 1, b, state) dest = get_write_param(op, 2, dest, state) { {List.replace_at(memory, dest, a_val + b_val), relative_base}, :cont, 4 } end # multiply def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 2) do a_val = get_param(op, 0, a, state) b_val = get_param(op, 1, b, state) dest = get_write_param(op, 2, dest, state) { {List.replace_at(memory, dest, a_val * b_val), relative_base}, :cont, 4 } end # input def eval([op, addr | _], {memory, relative_base} = state, parent) when opcode(op, 3) do addr = get_write_param(op, 0, addr, state) send(parent, {:in, self()}) res = receive do {:in, val} -> val end { {List.replace_at(memory, addr, res), relative_base}, :cont, 2 } end # output def eval([op, param | _], state, parent) when opcode(op, 4) do out = get_param(op, 0, param, state) send(parent, {:out, self(), out}) {state, :cont, 2} end # jump if non-zero def eval([op, condition, ip | _], state, _) when opcode(op, 5) do case get_param(op, 0, condition, state) do 0 -> {state, :cont, 3} _ -> {state, :jump, get_param(op, 1, ip, state)} end end # jump if zero def eval([op, condition, ip | _], state, _) when opcode(op, 6) do case get_param(op, 0, condition, state) do 0 -> {state, :jump, get_param(op, 1, ip, state)} _ -> {state, :cont, 3} end end # less than def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 7) do a_val = get_param(op, 0, a, state) b_val = get_param(op, 1, b, state) dest = get_write_param(op, 2, dest, state) memory = if a_val < b_val do List.replace_at(memory, dest, 1) else List.replace_at(memory, dest, 0) end {{memory, relative_base}, :cont, 4} end # equals def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 8) do a_val = get_param(op, 0, a, state) b_val = get_param(op, 1, b, state) dest = get_write_param(op, 2, dest, state) memory = if a_val == b_val do List.replace_at(memory, dest, 1) else List.replace_at(memory, dest, 0) end {{memory, relative_base}, :cont, 4} end # set relative base def eval([op, new_base | _], {memory, relative_base} = state, _) when opcode(op, 9) do {{memory, relative_base + get_param(op, 0, new_base, state)}, :cont, 2} end end