|
|
|
|
@ -1,68 +1,20 @@
|
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
|
|
import itertools
|
|
|
|
|
import functools
|
|
|
|
|
import json
|
|
|
|
|
import os.path
|
|
|
|
|
import sys
|
|
|
|
|
import random
|
|
|
|
|
from collections import defaultdict
|
|
|
|
|
|
|
|
|
|
import api
|
|
|
|
|
import graph
|
|
|
|
|
from path import DOORS, Path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExploreError(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@functools.total_ordering
|
|
|
|
|
class Path:
|
|
|
|
|
def __init__(self, path=""):
|
|
|
|
|
if isinstance(path, list):
|
|
|
|
|
path = "".join(str(p) for p in path)
|
|
|
|
|
self.path = path
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
return "." + self.path if self.path else "."
|
|
|
|
|
|
|
|
|
|
def __bool__(self):
|
|
|
|
|
return bool(self.path)
|
|
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
|
return self.path == other.path
|
|
|
|
|
|
|
|
|
|
def __lt__(self, other):
|
|
|
|
|
return (len(self.path), self.path) < (len(other.path), other.path)
|
|
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
|
return self.path.__hash__()
|
|
|
|
|
|
|
|
|
|
def __format__(self, spec):
|
|
|
|
|
return str(self).__format__(spec)
|
|
|
|
|
|
|
|
|
|
def __add__(self, other):
|
|
|
|
|
return Path(self.path + other.path)
|
|
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
|
return len(self.path)
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, idx):
|
|
|
|
|
return Path(self.path.__getitem__(idx))
|
|
|
|
|
|
|
|
|
|
def last(self):
|
|
|
|
|
return Path(self.path[:-1]), int(self.path[-1])
|
|
|
|
|
|
|
|
|
|
def shorten(self, path, pmerge):
|
|
|
|
|
if self.path.startswith(pmerge.path):
|
|
|
|
|
return Path(path.path + self.path[len(pmerge.path) :])
|
|
|
|
|
else:
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DOORS = [Path(str(i)) for i in range(6)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Explore:
|
|
|
|
|
def __init__(self, problem, probes):
|
|
|
|
|
self.problem = problem
|
|
|
|
|
@ -104,7 +56,7 @@ class Explore:
|
|
|
|
|
|
|
|
|
|
for path, presults in zip(paths, itertools.batched(results, len(probes))):
|
|
|
|
|
prefix_len = len(mark) - 2 * num_marks + len(path)
|
|
|
|
|
print("id", path, results)
|
|
|
|
|
# print("id", path, results)
|
|
|
|
|
self.update(path, [res[prefix_len:] for res in presults])
|
|
|
|
|
|
|
|
|
|
def _add_room(self, path, results):
|
|
|
|
|
@ -143,7 +95,9 @@ class Explore:
|
|
|
|
|
# self.dump()
|
|
|
|
|
|
|
|
|
|
p, rid = self.neighbors[pl][d]
|
|
|
|
|
assert rid is None or rid == room_id, f"penultimate {path} {pl}: {rid} != {room_id}"
|
|
|
|
|
assert (
|
|
|
|
|
rid is None or rid == room_id
|
|
|
|
|
), f"penultimate {path} {pl}: {rid} != {room_id}"
|
|
|
|
|
self.neighbors[pl][d] = (p, room_id)
|
|
|
|
|
|
|
|
|
|
# self.dump()
|
|
|
|
|
@ -177,7 +131,7 @@ class Explore:
|
|
|
|
|
try to unify rooms at paths p1, p2
|
|
|
|
|
return unified rooms
|
|
|
|
|
"""
|
|
|
|
|
print("unify", path1, path2)
|
|
|
|
|
# print("unify", path1, path2)
|
|
|
|
|
if path1 == path2:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
@ -185,7 +139,9 @@ class Explore:
|
|
|
|
|
assert path2 in self.rooms, f"room '{path2}' not explored"
|
|
|
|
|
|
|
|
|
|
if self.rooms[path1] != self.rooms[path2]:
|
|
|
|
|
raise ExploreError(f"ids of '{path1}'({self.rooms[path1]}) and '{path2}'({self.rooms[path2]}) do not match")
|
|
|
|
|
raise ExploreError(
|
|
|
|
|
f"ids of '{path1}'({self.rooms[path1]}) and '{path2}'({self.rooms[path2]}) do not match"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
path = min(path1, path2)
|
|
|
|
|
pmerge = max(path1, path2)
|
|
|
|
|
@ -194,9 +150,13 @@ class Explore:
|
|
|
|
|
self.unification_id[pmerge] = path
|
|
|
|
|
|
|
|
|
|
merged_neighbors = []
|
|
|
|
|
for n, ((p, rid), (pm, rmid)) in enumerate(zip(self.neighbors[path], self.neighbors[pmerge])):
|
|
|
|
|
for n, ((p, rid), (pm, rmid)) in enumerate(
|
|
|
|
|
zip(self.neighbors[path], self.neighbors[pmerge])
|
|
|
|
|
):
|
|
|
|
|
if rid and rmid and rid != rmid:
|
|
|
|
|
raise ExploreError(f"neighbor {n} of '{path}'({rid}) and '{pmerge}'({rmid}) do not match")
|
|
|
|
|
raise ExploreError(
|
|
|
|
|
f"neighbor {n} of '{path}'({rid}) and '{pmerge}'({rmid}) do not match"
|
|
|
|
|
)
|
|
|
|
|
merged_neighbors.append((self._path(p), rid or rmid))
|
|
|
|
|
|
|
|
|
|
# fix rooms
|
|
|
|
|
@ -224,7 +184,9 @@ class Explore:
|
|
|
|
|
new.append((np_, rid))
|
|
|
|
|
if rid:
|
|
|
|
|
try:
|
|
|
|
|
assert np_ in self.rooms, f"unify: path {np} {np_} of {(p, ns)} not in rooms"
|
|
|
|
|
assert (
|
|
|
|
|
np_ in self.rooms
|
|
|
|
|
), f"unify: path {np} {np_} of {(p, ns)} not in rooms"
|
|
|
|
|
except AssertionError as exc:
|
|
|
|
|
self.dump()
|
|
|
|
|
raise exc
|
|
|
|
|
@ -253,7 +215,9 @@ class Explore:
|
|
|
|
|
yield unexplored
|
|
|
|
|
|
|
|
|
|
def is_explored(self):
|
|
|
|
|
return next(self.unexplored(), None) is None and all(len(p) == 1 for p in self.room_ids.values())
|
|
|
|
|
return next(self.unexplored(), None) is None and all(
|
|
|
|
|
len(p) == 1 for p in self.room_ids.values()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def layout(self, orig=None, start=Path()):
|
|
|
|
|
aedi = graph.Aedificium(self.problem)
|
|
|
|
|
@ -271,12 +235,18 @@ class Explore:
|
|
|
|
|
for src_door, (trg_path, _) in enumerate(ns):
|
|
|
|
|
src = (src_path, src_door)
|
|
|
|
|
trg_door = next(
|
|
|
|
|
(j for j, (p, rid) in enumerate(self.neighbors[trg_path]) if p == src_path),
|
|
|
|
|
(
|
|
|
|
|
j
|
|
|
|
|
for j, (p, rid) in enumerate(self.neighbors[trg_path])
|
|
|
|
|
if p == src_path
|
|
|
|
|
),
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
if trg_door is None:
|
|
|
|
|
# raise Exception(f"backlink not found: {(src, trg_path, self.neighbors[trg_path])}")
|
|
|
|
|
print(f"backlink not found: {(src, trg_path, self.neighbors[trg_path])}")
|
|
|
|
|
print(
|
|
|
|
|
f"backlink not found: {(src, trg_path, self.neighbors[trg_path])}"
|
|
|
|
|
)
|
|
|
|
|
connect_errors.append((src[0], src[1], trg_path))
|
|
|
|
|
trg_door = 0
|
|
|
|
|
trg = (trg_path, trg_door)
|
|
|
|
|
@ -295,8 +265,14 @@ class Explore:
|
|
|
|
|
if connect_errors:
|
|
|
|
|
# try to fix connection issues
|
|
|
|
|
updated = False
|
|
|
|
|
src = {src_path: (trg_path, src_door) for src_path, src_door, trg_path in connect_errors}
|
|
|
|
|
trg = {trg_path: (src_path, src_door) for src_path, src_door, trg_path in connect_errors}
|
|
|
|
|
src = {
|
|
|
|
|
src_path: (trg_path, src_door)
|
|
|
|
|
for src_path, src_door, trg_path in connect_errors
|
|
|
|
|
}
|
|
|
|
|
trg = {
|
|
|
|
|
trg_path: (src_path, src_door)
|
|
|
|
|
for src_path, src_door, trg_path in connect_errors
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for trg_path, (src_path, src_door) in trg.items():
|
|
|
|
|
if trg_path in src:
|
|
|
|
|
@ -368,7 +344,9 @@ def mark_solve(problem, nrooms):
|
|
|
|
|
mark_loop = mark_loop + p + get_mark(rid, mask) + q
|
|
|
|
|
|
|
|
|
|
print("marked", mark_loop, loop)
|
|
|
|
|
assert exs[-1].walk(loop) == Path(), f"loop doesn't close {exs[-1].walk(loop)}"
|
|
|
|
|
assert (
|
|
|
|
|
exs[-1].walk(loop) == Path()
|
|
|
|
|
), f"loop doesn't close {exs[-1].walk(loop)}"
|
|
|
|
|
|
|
|
|
|
ex = Explore(problem, probes)
|
|
|
|
|
exs.append(ex)
|
|
|
|
|
@ -391,7 +369,9 @@ def mark_solve(problem, nrooms):
|
|
|
|
|
|
|
|
|
|
assert ex.is_explored(), "not fully explored"
|
|
|
|
|
if nrooms % len(ex.rooms) != 0:
|
|
|
|
|
raise ExploreError(f"not all rooms could be identifed {len(ex.rooms)}/{nrooms}")
|
|
|
|
|
raise ExploreError(
|
|
|
|
|
f"not all rooms could be identifed {len(ex.rooms)}/{nrooms}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if len(ex.rooms) == nrooms:
|
|
|
|
|
print("found all rooms")
|
|
|
|
|
@ -445,7 +425,9 @@ if __name__ == "__main__":
|
|
|
|
|
with open(os.path.join("..", "problems.json")) as h:
|
|
|
|
|
problems = json.loads(h.read())
|
|
|
|
|
|
|
|
|
|
problems = {p["problem"]: {"size": p["size"], "idx": i} for i, p in enumerate(problems)}
|
|
|
|
|
problems = {
|
|
|
|
|
p["problem"]: {"size": p["size"], "idx": i} for i, p in enumerate(problems)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if problem not in problems:
|
|
|
|
|
raise ExploreError(f"unknown problem {problem}")
|
|
|
|
|
|