import math import sys from enum import Enum from tools.aoc import AOCDay from typing import Any class OpCode(int, Enum): ADV = 0 BXL = 1 BST = 2 JNZ = 3 BXC = 4 OUT = 5 BDV = 6 CDV = 7 class Computer: def __init__(self, reg_a: int, reg_b: int, reg_c: int, code: list[int]) -> None: self.__reg_a = reg_a self.__reg_b = reg_b self.__reg_c = reg_c self.reg_a = self.__reg_a self.reg_b = self.__reg_b self.reg_c = self.__reg_c self.output = [] self.instr_pointer = 0 self.__code = code self.code = [(OpCode(code[x]), code[x + 1]) for x in range(0, len(code), 2)] self.check_output = False self.op = { OpCode.ADV: self.adv, OpCode.BXL: self.bxl, OpCode.BST: self.bst, OpCode.JNZ: self.jnz, OpCode.BXC: self.bxc, OpCode.OUT: self.out, OpCode.BDV: self.bdv, OpCode.CDV: self.cdv, } def reset(self) -> None: self.reg_a = self.__reg_a self.reg_b = self.__reg_b self.reg_c = self.__reg_c self.output = [] self.instr_pointer = 0 def get_combo_value(self, value: int) -> int: if value < 0 or value > 6: raise ValueError(f"Invalid Combo Operand: {value}") if value <= 3: return value elif value == 4: return self.reg_a elif value == 5: return self.reg_b elif value == 6: return self.reg_c def adv(self, operand: int) -> None: self.reg_a = self.reg_a // (2 ** self.get_combo_value(operand)) self.instr_pointer += 1 def bxl(self, operand: int) -> None: self.reg_b ^= operand self.instr_pointer += 1 def bst(self, operand: int) -> None: self.reg_b = self.get_combo_value(operand) % 8 self.instr_pointer += 1 def jnz(self, operand: int) -> None: if self.reg_a == 0: self.instr_pointer += 1 else: if operand % 2 != 0: raise ValueError(f"Invalid JNZ Operand: {operand}") self.instr_pointer = operand // 2 def bxc(self, _: int) -> None: self.reg_b = self.reg_c ^ self.reg_b self.instr_pointer += 1 def out(self, operand: int) -> None: value = self.get_combo_value(operand) % 8 self.output.append(value) if not self.check_output: self.instr_pointer += 1 elif value != self.__code[len(self.output) - 1]: # print(f"Want to add {value} to {self.output=}, but expected {self.__code[len(self.output) - 1]}") self.instr_pointer = len(self.code) else: self.instr_pointer += 1 def bdv(self, operand: int) -> None: self.reg_b = self.reg_a // (2 ** self.get_combo_value(operand)) self.instr_pointer += 1 def cdv(self, operand: int) -> None: self.reg_c = self.reg_a // (2 ** self.get_combo_value(operand)) self.instr_pointer += 1 def get_output(self) -> str: return ",".join(str(x) for x in self.output) def run_code(self) -> None: while self.instr_pointer < len(self.code): # print("Exec", self.instr_pointer) op_code, operand = self.code[self.instr_pointer] self.op[op_code](operand) class Day(AOCDay): inputs = [ [ ("4,6,3,5,6,3,5,2,1,0", "input17_test"), ("7,1,2,3,2,6,7,2,5", "input17_neil"), ("1,6,3,6,5,6,5,1,7", "input17"), ], [ (117440, "input17_test2"), (202356708354602, "input17_neil"), (None, "input17"), ], ] def parse_input(self) -> tuple[int, int, int, list[int]]: a, b, c, _, prog = self.getIntsFromInput() return a[0], b[0], c[0], prog def part1(self) -> Any: computer = Computer(*self.parse_input()) computer.run_code() return computer.get_output() def part2(self) -> Any: a, b, c, prog = self.parse_input() expected_output = ",".join(str(x) for x in prog) computer = Computer(a, b, c, prog) computer.check_output = True print(computer.code) init_a = 0 output = "" factor = 0 additive = 0 while output != expected_output: computer.reset() init_reg = init_a * (8 ** factor) + additive if init_reg > 8 ** 16: print("FAIL! AGAIN!") break computer.reg_a = init_reg computer.run_code() output = computer.get_output() if len(output) > factor + 1 and output == expected_output[:len(output)]: print(init_a, init_reg, output, expected_output) additive = (int(bin(init_reg)[-6:], 2) << (3 * factor)) | additive #additive += int(bin(init_reg)[-6:], 2) factor += 2 if len(output) > 3 and output == expected_output[:len(output)]: print(f"{init_a=}, {factor=}, {additive=}, {output=}, {expected_output=}") if init_a % 1_000_000 == 0: print(init_a, init_reg) init_a += 1 print(init_a - 1, (init_a - 1) * (8 ** factor) + additive) computer.reset() computer.reg_a = init_a - 1 computer.run_code() print(computer.get_output()) if computer.get_output() == expected_output: return init_a - 1 return "" first = [] for i, c in enumerate(prog): reg_a_init = 0 while len(first) <= i: computer.reset() computer.reg_a = reg_a_init computer.run_code() if int(computer.get_output()[0]) == c: first.append(reg_a_init) reg_a_init += 1 print(first) result = 0 for i, c in enumerate(reversed(first)): result = result << 3 result += c // 8 #result = sum(((x * (8 ** i) for i, x in enumerate(first)))) print(result) computer.reset() computer.reg_a = result computer.run_code() print(computer.get_output()) if computer.get_output() == expected_output: return result while computer.get_output() != expected_output: result -= 1 computer.reset() computer.reg_a = result computer.run_code() return result for i in range(len(first)): result = sum(((x * (8 ** i) for i, x in enumerate(first[:i + 1])))) print("Testing ", first[:i + 1], "=", result) computer.reset() computer.reg_a = result computer.run_code() print(expected_output) print(computer.get_output()) if computer.get_output() == expected_output: return result else: return "" if __name__ == "__main__": day = Day(2024, 17) day.run(verbose=True)