diff --git a/2019/aoc2019/day02.py b/2019/aoc2019/day02.py new file mode 100644 index 0000000..e1fa544 --- /dev/null +++ b/2019/aoc2019/day02.py @@ -0,0 +1,33 @@ +from typing import TextIO + +from aoc2019.intcode import read_program, Computer + + +def part1(data: TextIO) -> int: + program = read_program(data) + + program[1] = 12 + program[2] = 2 + + computer = Computer(program) + computer.run() + + return computer[0] + + +def part2(data: TextIO) -> int: + program = read_program(data) + + for verb in range(100): + for noun in range(100): + computer = Computer(program.copy()) + + computer[1] = noun + computer[2] = verb + + computer.run() + + if computer[0] == 19690720: + return 100 * noun + verb + + raise ValueError('Did not find valid combination') diff --git a/2019/aoc2019/intcode.py b/2019/aoc2019/intcode.py new file mode 100644 index 0000000..e92fcb2 --- /dev/null +++ b/2019/aoc2019/intcode.py @@ -0,0 +1,59 @@ +from typing import List, TextIO + + +def read_program(data: TextIO) -> List[int]: + line = next(data) + + return [int(i) for i in line.split(',')] + + +class Computer: + program: List[int] + pointer: int + + def __init__(self, program: List[int], pointer: int = 0) -> None: + self.program = program + self.pointer = pointer + + def __getitem__(self, item: int) -> int: + self._ensure_length(item + 1) + return self.program[item] + + def __setitem__(self, key: int, value: int) -> None: + self._ensure_length(key + 1) + self.program[key] = value + + def _ensure_length(self, length: int) -> None: + if len(self.program) < length: + # Double current program size with 0s + self.program.extend(0 for _ in range(len(self.program))) + + def run(self) -> None: + """ Run until failure""" + while self._execute_current(): + pass + + def _execute_current(self) -> bool: + """ + Execute a single instruction + :return: True if the program should continue + """ + pointer = self.pointer + opcode = self[pointer] + + if opcode == 1: + # Add + self[self[pointer + 3]] = self[self[pointer + 1]] + self[self[pointer + 2]] + self.pointer += 4 + elif opcode == 2: + # Multiply + self[self[pointer + 3]] = self[self[pointer + 1]] * self[self[pointer + 2]] + self.pointer += 4 + elif opcode == 99: + # Halt + return False + else: + raise ValueError(f'Unknown opcode {opcode} at {pointer}') + + return True + diff --git a/2019/tests/test_intcode.py b/2019/tests/test_intcode.py new file mode 100644 index 0000000..670b58c --- /dev/null +++ b/2019/tests/test_intcode.py @@ -0,0 +1,20 @@ +from typing import List + +import pytest + +from aoc2019.intcode import Computer + + +@pytest.mark.parametrize('program,expected', [ + ([1, 9, 10, 3, 2, 3, 11, 0, 99, 30, 40, 50], [3500, 9, 10, 70, 2, 3, 11, 0, 99, 30, 40, 50]), + ([1, 0, 0, 0, 99], [2, 0, 0, 0, 99]), + ([2, 3, 0, 3, 99], [2, 3, 0, 6, 99]), + ([2, 4, 4, 5, 99, 0], [2, 4, 4, 5, 99, 9801]), + ([1, 1, 1, 4, 99, 5, 6, 0, 99], [30, 1, 1, 4, 2, 5, 6, 0, 99]) +]) +def test_instructions_day2(program: List[int], expected: List[int]) -> None: + computer = Computer(program) + + computer.run() + + assert computer.program == expected