Sean McLemon | Advent of Code

Home | Czech | Blog | GitHub | Advent Of Code | Notes


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]