advent-of-code/2019/day5/day5.py
2024-12-03 15:26:47 +01:00

164 lines
4.7 KiB
Python

#! /usr/bin/env python3
from collections import namedtuple
from enum import Enum
import logging
logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.WARN)
def get_nth_digit(n, number):
"Returns the nth digit of the input number"
return number // 10 ** n % 10
class Operation(Enum):
ADDITION = 1
MULTIPLICATION = 2
INPUT = 3
OUTPUT = 4
JMP_IF_TRUE = 5
JMP_IF_FALSE = 6
LESS_THAN = 7
EQUALS = 8
TERMINATION = 99
class Mode(Enum):
POSITION = 0
IMMEDIATE = 1
class Instruction:
def __init__(self, opcode):
self.operation = Operation(opcode % 100)
self.modes = [Mode(get_nth_digit(n, opcode)) for n in range(2, 5)]
self.handler_name = f"handle_{self.operation.name.lower()}"
self.handler = getattr(self, self.handler_name, self.handle_termination)
def __repr__(self):
return f"Instruction({self.operation}, {self.modes})"
def handle(self, program, ip):
return self.handler(program, ip)
def handle_addition(self, program, ip):
first, second = self._get_parameters(program, ip)
logging.debug(f"ADD {first} {second}")
result = first + second
# the last mode should *always* be POSITION
program[program[ip + 3]] = result
ip += 4
return ip
def handle_multiplication(self, program, ip):
first, second = self._get_parameters(program, ip)
logging.debug(f"MUL {first} {second}")
result = first * second
# the last mode should *always* be POSITION
program[program[ip + 3]] = result
ip += 4
return ip
def handle_input(self, program, ip):
ip += 2
program[program[ip + 1]] = int(input("Enter ID > "))
return ip
def handle_output(self, program, ip):
print("OUT ", program[program[ip + 1]])
ip += 2
return ip
def handle_jmp_if_true(self, program, ip):
first, second = self._get_parameters(program, ip)
logging.debug(f"JMPT {first} {second}")
return second if first != 0 else ip + 3
def handle_jmp_if_false(self, program, ip):
first, second = self._get_parameters(program, ip)
logging.debug(f"JMPF {first} {second}")
return second if first == 0 else ip + 3
def handle_less_than(self, program, ip):
first, second = self._get_parameters(program, ip)
logging.debug(f"LT {first} {second}")
program[program[ip + 3]] = int(first < second)
ip += 4
return ip
def handle_equals(self, program, ip):
first, second = self._get_parameters(program, ip)
logging.debug(f"EQ {first} {second}")
program[program[ip + 3]] = int(first == second)
ip += 4
return ip
def handle_termination(self, program, ip):
print("HALT")
return ip
def _get_parameters(self, program, ip):
first = (
program[ip + 1]
if self.modes[0] is Mode.IMMEDIATE
else program[program[ip + 1]]
)
second = (
program[ip + 2]
if self.modes[1] is Mode.IMMEDIATE
else program[program[ip + 2]]
)
return first, second
def interpret_intcode(program, stdin=None):
ip = 0
while program[ip] != 99:
opcode = program[ip]
instruction = Instruction(opcode)
ip = instruction.handle(program, ip)
def tests():
inputs = (
[1, 0, 0, 0, 99], # ADD 1 + 1 to location 0
[2, 3, 0, 3, 99], # MUL 2 * 3 to location 3
[2, 4, 4, 5, 99, 0],
[1, 1, 1, 4, 99, 5, 6, 0, 99],
[1101, 1, 1, 0, 99], # ADD 1 + 1 to location 0 (direct access)
)
expected_outputs = (
[2, 0, 0, 0, 99], # 1 + 1 = 2
[2, 3, 0, 6, 99], # 3 * 2 = 6
[2, 4, 4, 5, 99, 9801], # 99 * 99 = 9801
[30, 1, 1, 4, 2, 5, 6, 0, 99],
[2, 1, 1, 0, 99], # 1 + 1 = 2 (direct access)
)
for i, inp in enumerate(inputs):
result = inp[:]
interpret_intcode(result)
assert (
result == expected_outputs[i]
), f"Expected output for {inp} is {expected_outputs[i]}, but found {result} instead."
print("\nAll tests passed.\n")
def run_input_program(filename):
import os
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
with open(os.path.join(SCRIPT_DIR, filename)) as inp:
print("Start of input program.")
memory = [int(x) for x in inp.readline().strip().split(",")]
interpret_intcode(memory, 5)
if __name__ == "__main__":
tests()
# a = [3, 12, 6, 12, 15, 1, 13, 14, 13, 4, 13, 99, -1, 0, 1, 9]
# b = [3, 3, 1105, -1, 9, 1101, 0, 0, 12, 4, 12, 99, 1]
# interpret_intcode(b, 0)
# interpret_intcode(a, 0)
run_input_program("input.txt")