Sean McLemon | Advent of Code

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


2019-12-05 - Sunny with a Chance of Asteroids

(original .ipynb)

Day 5 puzzle input is a list of integers which are instructions for a program which will run on the processor we started writing a few days before (mine is here). Part 1 involves adding a bit more functionality to the interpreter we wrote and finding what the output is if we send 1 as the input to the program. Part 2 involves adding still more functionality and finding what is output if we send 5 as input.

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_terminate = 99

mode_position = 0
mode_immediate = 1

def uop_read(memory, value, mode):
    debug_log("\tuop_read")
    if mode == mode_position:
        debug_log("\tposition")
        return int(memory[value])
    
    elif mode == mode_immediate:
        debug_log("\timmediate")
        return int(value)
    
    else:
        raise Exception("UNKNOWN MODE")
    
    
def uop_cond_jump(memory, pc, modes, cond):
    param_mode = try_pop_mode(modes)
    param_raw = int(memory[pc + 1])
    param = uop_read(memory, param_raw, param_mode)
    
    dest_mode = try_pop_mode(modes)
    dest_raw = int(memory[pc + 2])
    dest = uop_read(memory, dest_raw, dest_mode)
    
    if cond(param):
        return dest
    
    return pc + 3
    
def uop_cmp(memory, pc, modes, cmp):
    debug_log("\tuop_cmp")
    
    param0_mode = try_pop_mode(modes)
    param0_raw = int(memory[pc + 1])
    param0 = uop_read(memory, param0_raw, param0_mode)
    
    param1_mode = try_pop_mode(modes)
    param1_raw = int(memory[pc + 2])
    param1 = uop_read(memory, param1_raw, param1_mode)
    
    dest = int(memory[pc + 3])
    
    if cmp(param0, param1):
        memory[dest] = 1
    else:
        memory[dest] = 0
    
    return pc + 4    
    
    
    
def op_add(memory, pc, 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(memory[pc + 1])
    arg1_raw = int(memory[pc + 2])
    dest = int(memory[pc + 3])
    
    arg0 = uop_read(memory, arg0_raw, arg0_mode)
    arg1 = uop_read(memory, arg1_raw, arg1_mode)
    
    debug_log(dest, "=", arg0, "+", arg1)
    memory[dest] = str(int(arg0) + int(arg1))
    return pc + 4

def op_mul(memory, pc, 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(memory[pc + 1])
    arg1_raw = int(memory[pc + 2])
    dest = int(memory[pc + 3])
    
    arg0 = uop_read(memory, arg0_raw, arg0_mode)
    arg1 = uop_read(memory, arg1_raw, arg1_mode)
    
    debug_log(dest, "=", arg0, "*", arg1)
    memory[dest] = str(int(arg0) * int(arg1))
    return 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(memory, pc, modes, input_buffer, output_buffer):
    debug_log("op_read")    
    dest = int(memory[pc + 1])
    val = input_buffer.pop()
    debug_log("\tdest", dest)
    debug_log("\tval", val)
    memory[dest] = str(val)
    
    return 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(memory, pc, modes, input_buffer, output_buffer):
    debug_log("op_write")
    
    src_mode = try_pop_mode(modes)
    src_raw = int(memory[pc + 1])
    src = uop_read(memory, src_raw, src_mode)
    
    output_buffer.append(src)
    
    return 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(memory, pc, modes, input_buffer, output_buffer):
    debug_log("op_jump_true")
    return uop_cond_jump(memory, pc, 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(memory, pc, modes, input_buffer, output_buffer):
    debug_log("op_jump_false")

    return uop_cond_jump(memory, pc, 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(memory, pc, modes, input_buffer, output_buffer):
    debug_log("op_lt")
    
    return uop_cmp(memory, pc, 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(memory, pc, modes, input_buffer, output_buffer):
    debug_log("op_eq")
    
    return uop_cmp(memory, pc, modes, lambda x, y: x == y)

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()

def decode_instr(instr):
    instr = str(instr)
    opcode = decode_op(instr)
    modes = decode_modes(instr)
    debug_log(f"\t{modes}")

    if opcode == opcode_add:
        return (op_add, modes)
    elif opcode == opcode_mul:
        return (op_mul, modes)
    elif opcode == opcode_read:
        return (op_read, modes)
    elif opcode == opcode_write:
        return (op_write, modes)
    elif opcode == opcode_jump_true:
        return (op_jump_true, modes)
    elif opcode == opcode_jump_false:
        return (op_jump_false, modes)
    elif opcode == opcode_lt:
        return (op_lt, modes)
    elif opcode == opcode_eq:
        return (op_eq, modes)
    else:
        raise Exception(f"Invalid opcode {opcode}")

def run_intcode(program, input_buffer, output_buffer):
    pc = 0
    instr = program[pc]
    
    while int(instr) != opcode_terminate:
        debug_log(pc, instr)
        (op, modes) = decode_instr(instr)
        pc = op(program, pc, modes, input_buffer, output_buffer)
        instr = program[pc]
        debug_log(program)
        
    
    return program[0]


def debug_log(*args):
#     print(*args)
    pass


puzzle_input_str = open("puzzle_input/day5.txt", "r").read().split(",")
puzzle_input = [int(s) for s in puzzle_input_str]

def run_program(noun, verb, program, input_buffer, output_buffer):
    program_copy = [ x for x in program ]
#     program_copy[1] = noun
#     program_copy[2] = verb
    return run_intcode(program_copy, input_buffer, output_buffer)


output = []
print(run_program(12, 2, puzzle_input, [1], output))
print(output)
3
[0, 0, 0, 0, 0, 0, 0, 0, 0, 9775037]
output = []

print(run_program(None, None, puzzle_input, [5], output))
print(output)

# 3,21,1008,21,8,20,1005,20,22,107,8,21,20,1006,20,31,1106,0,36,98,0,0,1002,21,125,20,4,20,1105,1,46,104,999,1105,1,46,1101,1000,1,20,4,20,1105,1,46,98,99 - take input, if<8 output 999, if ==8 output 1000, if>8 output 1001
# 3,9,8,9,10,9,4,9,99,-1,8 - Using position mode, consider whether the input is equal to 8; output 1 (if it is) or 0 (if it is not).
# 3,9,7,9,10,9,4,9,99,-1,8 - Using position mode, consider whether the input is less than 8; output 1 (if it is) or 0 (if it is not).
# 3,3,1108,-1,8,3,4,3,99 - Using immediate mode, consider whether the input is equal to 8; output 1 (if it is) or 0 (if it is not).
# 3,3,1107,-1,8,3,4,3,99 - Using immediate mode, consider whether the input is less than 8; output 1 (if it is) or 0 (if it is not).
314
[15586959]