From: Jakob Cornell Date: Sat, 22 Jun 2019 02:32:04 +0000 (-0500) Subject: Various X-Git-Url: https://jcornell.net/gitweb/gitweb.cgi?a=commitdiff_plain;h=eaaeeddb5863b98aae80873a9d4f646fcd0167a5;p=zoombinis-solve.git Various --- diff --git a/common.py b/common.py index 13f8afb..ce0cf44 100644 --- a/common.py +++ b/common.py @@ -13,20 +13,31 @@ class Zoombini: class Feet(Property): SHOES = 'shoes'; PROPELLER = 'propeller'; SPRING = 'spring'; WHEELS = 'wheels'; SKATES = 'skates' + PROPERTIES = frozenset([Hair, Eyes, Nose, Feet]) + def __init__(self, attrs): assert ( all(isinstance(a, Zoombini.Property) for a in attrs) and len({type(a) for a in attrs}) == 4 ) - self.attrs = {type(attr): attr for attr in attrs} - - def has(self, attr): - return self.attrs[type(attr)] == attr + self.by_prop = {type(attr): attr for attr in attrs} + self.attrs = frozenset(attrs) def __str__(self): return str({type(a).__name__: a.value for a in self.attrs.values()}) +ALL_ATTRS = frozenset(attr for prop in Zoombini.PROPERTIES for attr in prop) + def load(path): import pickle - with path.open() as f: + with path.open('rb') as f: return pickle.load(f) + +def save(troupe, path): + import pickle + with path.open('wb') as f: + pickle.dump(troupe, f) + +def random_zoombini(): + import random + return Zoombini({random.choice(list(t)) for t in ALL_ATTRS}) diff --git a/puzzles/allergic_cliffs/4.py b/puzzles/allergic_cliffs/4.py index 2650c16..80de256 100644 --- a/puzzles/allergic_cliffs/4.py +++ b/puzzles/allergic_cliffs/4.py @@ -1,8 +1,7 @@ import sys -if '../../..' not in sys.path: - sys.path.append('../../..') import zoombinis.common as common +import zoombinis.ui as ui from zoombinis.puzzles.allergic_cliffs.common import * import pathlib @@ -15,47 +14,73 @@ except: print("args: ", file = sys.stderr) sys.exit(1) -cand_rules = set(ALL_RULES) -waiting = troupe -across = {Cliff.UPPER: set(), Cliff.LOWER: set()} - -def matching_rules(zoombini, rules): - return sum(zoombini.has(r.attr) for r in rules) - -def send(z, cliff): - print("Send {} across the {} cliff.".format(z, cliff.name.lower())) - ans = input("Did it work? (y/n) ") - if ans == 'n': - cliff = cliff.other() - print("Send {} across the {} cliff.".format(z, cliff.name.lower())) - input("Ready? (Enter) ") - waiting.remove(z) - across[cliff].add(z) - -def by_cliff(cliff): - return frozenset(c for c in cand_rules) - -while waiting and len(upper_cands) >= 3 and len(lower_cands) >= 3: - upper_rules = {r for r in rules if r.cliff == Cliff.UPPER} - lower_rules = {r for r in rules if r.cliff == Cliff.LOWER} - up = max(waiting, key = lambda z: matching_rules(z, upper_rules) * matching_rules(z, lower_rules)) - if not across[Cliff.UPPER]: - cliff = Cliff.UPPER - elif not across[Cliff.LOWER]: - cliff = Cliff.LOWER - else: - score = lambda cliff: sum(len(up.attrs & z.attrs) for z in across[cliff]) / len(across[cliff]) - cliff = max(Cliff, key = score) - send(up, cliff) - -if len(upper_cands) < 3: - pos_attrs = {attr for (cliff, attr) in lower_cands} - pos_cliff = Cliff.LOWER -else: - pos_attrs = {attr for (cliff, attr) in upper_cands} - pos_cliff = Cliff.UPPER - -while waiting: - up = waiting.pop() - cliff = pos_cliff if any(up.has(attr) for attr in pos_attrs) else pos_cliff.other() - send(up, cliff) +class Instance: + def __init__(self, zoombinis, io_agent): + self.io_agent = io_agent + self.waiting = set(zoombinis) + self.across = {cliff: set() for cliff in Cliff} + self.cand_rules = {cliff: set(common.ALL_ATTRS) for cliff in Cliff} + + self.pos_cliff = None + self.pos_cand_rules = None + self.pos_rules = None + + def choose_zoombini(self): + if self.pos_rules is not None: + return next(iter(self.waiting)) + elif self.pos_cliff is None: + key = lambda z: ( + len(z.attrs & self.cand_rules[Cliff.UPPER]) + * len(z.attrs & self.cand_rules[Cliff.LOWER]) + ) + return max(self.waiting, key = key) + else: + return next(iter(self.waiting)) + + def choose_cliff(self, zoombini): + self.check_solve() + if self.pos_rules is None: + def key(cliff): + pool = self.across[cliff] if self.across[cliff] else self.waiting - {zoombini} + # assume that if there's a cliff with no Zoombinis across there are still others waiting to cross + return sum(len(zoombini.attrs & z.attrs) for z in pool) / len(pool) + return max(Cliff, key = key) + else: + return self.pos_cliff if zoombini.attrs & self.pos_rules else self.pos_cliff.other() + + def check_solve(self): + if self.pos_cliff is None: + drained = [cliff for cliff in Cliff if len(self.cand_rules[cliff]) < 3] + if drained: + [neg_cliff] = drained + self.pos_cliff = neg_cliff.other() + self.pos_cand_rules = self.cand_rules[self.pos_cliff] + self.check_solve() + elif len(self.pos_cand_rules) == 3: + self.pos_rules = frozenset(self.pos_cand_rules) + + def send(zoombini, cliff): + send_fmt = "Send {} across the {} cliff." + self.io_agent.print(send_fmt.format(zoombini, cliff.name.lower())) + ans = self.io_agent.choose("Did it work?", ["y", "n"]) + if ans == 'n': + cliff = cliff.other() + self.io_agent.print(send_fmt.format(zoombini, cliff.name.lower())) + self.io_agent.wait() + self.waiting.remove(zoombini) + self.across[cliff].add(zoombini) + if self.pos_cliff is None: + for attr in zoombini.attrs: + self.cand_rules[cliff.other()].discard(attr) + elif self.pos_rules is None and cliff == self.pos_cliff.other(): + for attr in zoombini.attrs: + self.pos_cand_rules.discard(attr) + + def run(self): + while self.waiting: + z = self.choose_zoombini() + c = self.choose_cliff() + self.send(z, c) + self.waiting.remove(z) + +Instance(troupe, ui.Agent(sys.stdin, sys.stderr)).run() diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..99a54f3 --- /dev/null +++ b/ui.py @@ -0,0 +1,20 @@ +class Agent: + def __init__(self, in_stream, out_stream): + self.in_stream = in_stream + self.out_stream = out_stream + + def print(self, message): + print(message, file = self.out_stream) + + def choose(self, message, choices): + def prompt(): + self.out_stream.write("{} ({}) ".format(message, '/'.join(choices))) + return self.in_stream.readline().strip() + resp = prompt() + while resp not in choices: + resp = prompt() + return resp + + def wait(self): + self.out_stream.write("Ready? (Enter) ") + self.in_stream.readline()