diff --git a/day5/day5.py b/day5/day5.py index a11854e..02254fb 100644 --- a/day5/day5.py +++ b/day5/day5.py @@ -3,7 +3,7 @@ from collections import namedtuple from enum import Enum import logging -logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG) +logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.WARN) def get_nth_digit(n, number): @@ -154,7 +154,7 @@ def run_input_program(filename): if __name__ == "__main__": - # tests() + 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] diff --git a/day5/day52.py b/day5/day52.py new file mode 100644 index 0000000..d31af56 --- /dev/null +++ b/day5/day52.py @@ -0,0 +1,192 @@ +import logging + +logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG) + + +class Instruction: + code, argument_num = 0, 0 + + def execute(self, intcode, arguments): + pass + + def new_pc(self, intcode): + return intcode.pc + self.argument_num + 1 + + +class PlusInstruction(Instruction): + code, argument_num = 1, 3 + + def execute(self, intcode, arguments): + logging.debug(f"ADD {arguments[0].value} {arguments[1].value}") + intcode.set(arguments[2].address, arguments[0].value + arguments[1].value) + return self.new_pc(intcode) + + +class MultiplyInstruction(Instruction): + code, argument_num = 2, 3 + + def execute(self, intcode, arguments): + logging.debug(f"MUL {arguments[0].value} {arguments[1].value}") + intcode.set(arguments[2].address, arguments[0].value * arguments[1].value) + return self.new_pc(intcode) + + +class InputInstruction(Instruction): + code, argument_num = 3, 1 + + def execute(self, intcode, arguments): + intcode.set(arguments[0].address, intcode.get_input()) + return self.new_pc(intcode) + + +class OutputInstruction(Instruction): + code, argument_num = 4, 1 + + def execute(self, intcode, arguments): + intcode.output = arguments[0].value + return self.new_pc(intcode) + + +class JumpIfTrueInstruction(Instruction): + code, argument_num = 5, 2 + + def execute(self, intcode, arguments): + logging.debug(f"JMPT {arguments[0].value} {arguments[1].value}") + return arguments[1].value if arguments[0].value != 0 else self.new_pc(intcode) + + +class JumpIfFalseInstruction(Instruction): + code, argument_num = 6, 2 + + def execute(self, intcode, arguments): + logging.debug(f"JMPF {arguments[0].value} {arguments[1].value}") + return arguments[1].value if arguments[0].value == 0 else self.new_pc(intcode) + + +class LessThanInstruction(Instruction): + code, argument_num = 7, 3 + + def execute(self, intcode, arguments): + logging.debug(f"LT {arguments[0].value} {arguments[1].value}") + intcode.set(arguments[2].address, int(arguments[0].value < arguments[1].value)) + return self.new_pc(intcode) + + +class EqualsInstruction(Instruction): + code, argument_num = 8, 3 + + def execute(self, intcode, arguments): + intcode.set(arguments[2].address, int(arguments[0].value == arguments[1].value)) + return self.new_pc(intcode) + + +class RelativeBaseOffsetInstruction(Instruction): + code, argument_num = 9, 1 + + def execute(self, intcode, arguments): + intcode.relative_base += arguments[0].value + return self.new_pc(intcode) + + +class HaltInstruction(Instruction): + code, argument_num = 99, 0 + + def execute(self, intcode, arguments): + intcode.halted = True + return None + + +class Argument: + def __init__(self, value, address): + self.value = value + self.address = address + + +class IntCode: + def __init__(self, program, inputs=[], input_func=None): + self.program = program[:] + self.inputs = inputs[::-1] + self.input_func = input_func + self.relative_base = 0 + self.memory = {} + self.output = None + self.halted = False + self.pc = 0 + + def _get_instruction(self, instruction_code): + return next( + cls for cls in Instruction.__subclasses__() if cls.code == instruction_code + ) + + def _parse_arguments(self, argument_num): + modes = str(self.program[self.pc]).zfill(5)[:3][::-1] + arguments = [] + for i in range(argument_num): + value = self.program[self.pc + i + 1] + ( + self.relative_base if modes[i] == "2" else 0 + ) + if modes[i] == "1": + arguments.append(Argument(value, value)) + else: + arguments.append( + Argument( + self.program[value] + if value < len(self.program) + else self.memory.get(value, 0), + value, + ) + ) + return arguments + + def run(self): + self.output = None + while not self.halted and self.output is None: + instruction = self._get_instruction(self.program[self.pc] % 100) + arguments = self._parse_arguments(instruction.argument_num) + self.pc = instruction().execute(self, arguments) + return self.output + + def execute(self): + last_output = None + while not self.halted: + output = self.run() + if not self.halted: + last_output = output + return last_output + + def clone(self): + cloned = IntCode(self.program) + cloned.inputs = self.inputs[:] + cloned.input_func = self.input_func + cloned.relative_base = self.relative_base + cloned.memory = {key: value for key, value in self.memory.items()} + cloned.output = self.output + cloned.halted = self.halted + cloned.pc = self.pc + return cloned + + def get(self, address): + return ( + self.program[address] + if address < len(self.program) + else self.memory.get(value + self.relative_base, 0) + ) + + def set(self, address, value): + target = self.program if address < len(self.program) else self.memory + target[address] = value + + def input(self, value): + self.inputs = [value] + self.inputs + + def get_input(self): + return self.inputs.pop() if self.inputs else self.input_func() + + +import os + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +code = list(map(int, open(os.path.join(SCRIPT_DIR, "input.txt")).read().split(","))) + +part2 = IntCode(code, [5]).execute() +print(part2)