function get_arg(mem, pos, arg) local param_mode = mem[pos] // (10 ^ (arg + 1)) % 10 if #mem < pos + arg then print(string.format("Error: Opcode too short at end of memory: %d", pos)) os.exit(false) end local arg = mem[pos + arg] if param_mode == 0 then arg = arg + 1 if #mem < arg then print(string.format("Error: Memory offset out of bounds: %d", arg)) os.exit(false) end end return arg end function get_param(mem, pos, arg) local param_mode = mem[pos] // (10 ^ (arg + 1)) % 10 local arg = get_arg(mem, pos, arg) if param_mode == 0 then return mem[arg] end return arg end function interpret(mem, state, input_func, output_func) if not state.pos then state.pos = 1 end local pos = state.pos while true do if #mem < pos then print(string.format("Instruction pointer ran off of memory")) os.exit(false) end local opcode = mem[pos] % 100 if opcode == 1 then mem[get_arg(mem, pos, 3)] = get_param(mem, pos, 1) + get_param(mem, pos, 2) pos = pos + 4 elseif opcode == 2 then mem[get_arg(mem, pos, 3)] = get_param(mem, pos, 1) * get_param(mem, pos, 2) pos = pos + 4 elseif opcode == 3 then local val = input_func() if not val then state.pos = pos return true end mem[get_arg(mem, pos, 1)] = val pos = pos + 2 elseif opcode == 4 then output_func(get_param(mem, pos, 1)) pos = pos + 2 elseif opcode == 5 then if get_param(mem, pos, 1) ~= 0 then pos = get_param(mem, pos, 2) + 1 else pos = pos + 3 end elseif opcode == 6 then if get_param(mem, pos, 1) == 0 then pos = get_param(mem, pos, 2) + 1 else pos = pos + 3 end elseif opcode == 7 then mem[get_arg(mem, pos, 3)] = get_param(mem, pos, 1) < get_param(mem, pos, 2) and 1 or 0 pos = pos + 4 elseif opcode == 8 then mem[get_arg(mem, pos, 3)] = get_param(mem, pos, 1) == get_param(mem, pos, 2) and 1 or 0 pos = pos + 4 elseif opcode == 99 then state.pos = pos return false else print(string.format("Error: Invalid opcode %d at %d", opcode, pos - 1)) os.exit(false) end end end function input_stdin() io.stdout:write("> ") return io.stdin:read("n") end function output_stdout(val) io.stdout:write(val) io.stdout:write("\n") end function input_array(array) function inner() return table.remove(array, 1) end return inner end function output_array(array) function inner(val) table.insert(array, val) end return inner end file = io.open(arg[1]) program = {} for num in string.gmatch(file:read(), "(-?%d+),?") do table.insert(program, tonumber(num)) end file:close() function get_signal(phase) signal = 0 for i = 1, 5 do memory = {table.unpack(program)} output = {} interpret(memory, {}, input_array({phase[i], signal}), output_array(output)) signal = output[1] end return signal end function get_signal_p2(phase) machines = {} for i = 1, 5 do if i == 1 then input = {} else input = machines[i - 1].output end table.insert(input, phase[i] + 5) machines[i] = {mem={table.unpack(program)}, input=input, output={}} end machines[5].output = machines[1].input table.insert(machines[1].input, 0) while true do done = true for i = 1, 5 do if interpret(machines[i].mem, machines[i], input_array(machines[i].input), output_array(machines[i].output)) then done = false end end if done then break end end return machines[5].output[1] end biggest = 0 biggest_p2 = 0 for i = 0, (5 ^ 5) - 1 do phase = { i // (5 ^ 4) % 5, i // (5 ^ 3) % 5, i // (5 ^ 2) % 5, i // (5 ^ 1) % 5, i // (5 ^ 0) % 5 } count = {} for _, x in ipairs(phase) do if not count[x] then count[x] = 0 end count[x] = count[x] + 1 end double = false for _, x in pairs(count) do if x > 1 then double = true end end if not double then signal = get_signal(phase) if signal > biggest then biggest = signal end signal = get_signal_p2(phase) if signal > biggest_p2 then biggest_p2 = signal end end end print("Part 1:", biggest) print("Part 2:", biggest_p2)