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
This commit is contained in:
Anders Englöf Ytterström 2023-11-28 14:01:49 +01:00
parent 262ad34c51
commit f98545ae5a
6 changed files with 168 additions and 74 deletions

View file

@ -33,6 +33,7 @@ def answer(part_index, fmt_string):
else:
formatted = fmt_string.format(answer)
print(f"[part {part_index}] {formatted}")
return answer
return wrapper_aoc

View file

@ -13,13 +13,13 @@ def parse_input(data):
@answer(1, "[intcode-0.2.0] Program diagnostic code, ID 1: {}")
def part_1(program):
_code, _state, _cursorpos, stdout = execute(program, stdin=1)
_code, _state, _cursorpos, rb, stdout = execute(program, stdin=[1])
return max(stdout)
@answer(2, "[intcode-0.2.1] Program diagnostic code, ID 5: {}")
def part_2(program):
_code, _state, _cursorpos, stdout = execute(program, stdin=5)
_code, _state, _cursorpos, rb, stdout = execute(program, stdin=[5])
return stdout[0]

View file

@ -22,7 +22,7 @@ def part_1(program):
for settings in map(list, permutations(range(5))):
o = 0
for ps in settings:
_code, _state, _n, so = execute(program, stdin=[ps, o])
_code, _state, _n, _rb, so = execute(program, stdin=[ps, o])
o = so.pop(0)
thruster_signals.append(o)
return max(thruster_signals)
@ -43,9 +43,9 @@ def part_2(program):
if paused[amp]:
program, resume_at = paused[amp]
del paused[amp]
code, state, n, so = execute(program, stdin=o, n=resume_at)
code, state, n, _rb, so = execute(program, stdin=o, n=resume_at)
else:
code, state, n, so = execute(program, stdin=[ps, *o])
code, state, n, _rb, so = execute(program, stdin=[ps, *o])
if code == 3:
paused[amp] = (
list(state.values()),

View file

@ -12,13 +12,13 @@ def parse_input(data):
@answer(1, "[intcode 0.3.1] BOOST keycode: {}")
def part_1(program):
_c, _s, _n, outputs = execute(program, stdin=[1])
_c, _s, _n, _rb, outputs = execute(program, stdin=[1])
return outputs.pop(0)
@answer(2, "[intcode 0.3.1] Distress signal coordinates: {}")
def part_2(program):
_c, _s, _n, outputs = execute(program, stdin=[2])
_c, _s, _n, _rb, outputs = execute(program, stdin=[2])
return outputs.pop(0)
@ -42,7 +42,7 @@ if __name__ == "__main__":
0,
99,
]
)[3] == [
)[4] == [
109,
1,
204,
@ -60,20 +60,20 @@ if __name__ == "__main__":
0,
99,
]
assert len(str(execute([1102, 34915192, 34915192, 7, 4, 7, 99, 0])[3][0])) == 16
assert 1125899906842624 in execute([104, 1125899906842624, 99])[3]
assert execute([109, -1, 4, 1, 99])[3][0] == -1
assert execute([109, -1, 104, 1, 99])[3][0] == 1
assert execute([109, -1, 204, 1, 99])[3][0] == 109
assert execute([109, 1, 9, 2, 204, -6, 99])[3][0] == 204
assert execute([109, 1, 109, 9, 204, -6, 99])[3][0] == 204
assert execute([109, 1, 209, -1, 204, -106, 99])[3][0] == 204
assert execute([109, 1, 3, 3, 204, 2, 99], stdin=[666])[3][0] == 666
assert execute([109, 1, 203, 2, 204, 2, 99], stdin=[666])[3][0] == 666
assert execute([109, 6, 21001, 9, 25, 1, 104, 0, 99, 49])[3][0] == 74
assert len(str(execute([1102, 34915192, 34915192, 7, 4, 7, 99, 0])[4][0])) == 16
assert 1125899906842624 in execute([104, 1125899906842624, 99])[4]
assert execute([109, -1, 4, 1, 99])[4][0] == -1
assert execute([109, -1, 104, 1, 99])[4][0] == 1
assert execute([109, -1, 204, 1, 99])[4][0] == 109
assert execute([109, 1, 9, 2, 204, -6, 99])[4][0] == 204
assert execute([109, 1, 109, 9, 204, -6, 99])[4][0] == 204
assert execute([109, 1, 209, -1, 204, -106, 99])[4][0] == 204
assert execute([109, 1, 3, 3, 204, 2, 99], stdin=[666])[4][0] == 666
assert execute([109, 1, 203, 2, 204, 2, 99], stdin=[666])[4][0] == 666
assert execute([109, 6, 21001, 9, 25, 1, 104, 0, 99, 49])[4][0] == 74
parsed = parse_input()
assert execute(parsed, stdin=[1])[3][0] == 2351176124
assert execute(parsed, stdin=[1])[4][0] == 2351176124
part_1(parsed)
part_2(parsed)

View file

