2019-12-09 - Sensor Boost
(original .ipynb)
Day 9 puzzle input is IntCode program called "BOOST" which is a test that the IntCode CPU is operating correctly (mine is here). Part 1 involves running the program with a given input until it succeeds and outputs a code. Part 2 involves running this program with a different input - it'll output another code.
opcode_add = 1 opcode_mul = 2 opcode_read = 3 opcode_write = 4 opcode_jump_true = 5 opcode_jump_false = 6 opcode_lt = 7 opcode_eq = 8 opcode_rebase = 9 opcode_terminate = 99 mode_position = 0 mode_immediate = 1 mode_relative = 2 class CpuState: def __init__(self, memory, pc, offset): self.memory = memory self.pc = pc self.offset = offset def resize_memory(state, target_addr): state.memory += ([0] * (1 + target_addr - len(state.memory))) def uop_read(state, value, mode): debug_log("\tuop_read") if mode == mode_position: if value >= len(state.memory): resize_memory(state, value) return int(state.memory[value]) elif mode == mode_relative: if state.offset + value >= len(state.memory): resize_memory(state, state.offset + value) return int(state.memory[state.offset + value]) elif mode == mode_immediate: return int(value) else: raise Exception("UNKNOWN MODE") def uop_write(state, dst, value, mode): debug_log("\tuop_write") if mode == mode_position: if dst >= len(state.memory): resize_memory(state, dst) state.memory[dst] = value elif mode == mode_relative: if state.offset + dst >= len(state.memory): resize_memory(state, state.offset + dst) state.memory[state.offset + dst] = value elif mode == mode_immediate: raise Exception(f"cannot write {value} to literal {dst}") def uop_cond_jump(state, modes, cond): param_mode = try_pop_mode(modes) param_raw = int(state.memory[state.pc + 1]) param = uop_read(state, param_raw, param_mode) dest_mode = try_pop_mode(modes) dest_raw = int(state.memory[state.pc + 2]) dest = uop_read(state, dest_raw, dest_mode) debug_log("\tuop_cond_jump", param, dest) if cond(param): return dest return state.pc + 3 def uop_cmp(state, modes, cmp): param0_mode = try_pop_mode(modes) param0_raw = int(state.memory[state.pc + 1]) param0 = uop_read(state, param0_raw, param0_mode) param1_mode = try_pop_mode(modes) param1_raw = int(state.memory[state.pc + 2]) param1 = uop_read(state, param1_raw, param1_mode) dest_mode = try_pop_mode(modes) dest = int(state.memory[state.pc + 3]) debug_log("\tuop_cmp", param0, param1, dest, dest_mode) if cmp(param0, param1): uop_write(state, dest, 1, dest_mode) else: uop_write(state, dest, 0, dest_mode) return state.pc + 4 def op_add(state, modes, input_buffer, output_buffer): debug_log("op_add") arg0_mode = try_pop_mode(modes) arg1_mode = try_pop_mode(modes) dest_mode = try_pop_mode(modes) arg0_raw = int(state.memory[state.pc + 1]) arg1_raw = int(state.memory[state.pc + 2]) dest = int(state.memory[state.pc + 3]) arg0 = uop_read(state, arg0_raw, arg0_mode) arg1 = uop_read(state, arg1_raw, arg1_mode) debug_log("\t", dest, "=", arg0, "+", arg1) uop_write(state, dest, str(int(arg0) + int(arg1)), dest_mode) return state.pc + 4 def op_mul(state, modes, input_buffer, output_buffer): debug_log("op_mul") arg0_mode = try_pop_mode(modes) arg1_mode = try_pop_mode(modes) dest_mode = try_pop_mode(modes) arg0_raw = int(state.memory[state.pc + 1]) arg1_raw = int(state.memory[state.pc + 2]) dest = int(state.memory[state.pc + 3]) arg0 = uop_read(state, arg0_raw, arg0_mode) arg1 = uop_read(state, arg1_raw, arg1_mode) debug_log("\t", dest, "=", arg0, "*", arg1) uop_write(state, dest, str(int(arg0) * int(arg1)), dest_mode) return state.pc + 4 # Opcode 3 takes a single integer as input and # saves it to the position given by its only parameter. For example, the instruction 3,50 # would take an input value and store it at address 50. def op_read(state, modes, input_buffer, output_buffer): debug_log("op_read") dest_mode = try_pop_mode(modes) dest = int(state.memory[state.pc + 1]) val = input_buffer.pop() debug_log("\tdest", dest) debug_log("\tval", val) uop_write(state, dest, str(val), dest_mode) debug_log("\tread:", state.memory[dest]) return state.pc + 2 # Opcode 4 outputs the value of its only parameter. For example, the instruction 4,50 # would output the value at address 50. def op_write(state, modes, input_buffer, output_buffer): debug_log("op_write") src_mode = try_pop_mode(modes) src_raw = int(state.memory[state.pc + 1]) src = uop_read(state, src_raw, src_mode) output_buffer.append(src) return state.pc + 2 # Opcode 5 is jump-if-true: # if the first parameter is non-zero, it sets the instruction pointer to # the value from the second parameter. Otherwise, it does nothing. def op_jump_true(state, modes, input_buffer, output_buffer): debug_log("op_jump_true") return uop_cond_jump(state, modes, lambda x: x != 0) # Opcode 6 is jump-if-false: # if the first parameter is zero, it sets the instruction pointer to the # value from the second parameter. Otherwise, it does nothing. def op_jump_false(state, modes, input_buffer, output_buffer): debug_log("op_jump_false") return uop_cond_jump(state, modes, lambda x: x == 0) # Opcode 7 is less than: # if the first parameter is less than the second parameter, it stores 1 # in the position given by the third parameter. Otherwise, it stores 0. def op_lt(state, modes, input_buffer, output_buffer): debug_log("op_lt") return uop_cmp(state, modes, lambda x, y: x < y) # Opcode 8 is equals: # if the first parameter is equal to the second parameter, it stores 1 # in the position given by the third parameter. Otherwise, it stores 0. def op_eq(state, modes, input_buffer, output_buffer): debug_log("op_eq") return uop_cmp(state, modes, lambda x, y: x == y) # Opcode 9 adjusts the relative base by the value of its only parameter. # The relative base increases (or decreases, if the value is negative) # by the value of the parameter. def op_rebase(state, modes, input_buffer, output_buffer): param_mode = try_pop_mode(modes) param_raw = int(state.memory[state.pc + 1]) param = uop_read(state, param_raw, param_mode) state.offset += param return state.pc + 2 def decode_op(instr): if len(instr) > 2: return int(instr[-2:]) return int(instr) def decode_modes(instr): if len(instr) > 2: return [ int(d) for d in instr[:-2]] return [] def try_pop_mode(modes): if len(modes) == 0: return 0 return modes.pop() opcodes = { opcode_add: op_add, opcode_mul: op_mul, opcode_read: op_read, opcode_write: op_write, opcode_jump_true: op_jump_true, opcode_jump_false: op_jump_false, opcode_lt: op_lt, opcode_eq: op_eq, opcode_rebase: op_rebase } def decode_instr(instr): instr = str(instr) opcode = decode_op(instr) modes = decode_modes(instr) if not (opcode in opcodes): raise Exception(f"Invalid opcode {opcode}") return (opcodes[opcode], modes) debug_on = False def debug_log(*args): global debug_on if debug_on: print(*args) def run_intcode(program, input_buffer, output_buffer): state = CpuState(program, 0, 0) instr = state.memory[state.pc] while int(instr) != opcode_terminate: debug_log(state.pc, state.offset, instr) (op, modes) = decode_instr(instr) state.pc = op(state, modes, input_buffer, output_buffer) instr = state.memory[state.pc] debug_log() return program[0] def run_program(program, input_buffer, output_buffer, noun=None, verb=None): program_copy = [ x for x in program ] if noun: program_copy[1] = noun if verb: program_copy[2] = verb return run_intcode(program_copy, input_buffer, output_buffer) def calculate_keycode(program, input_buffer): output_buffer = [] run_program(program, input_buffer, output_buffer) print(output_buffer) puzzle_input = open("puzzle_input/day9.txt", "r").read().split(",") calculate_keycode(puzzle_input, [1])
[2351176124]
calculate_keycode(puzzle_input, [2])
[73110]