diff --git a/harald/api.py b/harald/api.py index 5316603..b60dd3d 100644 --- a/harald/api.py +++ b/harald/api.py @@ -21,7 +21,9 @@ class APIError(Exception): def select(problem: str): - response = requests.post(config.contest_url + "/select", json={"problemName": problem, "id": config.id}) + response = requests.post( + config.contest_url + "/select", json={"problemName": problem, "id": config.id} + ) if not response.ok: raise APIError(f"{response.status_code}: {response.text}") @@ -43,7 +45,6 @@ def init_explore_cache(): def write_explore_cache(): - return with open("explore_cache.json", "w") as h: h.write(json.dumps(explore_cache)) diff --git a/harald/explore.py b/harald/explore.py index 71d9874..071eca8 100755 --- a/harald/explore.py +++ b/harald/explore.py @@ -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}") diff --git a/harald/explore.sh b/harald/explore.sh index aaf2615..dc34e1b 100755 --- a/harald/explore.sh +++ b/harald/explore.sh @@ -6,13 +6,21 @@ if [ -z "$PROBLEMS" ] ; then PROBLEMS=$(jq -r '.[]|.problem' ../problems.json) fi +if [ $TRIES -eq 0 ]; then + FOREVER=1 + TRIES=1 +fi + TRIES=${TRIES:-10} -for problem in $PROBLEMS; do - for t in $(seq $TRIES); do - ./explore.py $problem - if [ $? -eq 0 ]; then - break - fi - done +while : ; do + for problem in $PROBLEMS; do + for t in $(seq $TRIES); do + ./explore.py $problem + if [ $? -eq 0 ]; then + break + fi + done + done + [ -n "$FOREVER" ] || break done diff --git a/harald/graph.py b/harald/graph.py index 1ed117e..84d68de 100644 --- a/harald/graph.py +++ b/harald/graph.py @@ -12,7 +12,9 @@ class Aedificium: def add_edge(self, scr, src_d, trg, trg_d): self.graph.add_edge( - pydot.Edge(scr, trg) # , headport=self.PORTS[src_d], tailport=self.PORTS[trg_d]) + pydot.Edge( + scr, trg + ) # , headport=self.PORTS[src_d], tailport=self.PORTS[trg_d]) ) def render(self): diff --git a/harald/path.py b/harald/path.py new file mode 100644 index 0000000..d4e9f4e --- /dev/null +++ b/harald/path.py @@ -0,0 +1,48 @@ +import functools + + +@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)]