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, debug \\ false) def run(memory, parent, ip, debug) when is_list(memory) do run({memory, 0}, parent, ip, debug) end def run({memory, _relative_base} = state, parent, ip, debug) 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} -> if debug do receive do :step -> run(state, parent, ip + offset) :abort -> send(parent, {:abort, self(), state}) end else run(state, parent, ip + offset) end {state, :jump, ip} -> if debug do receive do :step -> run(state, parent, ip) :abort -> send(parent, {:abort, self(), state}) end else run(state, parent, ip) end 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), {:position, val}} 1 -> {val, {:immediate, val}} 2 -> {Enum.at(memory, relative_base + val), {:relative, 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, {:relative, val}} _ -> {val, {:immediate, 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, parent) do send(parent, {:debug, "Halted", [], memory}) {memory, :halt} end # add def eval([op, a, b, dest | _], {memory, relative_base} = state, parent) when opcode(op, 1) do {a_val, a_param} = get_param(op, 0, a, state) {b_val, b_param} = get_param(op, 1, b, state) {dest, dest_param} = get_write_param(op, 2, dest, state) new_state = {List.replace_at(memory, dest, a_val + b_val), relative_base} send(parent, {:debug, "Add", [a_param, b_param, dest_param], new_state}) { new_state, :cont, 4 } end # multiply def eval([op, a, b, dest | _], {memory, relative_base} = state, parent) when opcode(op, 2) do {a_val, a_param} = get_param(op, 0, a, state) {b_val, b_param} = get_param(op, 1, b, state) {dest, dest_param} = get_write_param(op, 2, dest, state) new_state = {List.replace_at(memory, dest, a_val * b_val), relative_base} send(parent, {:debug, "Multiply", [a_param, b_param, dest_param], new_state}) { new_state, :cont, 4 } end # input def eval([op, addr | _], {memory, relative_base} = state, parent) when opcode(op, 3) do {addr, addr_param} = get_write_param(op, 0, addr, state) send(parent, {:in, self()}) res = receive do {:in, val} -> val end new_state = {List.replace_at(memory, addr, res), relative_base} send(parent, {:debug, "Input", [addr_param], new_state}) { new_state, :cont, 2 } end # output def eval([op, param | _], state, parent) when opcode(op, 4) do {out, out_param} = get_param(op, 0, param, state) send(parent, {:out, self(), out}) send(parent, {:debug, "Output", [out_param], state}) {state, :cont, 2} end # jump if non-zero def eval([op, condition, ip | _], state, parent) when opcode(op, 5) do {cond_val, cond_param} = get_param(op, 0, condition, state) {dest_val, dest_param} = get_param(op, 1, ip, state) new_state = case cond_val do 0 -> {state, :cont, 3} _ -> {state, :jump, dest_val} end send(parent, {:debug, "Jump if non-zero", [cond_param, dest_param], new_state}) new_state end # jump if zero def eval([op, condition, ip | _], state, parent) when opcode(op, 6) do {cond_val, cond_param} = get_param(op, 0, condition, state) {dest_val, dest_param} = get_param(op, 1, ip, state) new_state = case cond_val do 0 -> {state, :jump, dest_val} _ -> {state, :cont, 3} end send(parent, {:debug, "Jump if zero", [cond_param, dest_param], new_state}) new_state end # less than def eval([op, a, b, dest | _], {memory, relative_base} = state, parent) when opcode(op, 7) do {a_val, a_param} = get_param(op, 0, a, state) {b_val, b_param} = get_param(op, 1, b, state) {dest, dest_param} = get_write_param(op, 2, dest, state) new_state = { if a_val < b_val do List.replace_at(memory, dest, 1) else List.replace_at(memory, dest, 0) end, relative_base } send(parent, {:debug, "Less than", [a_param, b_param, dest_param], new_state}) {new_state, :cont, 4} end # equals def eval([op, a, b, dest | _], {memory, relative_base} = state, parent) when opcode(op, 8) do {a_val, a_param} = get_param(op, 0, a, state) {b_val, b_param} = get_param(op, 1, b, state) {dest, dest_param} = get_write_param(op, 2, dest, state) new_state = { if a_val == b_val do List.replace_at(memory, dest, 1) else List.replace_at(memory, dest, 0) end, relative_base } send(parent, {:debug, "Equal to", [a_param, b_param, dest_param], new_state}) {new_state, :cont, 4} end # set relative base def eval([op, new_base | _], {memory, relative_base} = state, parent) when opcode(op, 9) do {new_base_val, new_base_param} = get_param(op, 0, new_base, state) new_state = {memory, relative_base + new_base_val} send(parent, {:debug, "Set relative base", [new_base_param], new_state}) {new_state, :cont, 2} end end