Skip to content

Commit 327c606

Browse files
committed
2019 day 25 generic solution
1 parent 8f96d20 commit 327c606

File tree

4 files changed

+181
-120
lines changed

4 files changed

+181
-120
lines changed

2019/day25/day25.py

Lines changed: 170 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -4,133 +4,186 @@
44
import argparse
55
import re
66
import sys
7-
from functools import reduce
7+
from collections import defaultdict
88
from pathlib import Path
9+
import typing as t
910

1011
sys.path.append(Path(__file__).parent.parent.as_posix())
1112
from intcode.Intcode import Computer # noqa
1213

13-
parser = argparse.ArgumentParser()
14-
parser.add_argument("-v", "--verbose", action="store_true")
15-
parser.add_argument("-m", "--manual", action="store_true", help="play the game")
16-
parser.add_argument("input", nargs="?", default="input.txt")
17-
args = parser.parse_args()
18-
19-
software = Path(args.input).read_text()
20-
21-
22-
computer = Computer()
23-
computer.load(software)
24-
computer.start()
25-
26-
27-
if not args.manual:
28-
if reduce(lambda a, b: a ^ b, computer.program) != -2251798974787211:
29-
print("work only for my puzzle input", file=sys.stderr)
30-
exit(2)
31-
32-
# explore the spacecraft and take items - works only for my puzzle input
33-
explore_cmds = (
34-
"east,east,take semiconductor,north,take planetoid,west,take food ration,west,west,"
35-
+ "take monolith,east,east,north,take space law space brochure,north,north,"
36-
+ "take weather machine,south,south,south,east,north,take antenna,east,north,"
37-
+ "south,north,west,east,south,south,east,north,south,west,east,west,south,east,"
38-
+ "south,south,south,east,inv,west,west,west,north,north,west,north,north,south,"
39-
+ "west,north,east,take jam,west,south,east,south,east,south,south,east,inv"
40-
).split(",")
41-
42-
# try all combinations of items - works only for my puzzle input
43-
solve_cmds = []
44-
items = "food ration,weather machine,antenna,space law space brochure,jam,semiconductor,planetoid,monolith".split(
45-
","
46-
)
47-
for k in range(8):
48-
solve_cmds.append("drop " + items[k])
49-
50-
previous = 0
51-
for i in range(1, 256):
52-
for k in range(8):
53-
if i & (1 << k) != 0:
54-
if previous & (1 << k) == 0:
55-
solve_cmds.append("take " + items[k])
56-
if i & (1 << k) == 0:
57-
if (previous & (1 << k)) != 0:
58-
solve_cmds.append("drop " + items[k])
59-
solve_cmds.append("inv")
60-
solve_cmds.append("east")
61-
previous = i
62-
63-
# let's go
64-
for cmd in explore_cmds + solve_cmds:
65-
if args.verbose:
66-
print(f"> {cmd}")
67-
68-
computer.input.extend(map(ord, cmd))
14+
15+
def run(computer, command=None):
16+
17+
computer.flush_io()
18+
19+
if command:
20+
computer.input.extend(map(ord, command))
6921
computer.input.append(10)
70-
state = computer.resume()
71-
72-
t = "".join(map(chr, computer.output))
73-
if args.verbose:
74-
print(f"\033[2m{t}\033[0m")
75-
76-
computer.flush_io()
77-
78-
if state != "read":
79-
answer = re.search(
80-
r"Oh, hello! You should be able to get in by typing (\d+) on the keypad at the main airlock.", t
81-
)
82-
if answer:
83-
answer = answer[1]
84-
break
85-
86-
print(answer)
87-
88-
else:
89-
shortcuts = {
90-
"n": "north",
91-
"s": "south",
92-
"e": "east",
93-
"w": "west",
94-
}
95-
96-
log = Path("moves.log").open("w")
97-
98-
state = computer.run()
99-
while state == "read":
100-
t = "".join(map(chr, computer.output))
101-
print(f"\033[2m{t}\033[0m")
102-
computer.flush_io()
103-
104-
# 't' to take the current item
105-
w = ""
106-
take = ""
107-
for line in t.splitlines():
108-
if line.startswith("Items here:"):
109-
w = "items"
110-
elif line.startswith("- ") and w == "items":
111-
take = "take " + line[2:]
112-
break
113-
else:
114-
w = ""
11522

116-
while True:
117-
value = input("input> ")
118-
if value.strip() == "":
119-
continue
23+
computer.resume()
24+
25+
return "".join(map(chr, computer.output))
26+
27+
28+
def parse(out: str) -> t.Tuple[str, t.List, t.List]:
12029

