Files
adventofcode/2019/aoc2019/intcode.py
2021-02-01 22:39:09 +01:00

148 lines
4.4 KiB
Python

import collections
from typing import List, TextIO, Tuple, Union
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
relative_base: int
input: collections.deque[int]
output: collections.deque[int]
def __init__(self, program: List[int], pointer: int = 0) -> None:
self.program = program
self.pointer = pointer
self.relative_base = 0
self.input = collections.deque()
self.output = collections.deque()
def _mode_and_key(self, item: Union[int, Tuple[int, int]]) -> Tuple[int, int]:
if isinstance(item, int):
return 0, item
else:
mode, key = item
return mode, self.program[self.pointer + key]
def __getitem__(self, item: Union[int, Tuple[int, int]]) -> int:
mode, key = self._mode_and_key(item)
if mode == 1:
return key
elif mode == 0:
pass # Nothing to do here, handled below
elif mode == 2:
key += self.relative_base
else:
raise ValueError(f'Unsupported mode "{mode}"')
self._ensure_length(key + 1)
return self.program[key]
def __setitem__(self, item: Union[int, Tuple[int, int]], value: int) -> None:
mode, key = self._mode_and_key(item)
if mode == 1:
raise ValueError('Cannot assign to an immediate')
elif mode == 0:
pass # Nothing to do here, handled below
elif mode == 2:
key += self.relative_base
else:
raise ValueError(f'Unsupported mode "{mode}"')
self._ensure_length(key + 1)
self.program[key] = value
def _ensure_length(self, length: int) -> None:
if len(self.program) < length:
if 2 * len(self.program) >= length:
# Double current program size with 0s
self.program.extend(0 for _ in range(len(self.program)))
else:
# Resize until the desired length
self.program.extend(0 for _ in range(length - len(self.program)))
def run(self) -> None:
""" Run until failure """
while self._execute_current():
pass
def get_output(self) -> int:
return self.output.popleft()
def _execute_current(self) -> bool:
"""
Execute a single instruction
:return: True if the program should continue
"""
pointer = self.pointer
instruction = self[pointer]
opcode = instruction % 100
mode = [
(instruction // 100) % 10,
(instruction // 1000) % 10,
(instruction // 10000) % 10,
]
if opcode == 1:
# Add
self[mode[2], 3] = self[mode[0], 1] + self[mode[1], 2]
self.pointer += 4
elif opcode == 2:
# Multiply
self[mode[2], 3] = self[mode[0], 1] * self[mode[1], 2]
self.pointer += 4
elif opcode == 3:
# Input
self[mode[0], 1] = self.input.popleft()
self.pointer += 2
elif opcode == 4:
# Output
self.output.append(self[mode[0], 1])
self.pointer += 2
elif opcode == 5:
# Jump if true
if self[mode[0], 1] != 0:
self.pointer = self[mode[1], 2]
else:
self.pointer += 3
elif opcode == 6:
# Jump if false
if self[mode[0], 1] == 0:
self.pointer = self[mode[1], 2]
else:
self.pointer += 3
elif opcode == 7:
# Less than
if self[mode[0], 1] < self[mode[1], 2]:
self[mode[2], 3] = 1
else:
self[mode[2], 3] = 0
self.pointer += 4
elif opcode == 8:
# Equals
if self[mode[0], 1] == self[mode[1], 2]:
self[mode[2], 3] = 1
else:
self[mode[2], 3] = 0
self.pointer += 4
elif opcode == 9:
# Adjust relative base
self.relative_base += self[mode[0], 1]
self.pointer += 2
elif opcode == 99:
# Halt
return False
else:
raise ValueError(f'Unknown opcode {opcode} at {pointer}')
return True