From 2975b811386b3571cc770137b192ea36bb02eb4b Mon Sep 17 00:00:00 2001 From: Jakob Cornell Date: Fri, 21 Jun 2019 17:22:59 -0500 Subject: [PATCH] Initial commit --- .gitignore | 1 + __init__.py | 0 common.py | 32 +++++++++++++++ puzzles/__init__.py | 0 puzzles/allergic_cliffs/4.py | 61 +++++++++++++++++++++++++++++ puzzles/allergic_cliffs/__init__.py | 0 puzzles/allergic_cliffs/common.py | 15 +++++++ troupe.py | 36 +++++++++++++++++ 8 files changed, 145 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 common.py create mode 100644 puzzles/__init__.py create mode 100644 puzzles/allergic_cliffs/4.py create mode 100644 puzzles/allergic_cliffs/__init__.py create mode 100644 puzzles/allergic_cliffs/common.py create mode 100644 troupe.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/common.py b/common.py new file mode 100644 index 0000000..13f8afb --- /dev/null +++ b/common.py @@ -0,0 +1,32 @@ +from enum import Enum + +class Zoombini: + class Property(Enum): + pass # marker class + + class Hair(Property): + PINK = 'pink'; GREEN = 'green'; TUFT = 'tuft'; SPIKY = 'spiky'; FLAT = 'flat' + class Eyes(Property): + ONE = 'one'; WIDE = 'wide'; SLEEPY = 'sleepy'; SHADES = 'shades'; GLASSES = 'glasses' + class Nose(Property): + RED = 'red'; ORANGE = 'orange'; GREEN = 'green'; BLUE = 'blue'; PURPLE = 'purple' + class Feet(Property): + SHOES = 'shoes'; PROPELLER = 'propeller'; SPRING = 'spring'; WHEELS = 'wheels'; SKATES = 'skates' + + 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 + + def __str__(self): + return str({type(a).__name__: a.value for a in self.attrs.values()}) + +def load(path): + import pickle + with path.open() as f: + return pickle.load(f) diff --git a/puzzles/__init__.py b/puzzles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/puzzles/allergic_cliffs/4.py b/puzzles/allergic_cliffs/4.py new file mode 100644 index 0000000..2650c16 --- /dev/null +++ b/puzzles/allergic_cliffs/4.py @@ -0,0 +1,61 @@ +import sys +if '../../..' not in sys.path: + sys.path.append('../../..') + +import zoombinis.common as common +from zoombinis.puzzles.allergic_cliffs.common import * + +import pathlib +import random + +try: + [file_name] = sys.argv[1:] + troupe = common.load(pathlib.Path(file_name)) +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) diff --git a/puzzles/allergic_cliffs/__init__.py b/puzzles/allergic_cliffs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/puzzles/allergic_cliffs/common.py b/puzzles/allergic_cliffs/common.py new file mode 100644 index 0000000..d8f8d75 --- /dev/null +++ b/puzzles/allergic_cliffs/common.py @@ -0,0 +1,15 @@ +import sys +sys.path.append('../../..') +import zoombinis.common as comm + +from enum import Enum + +Cliff = Enum('Cliff', ['UPPER', 'LOWER']) +Cliff.other = lambda self: {Cliff.UPPER: Cliff.LOWER, Cliff.LOWER: Cliff.UPPER}[self] + +ALL_RULES = frozenset( + (cliff, attr) + for cliff in Cliff + for prop in comm.Zoombini.Property + for attr in prop +) diff --git a/troupe.py b/troupe.py new file mode 100644 index 0000000..dff8e6b --- /dev/null +++ b/troupe.py @@ -0,0 +1,36 @@ +import pickle +import sys + +from common import Zoombini +Z = Zoombini + +def get_options(type_): + return '/'.join(variant.value for variant in type_) + +zs = [] +try: + while True: + template = "\tchoose {} ({}): " + print(template.format('hair', get_options(Z.Hair)), file = sys.stderr, end = '') + hair = input() + print(template.format('eyes', get_options(Z.Eyes)), file = sys.stderr, end = '') + eyes = input() + print(template.format('nose', get_options(Z.Nose)), file = sys.stderr, end = '') + nose = input() + print(template.format('feet', get_options(Z.Feet)), file = sys.stderr, end = '') + feet = input() + + zs.append( + Zoombini({ + Z.Hair[hair.upper()], + Z.Eyes[eyes.upper()], + Z.Nose[nose.upper()], + Z.Feet[feet.upper()], + }) + ) + print("added", file = sys.stderr) +except EOFError: + pass + +pickle.dump(zs, sys.stdout.buffer) +print() -- 2.30.2