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]