More computer work in day 9, this time adding what is kind of a stack pointer and an opcode to adjust that pointer. Now I can add a relative address mode, getting positions relative to the stack pointer.

Challenge

The puzzle can be found here. Today I’m to add a new address mode, relative base. I’ll keep a pointer to the current relative base, which starts at 0. Now address mode 2 is to look that many spots from the base pointer. I’ll also need to handle memory space beyond my initial proram.

Solution

Part 1

The first thing I did was add the new mode to the computer. Since I’m already working with a get_param function, it was relatively easy. I’ll initialize the relative base at object creation. I did a bit of refactoring so that I could clean up my code calling get_param which included calling with the reg offset vs the value. And I added the new mode:

    def get_param(self, mode, reg):
        value = self.program[self.eip + reg]
        if mode == "0":
            return self.program[value]
        elif mode == "1":
            return value
        elif mode == "2":
            return self.program[self.rel_base + value]
        else:
            print("Error: Invalid Parameter Mode")
            sys.exit()

I’ll also handle the new op code:

            elif op == 9:
                self.rel_base += self.get_param(mode1, 1)
                self.eip += 3

To handle the larger computer memory, with default values of 0, I’ll start using a defaultdict instead of a list. I’ll also move the processing of the string into the __init__ function, so I’m not having to handle it outside the class:

    def __init__(self, program):
        self.program = defaultdict(int)
        for i, v in enumerate(list(map(int, program.split(",")))):
            self.program[i] = v
        self.eip = 0
        self.rel_base = 0

In day 7, I had to handle two inputs, and I ended up splitting that into a phase that was passed in at computer initialization, and a signal that was passed on each call to compute. Since the BOOST program for today only takes one input, I’ll just pass it to compute. This results in less stuff in __init__ above, and as well as a simplified opcode 3:

            elif op == 3:
                self.program[self.get_address(mode1, 1)] = signal
                self.eip += 2

I spent a lot of time troubleshooting, and eventually figured out that I was mishandling how a result could be written to relatively mode. I had an assert to check that mode3 was 0, but other commands used earlier args to get the address to write to. I eventually added a get_address function:

    def get_address(self, mode, reg):
        value = self.program[self.eip + reg]
        if mode == "0":
            return value
        elif mode == "2":
            return self.rel_base + value
        else:
            print("Error: Invalid Address Mode")
            sys.exit()

Putting it all together, I get a quick answer:

$ time python3 ./day9.py 09-puzzle_input.txt 
Part 1:
3989758265

real    0m0.018s
user    0m0.009s
sys     0m0.009s

Part 2

Nothing changed for part 2, other than the input. So I added a new computer and called compute(2), and ran it, and got the result, albeit slightly more slowly:

$ time python3 ./day9.py 09-puzzle_input.txt 
Part 1:
3989758265
Part 2:
76791

real    0m0.534s
user    0m0.534s
sys     0m0.000s

Final Code

#!/usr/bin/env python3

import sys
from collections import defaultdict
from itertools import permutations


class computer:
    def __init__(self, program):
        self.program = defaultdict(int)
        for i, v in enumerate(list(map(int, program.split(",")))):
            self.program[i] = v
        self.eip = 0
        self.rel_base = 0

    def get_param(self, mode, reg):
        value = self.program[self.eip + reg]
        if mode == "0":
            return self.program[value]
        elif mode == "1":
            return value
        elif mode == "2":
            return self.program[self.rel_base + value]
        else:
            print("Error: Invalid Parameter Mode")
            sys.exit()

    def get_address(self, mode, reg):
        value = self.program[self.eip + reg]
        if mode == "0":
            return value
        elif mode == "2":
            return self.rel_base + value
        else:
            print("Error: Invalid Address Mode")
            sys.exit()

    def compute(self, signal):
        while True:
            inst = self.program[self.eip]
            op = inst % 100
            mode3, mode2, mode1 = f"{inst // 100:03d}"
            if op == 1:
                self.program[self.get_address(mode3, 3)] = self.get_param(
                    mode1, 1
                ) + self.get_param(mode2, 2)
                self.eip += 4
            elif op == 2:
                self.program[self.get_address(mode3, 3)] = self.get_param(
                    mode1, 1
                ) * self.get_param(mode2, 2)
                self.eip += 4
            elif op == 3:
                self.program[self.get_address(mode1, 1)] = signal
                self.eip += 2
            elif op == 4:
                print(self.get_param(mode1, 1))
                self.eip += 2
            elif op == 5:
                if self.get_param(mode1, 1) != 0:
                    self.eip = self.get_param(mode2, 2)
                else:
                    self.eip += 3
            elif op == 6:
                if self.get_param(mode1, 1) == 0:
                    self.eip = self.get_param(mode2, 2)
                else:
                    self.eip += 3
            elif op == 7:
                self.program[self.get_address(mode3, 3)] = int(
                    self.get_param(mode1, 1) < self.get_param(mode2, 2)
                )
                self.eip += 4
            elif op == 8:
                self.program[self.get_address(mode3, 3)] = int(
                    self.get_param(mode1, 1) == self.get_param(mode2, 2)
                )
                self.eip += 4
            elif op == 9:
                self.rel_base += self.get_param(mode1, 1)
                self.eip += 2
            elif op == 99:
                self.done = True
                return None
            else:
                print("Error")
                sys.exit()


with open(sys.argv[1], "r") as f:
    program_str = f.read().strip()

print("Part 1:")
comp = computer(program_str)
comp.compute(1)

print("Part 2:")
comp = computer(program_str)
comp.compute(2)