168 lines
4.6 KiB
Python
168 lines
4.6 KiB
Python
import logging
|
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
|
|
class Machine:
|
|
register_a: int
|
|
register_b: int
|
|
register_c: int
|
|
|
|
ic: int = 0 # Instruction Counter
|
|
|
|
program: list[int] = []
|
|
|
|
def __init__(self, program: str):
|
|
self.register_a = 0
|
|
self.register_b = 0
|
|
self.register_c = 0
|
|
|
|
self.program = [int(x) for x in program.split(',')]
|
|
|
|
def interpret_combo_operand(self, operand: int) -> int:
|
|
match operand:
|
|
case operand if operand in range(0, 4):
|
|
return operand
|
|
case 4:
|
|
return self.register_a
|
|
case 5:
|
|
return self.register_b
|
|
case 6:
|
|
return self.register_c
|
|
|
|
def adv(self, operand: int) -> None:
|
|
operand = self.interpret_combo_operand(operand)
|
|
self.ic += 2
|
|
self.register_a = int(self.register_a / (2 ** operand))
|
|
|
|
def bxl(self, operand: int) -> None:
|
|
self.ic += 2
|
|
self.register_b = self.register_b ^ operand
|
|
|
|
def bst(self, operand: int) -> None:
|
|
operand = self.interpret_combo_operand(operand)
|
|
self.ic += 2
|
|
self.register_b = operand % 8
|
|
|
|
def jnz(self, operand: int) -> None:
|
|
if self.register_a == 0:
|
|
self.ic += 2
|
|
return
|
|
self.ic = operand
|
|
|
|
def bxc(self, operand: int) -> None:
|
|
self.ic += 2
|
|
self.register_b = self.register_b ^ self.register_c
|
|
|
|
def out(self, operand: int) -> int:
|
|
operand = self.interpret_combo_operand(operand)
|
|
self.ic += 2
|
|
return operand % 8
|
|
|
|
def bdv(self, operand: int) -> None:
|
|
operand = self.interpret_combo_operand(operand)
|
|
self.ic += 2
|
|
self.register_b = int(self.register_a / (2 ** operand))
|
|
|
|
def cdv(self, operand: int) -> None:
|
|
operand = self.interpret_combo_operand(operand)
|
|
self.ic += 2
|
|
self.register_c = int(self.register_a / (2 ** operand))
|
|
|
|
def run(self) -> str:
|
|
output = []
|
|
while self.ic < len(self.program):
|
|
|
|
opcode = self.program[self.ic]
|
|
operand = self.program[self.ic + 1]
|
|
logging.debug(f"IC: {self.ic}, A: {self.register_a}, B: {self.register_b}, C: {self.register_c}, Opcode: {opcode}, Operand: {operand}")
|
|
|
|
opcode_out: None | int = None
|
|
match opcode:
|
|
case 0:
|
|
opcode_out = self.adv(operand)
|
|
case 1:
|
|
opcode_out = self.bxl(operand)
|
|
case 2:
|
|
opcode_out = self.bst(operand)
|
|
case 3:
|
|
self.jnz(operand)
|
|
case 4:
|
|
opcode_out = self.bxc(operand)
|
|
case 5:
|
|
opcode_out = self.out(operand)
|
|
case 6:
|
|
self.bdv(operand)
|
|
case 7:
|
|
self.cdv(operand)
|
|
|
|
if opcode_out is not None:
|
|
output.append(opcode_out)
|
|
|
|
return ','.join([str(x) for x in output])
|
|
|
|
def part1():
|
|
INPUT_FILE = "input.txt"
|
|
|
|
with open(INPUT_FILE, 'r') as file:
|
|
data = file.readlines()
|
|
|
|
reg_a = int(data[0].split(':')[1].strip())
|
|
reg_b = int(data[1].split(':')[1].strip())
|
|
reg_c = int(data[2].split(':')[1].strip())
|
|
|
|
program = data[4].split(':')[1].strip()
|
|
|
|
machine = Machine(program)
|
|
machine.register_a = reg_a
|
|
machine.register_b = reg_b
|
|
machine.register_c = reg_c
|
|
|
|
print(f'Output: {machine.run()}')
|
|
logging.debug(f'Final Registers: A={machine.register_a}, B={machine.register_b}, C={machine.register_c}')
|
|
|
|
def part2():
|
|
INPUT_FILE = "input.txt"
|
|
|
|
with open(INPUT_FILE, 'r') as file:
|
|
data = file.readlines()
|
|
|
|
reg_b = int(data[1].split(':')[1].strip())
|
|
reg_c = int(data[2].split(':')[1].strip())
|
|
|
|
program = data[4].split(':')[1].strip()
|
|
|
|
program_len: int = len(program.split(','))
|
|
|
|
reg_a = 8 ** program_len - 1
|
|
|
|
# machine = Machine(program)
|
|
# machine.register_a = reg_a
|
|
# machine.register_b = reg_b
|
|
# machine.register_c = reg_c
|
|
|
|
# print(machine.run())
|
|
# print(program)
|
|
|
|
# exit()
|
|
|
|
# Naive solution but it works (in time)
|
|
while True:
|
|
logging.info(f'Testing Reg A: {reg_a}')
|
|
machine = Machine(program)
|
|
machine.register_a = reg_a
|
|
machine.register_b = reg_b
|
|
machine.register_c = reg_c
|
|
|
|
output = machine.run()
|
|
logging.info(f'Output: {output}')
|
|
|
|
if output == program:
|
|
print(f'Correct Reg A: {reg_a}')
|
|
break
|
|
|
|
reg_a += 1
|
|
|
|
if __name__ == "__main__":
|
|
part2()
|