@ -0,0 +1,82 @@
from collections import defaultdict
from output.intcode_computer import execute, parse
from output import answer, puzzleinput
n = 11
title = "Space Police"
@puzzleinput(n)
def parse_input(data):
return parse(data)
DIRS = [
(-1, 0),
(0, -1),
(1, 0),
(0, 1),
]
COLORS = [".", "#"]
CL = ["black", "white"]
DL = ["UP", "LEFT", "BOTTOM", "RIGHT"]
TL = ["RIGHT", "LEFT"]
@answer(1, "[intcode 0.3.2] Robot paints {} panes at least once")
def part_1(program):
path, pos, d = _paint(program)
return len(path)
@answer(
2,
'[intcode 0.3.2] The hull has registration identifier "JZPJRAGJ" freshly painted, see below: \n\n{}',
)
def part_2(program):
path, pos, d = _paint(program, 1)
return _inspect(path.copy(), pos, d)
def _paint(program, initial=0):
pos = (0, 0)
d = 0
path = defaultdict(int)
path[pos] = initial
n = 0
rb = 0
code = 0
while True:
code, program, n, rb, outputs = execute(program, n=n, rb=rb, stdin=[path[pos]])
if code == 99:
break
if outputs:
paint, turn_to = outputs
path[pos] = paint
d = (d - 1 if turn_to == 1 else d + 1) % 4
pos = (pos[0] + DIRS[d][0], pos[1] + DIRS[d][1])
return path, pos, d
def _inspect(path, p, d):
pk = path.keys()
startx = min(map(lambda yx: yx[1], pk)) - 1
endx = max(map(lambda yx: yx[1], pk)) + 2
starty = min(map(lambda yx: yx[0], pk)) - 1
endy = max(map(lambda yx: yx[0], pk)) + 2
matrix = [
[COLORS[path[(y, x)]] for x in range(startx, endx)] for y in range(starty, endy)
]
y, x = p
matrix[abs(starty) + y][abs(startx) + x] = "^<v>"[d]
return "\n".join(["".join(line) for line in matrix])
if __name__ == "__main__":
parsed = parse_input()
part_1(parsed)
part_2(parsed) # JZPJRAGJ

View file

@ -7,6 +7,15 @@ 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
-----
@ -71,7 +80,7 @@ Initial version (day 2 part 1).
- 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.1"
__version__ = "0.3.2"
def parse(data):
@ -87,61 +96,42 @@ def execute(
interactive=False,
verbose=False,
n=0,
rb=0,
):
if verbose:
title = f"intcode computer, version {v}"
title = f"intcode computer, version {__version__}"
print("".join("=" for _ in title))
print(title)
print("".join("=" for _ in title))
state = defaultdict(int)
for k, v in zip(range(len(program)), program):
state[k] = v
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
c = 0
rb = 0
stdout = []
if not isinstance(stdin, list):
stdin = [stdin]
def halt(code):
return code, state, n, stdout
def values(modes, *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")
def value(i, v):
if modes[i] == "1":
return v
if modes[i] == "2":
return state[v + rb]
return state[v]
if len(parameters) > 1:
return [value(i, v) for i, v in enumerate(parameters)]
return value(0, parameters[0])
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]
# if instruction > 200 and instruction < 1000:
# print("")
# spn = 2 if instruction % 100 % 3 == 0 else 4
# print(list(state.values())[n : n + spn])
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(modes, a, b)
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
@ -153,7 +143,7 @@ def execute(
a = state[n + 1]
b = state[n + 2]
c = n + 3
x, y = values(modes, a, b)
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
@ -174,15 +164,17 @@ def execute(
state[p] = stdin.pop(0)
else:
if interactive:
state[p] = int(input("> "))
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]")
print(f"@{str(n).zfill(4)} [suspended, awaiting input ...]")
return halt(3)
n += 2
if opcode == 4:
a = state[n + 1]
x = values(modes, a)
x = _values(state, modes, rb, a)
stdout.append(x)
if verbose:
print(x)
@ -192,7 +184,7 @@ def execute(
if opcode == 5:
a = state[n + 1]
b = state[n + 2]
x, y = values(modes, a, b)
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}")
@ -204,18 +196,20 @@ def execute(
if opcode == 6:
a = state[n + 1]
b = state[n + 2]
x, y = values(modes, a, b)
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 debug:
print(f"{n}:{opcode} | {n}")
if opcode == 7:
a = state[n + 1]
b = state[n + 2]
c = n + 3
x, y = values(modes, a, b)
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
@ -227,7 +221,7 @@ def execute(
a = state[n + 1]
b = state[n + 2]
c = n + 3
x, y = values(modes, a, b)
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
@ -237,18 +231,35 @@ def execute(
n += 4
if opcode == 9:
a = state[n + 1]
x = values(modes, a)
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:
break
c += 1
if debug and c % 1000 == 0:
print(f"{c} instructions done, current pos: {n}")
if c == 33:
break
if verbose:
title = f"intcode computer received SIGTERM"
return halt(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]