aoc2024/day17.py
2024-12-17 19:45:36 +01:00

239 lines
7.0 KiB
Python

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)