121-
if take and value == "t":
122-
value = take
30+
room = None
31+
dirs = []
32+
items = []
33+
34+
for line in out.splitlines():
35+
if line.startswith("== ") and line.endswith(" =="):
36+
if not room:
37+
room = line[3:-3]
38+
if line.startswith("- "):
39+
line = line.removeprefix("- ")
40+
if line in ("north", "east", "south", "west"):
41+
dirs.append(line)
12342
else:
124-
value = shortcuts.get(value, value)
43+
items.append(line)
44+
45+
return room, dirs, items
46+
47+
48+
def reverse(dir: str) -> str:
49+
match dir:
50+
case "north":
51+
return "south"
52+
case "south":
53+
return "north"
54+
case "east":
55+
return "west"
56+
case "west":
57+
return "east"
58+
59+
raise ValueError(dir)
60+
61+
62+
def explore(computer, map, out):
63+
64+
room, dirs, items = parse(out)
65+
66+
for dir in dirs:
67+
if dir in map[room]:
68+
continue
69+
70+
# go next room
71+
out = run(computer, dir)
72+
73+
newroom, _, _ = parse(out)
74+
known = newroom in map
75+
76+
map[room][dir] = newroom
77+
map[newroom][reverse(dir)] = room
78+
79+
if "A loud" in out:
80+
continue
81+
82+
if not known:
83+
explore(computer, map, out)
84+
85+
# go back
86+
out = run(computer, reverse(dir))
87+
88+
for item in items:
89+
90+
cmd = f"take {item}"
91+
92+
temp_computer = computer.clone()
93+
temp_computer.max_iterations = 10000 # prevent infinite loop
94+
run(temp_computer, cmd)
95+
96+
if temp_computer._state == "read":
97+
out = run(temp_computer, "inv")
98+
if "Items in your inventory" in out:
99+
run(computer, cmd)
100+
101+
102+
def find_path(map, current, target, seen=set()) -> t.List[str]:
103+
104+
seen.add(current)
105+
if current == target:
106+
return []
107+
108+
for dir, cell in map[current].items():
109+
if cell not in seen:
110+
if (path := find_path(map, cell, target, seen)) is not None:
111+
return [dir] + path
112+
113+
114+
def find_weight(computer, inventory, checkpoint_dir) -> str:
115+
116+
prev_code = 0
117+
for cod in range(1 << len(inventory)):
118+
119+
gray_code = cod ^ (cod >> 1)
120+
121+
diff = gray_code - prev_code
122+
123+
if diff != 0:
124+
cmd = "drop" if diff > 0 else "take"
125+
diff = abs(diff)
126+
item = 0
127+
while diff & 1 == 0:
128+
diff >>= 1
129+
item += 1
130+
run(computer, f"{cmd} {inventory[item]}")
131+
132+
out = run(computer, checkpoint_dir)
133+
answer = re.search(r"You should be able to get in by typing (\d+) on the keypad at the main airlock.", out)
134+
if answer:
135+
return answer[1]
136+
137+
prev_code = gray_code
138+
139+
140+
def solve(software):
141+
142+
computer = Computer()
143+
computer.load(software)
144+
computer.start()
145+
146+
map = defaultdict(dict)
147+
148+
# start up
149+
out = run(computer)
150+
start_room, _, _ = parse(out)
151+
152+
# visit all rooms and take items
153+
explore(computer, map, out)
154+
155+
# find path to the detection room
156+
path = find_path(map, start_room, "Pressure-Sensitive Floor")
157+
158+
# the diretion to get to Security Checkpoint from Pressure-Sensitive Floor
159+
checkpoint_dir = [k for k, v in map["Security Checkpoint"].items() if v == "Pressure-Sensitive Floor"][0]
160+
161+
# go to the pre
162+
for step in path:
163+
run(computer, step)
164+
165+
# the inventory
166+
out = run(computer, "inv")
167+
inventory = [s.removeprefix("- ") for s in out.splitlines() if s.startswith("- ")]
168+
169+
# get the unlock code
170+
unlock = find_weight(computer, inventory, checkpoint_dir)
171+
172+
print(unlock)
173+
174+
175+
def main():
176+
177+
parser = argparse.ArgumentParser()
178+
parser.add_argument("-v", "--verbose", action="store_true")
179+
parser.add_argument("-m", "--manual", action="store_true", help="play the game")
180+
parser.add_argument("input", nargs="?", default="input.txt")
181+
args = parser.parse_args()
125182

126-
print(value, file=log)
127-
log.flush()
183+
software = Path(args.input).read_text()
128184

129-
computer.input.extend(map(ord, value))
130-
computer.input.append(10)
131-
break
185+
solve(software)
132186

133-
state = computer.resume()
134187

135-
t = "".join(map(chr, computer.output))
136-
print(f"\033[2m{t}\033[0m")
188+
if __name__ == "__main__":
189+
main()

2019/day25/play.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
parser = argparse.ArgumentParser()
1414
parser.add_argument("-v", "--verbose", action="store_true")
15-
parser.add_argument("-m", "--manual", action="store_true", help="play the game")
15+
parser.add_argument("-s", "--solve", action="store_true", help="solve my input")
1616
parser.add_argument("input", nargs="?", default="input.txt")
1717
args = parser.parse_args()
1818

@@ -24,7 +24,7 @@
2424
computer.start()
2525

2626

27-
if not args.manual:
27+
if args.solve:
2828
if reduce(lambda a, b: a ^ b, computer.program) != -2251798974787211:
2929
print("work only for my puzzle input", file=sys.stderr)
3030
exit(2)

2019/intcode/Intcode.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
class Computer:
3333
def __init__(self):
34+
self.max_iterations = 0
3435
self.load("99")
3536

3637
def load(self, data):
@@ -150,6 +151,7 @@ def resume(self):
150151

151152
def clone(self):
152153
clone = Computer()
154+
clone.max_iterations = self.max_iterations
153155
clone.program = self.program
154156
clone._debug = self._debug
155157
clone._ip = self._ip
@@ -183,7 +185,14 @@ def _run(self):
183185
assert self._state in ["start", "pause", "yield", "read"]
184186
self._state = ""
185187

188+
iterations = 0
189+
186190
while ip < len(self._text):
191+
192+
iterations += 1
193+
if self.max_iterations != 0 and iterations > self.max_iterations:
194+
return ip, f"infinite loop detected at ip {ip}"
195+
187196
if self._debug:
188197
print()
189198
print(self.disasm(debugger=ip))

scripts/runall.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,6 @@ def main():
559559
args.exclude.extend(
560560
" -x 2016:5 -x 2016:11 -x 2016:14 -x 2016:23"
561561
" -x 2018:21 -x 2018:23"
562-
" -x 2019:25" # no generic solution yet
563562
" -x 2020:15"
564563
" -x 2021:18"
565564
" -x 2022:15"

0 commit comments

Comments
 (0)