advent-of-code/2019-python/output/intcode_computer.py
Anders Englöf Ytterström f98545ae5a Solve 2019:11 "Space Police"
Bugs discovered in intcode:

- Relative base was reset on await input suspend.
- No handling for incorrect intcodes.
- State was sometimes corrupted when resumed after
  suspend.

Fixed and Patched in 0.3.2
2023-12-19 14:51:11 +01:00

265 lines
7.7 KiB
Python

from collections import defaultdict
"""
intcode computer, AoC 2019
Changelog
=========
0.3.2
-----
Patch release (day 9 part 1-2, day 11 part 1-2).
- Return relative base upon input suspension
- Improve intcode debugger
- Fix errorous state restoration upon resuming upon input
0.3.1
-----
Patch release (day 7 part 1-2).
- Supports relative parameter mode
0.3.0
-----
Minor release (day 7 part 1-2).
BREAKING CHANGE: execute() now returns 4 values.
- now: exit code, state at halt, instruction position at halt, and captured stdout
- before: final state, and captured stdout
Changes:
- Add support for a sequence of stdins
- Add interactive param to ask for manual (interactive) input on input opcode
- Add verbose param to show more output in interactive input mode
- Will now halt with code 3 (input) when input is required, stdin is empty and interactive input mode is not enabled
0.2.1
-----
Patch release (day 5 part 2).
- Add operation 5: set instruction pointer to value at parameter 2 position, based on value at parameter 1 position
- Add operation 6: set instruction pointer to value at parameter 2 position, based on value at parameter 1 position
- Add operation 7: compares values in parameter 1 position and parameter 2 position, stores at parameter 3 position
- Add operation 8: compares values in parameter 1 position and parameter 2 position, stores at parameter 3 position
0.2.0
-----
Minor release (day 5 part 1).
- Support immediate parameter mode
- Add stdin argument
- Make arguments optional: noun, verb
- Capture and return stdout
- Add operation 3: store stdin to parameter 1 position
- Add operation 4: output value at parameter 1 position to stdout
0.1.1
-----
Patch release (day 2 part 2).
- Remove initial modification 1=12, 2=2
- Add noun argument, stored at pos 1 (default value: 12)
- Add verb argument, stored at pos 2 (default value: 2)
0.1.0
-----
Initial version (day 2 part 1).
- Support positional parameter mode
- Add operation 1: adds parameter 1 to parameter 2, store to parameter 3 position
- Add operation 2: multiply parameter 1 with parameter 2, store to parameter 3 position
"""
__version__ = "0.3.2"
def parse(data):
return list(map(int, data.split(",")))
def execute(
program,
noun=None,
verb=None,
stdin=[],
debug=False,
interactive=False,
verbose=False,
n=0,
rb=0,
):
if verbose:
title = f"intcode computer, version {__version__}"
print("".join("=" for _ in title))
print(title)
print("".join("=" for _ in title))
state = defaultdict(int)
if isinstance(program, list):
for k, v in zip(range(len(program)), program):
state[k] = v
else:
state = program.copy()
if noun:
state[1] = noun
if verb:
state[2] = verb
stdout = []
def halt(code):
return code, state, n, rb, stdout
if debug and n > 0:
print(f"@{str(n).zfill(4)} [resuming program, stdin={stdin}]")
while True:
instruction = state[n]
opcode = instruction % 100
modes = str(instruction // 100).zfill(3)[::-1]
if opcode not in (1, 2, 3, 4, 5, 6, 7, 8, 9, 99):
print("opcode={opcode} not implemented, halting")
return halt(-2)
if opcode == 1:
a = state[n + 1]
b = state[n + 2]
c = n + 3
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
if debug:
print(f"@{str(n).zfill(4)} {opcode}_ADDITION | {x} + {y} to {p})")
state[p] = x + y
n += 4
if opcode == 2:
a = state[n + 1]
b = state[n + 2]
c = n + 3
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
if debug:
print(f"@{str(n).zfill(4)} {opcode}_MULTIPLY | {x} * {y} to {p}")
state[p] = x * y
n += 4
if opcode == 3:
a = n + 1
p = state[a]
if modes[0] == "2":
p += rb
if debug:
print(
f"@{str(n).zfill(4)} {opcode}_INPUT | target={p}, queued={stdin}, interactive={interactive}"
)
if stdin:
state[p] = stdin.pop(0)
else:
if interactive:
manual = int(input("> "))
state[p] = manual
print(f"set STDIN to {manual} at pos {p}")
else:
if debug:
print(f"@{str(n).zfill(4)} [suspended, awaiting input ...]")
return halt(3)
n += 2
if opcode == 4:
a = state[n + 1]
x = _values(state, modes, rb, a)
stdout.append(x)
if verbose:
print(x)
if debug:
print(f"@{str(n).zfill(4)} {opcode}_OUTPUT | echo {x}")
n += 2
if opcode == 5:
a = state[n + 1]
b = state[n + 2]
x, y = _values(state, modes, rb, a, b)
if x != 0:
if debug:
print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-1 | {x} != 0, n={y}")
n = y
else:
if debug:
print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-1 | {x} != 0, ignoring")
n += 3
if opcode == 6:
a = state[n + 1]
b = state[n + 2]
x, y = _values(state, modes, rb, a, b)
if x == 0:
if debug:
print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-0 | {x} == 0, n={y}")
n = y
else:
if debug:
print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-0 | {x} == 0, ignoring")
n += 3
if opcode == 7:
a = state[n + 1]
b = state[n + 2]
c = n + 3
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
if debug:
print(f"@{str(n).zfill(4)} {opcode}_LESSTHAN | {x} < {y} to {p}")
state[p] = int(x < y)
n += 4
if opcode == 8:
a = state[n + 1]
b = state[n + 2]
c = n + 3
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
if debug:
print(f"@{str(n).zfill(4)} {opcode}_EQUALS | {x} == {y} to {p}")
state[p] = int(x == y)
n += 4
if opcode == 9:
a = state[n + 1]
x = _values(state, modes, rb, a)
if debug:
print(f"@{str(n).zfill(4)} {opcode}_RELBASE | {rb} + {x}")
rb += x
n += 2
if opcode == 99:
if debug:
print(f"@{str(n).zfill(4)} {opcode}_HALT | n={n}")
return halt(99)
return halt(-1)
def _values(state, modes, rb, *parameters):
# for i, v in enumerate(parameters):
# if modes[i] == "0" and v < 0:
# print("================ ERROR =================")
# print("Negative index provided to position mode")
# if modes[i] == "2" and rb + v < 0:
# print("================ ERROR =================")
# print("Negative index provided to relative mode")
if len(parameters) > 1:
return [_value(state, modes, rb, k, v) for k, v in enumerate(parameters)]
return _value(state, modes, rb, 0, parameters[0])
def _value(state, modes, rb, m, s):
if modes[m] == "1":
return s
if modes[m] == "2":
return state[s + rb]
return state[s]