Initial commit
authorJakob Cornell <jakob@jcornell.net>
Fri, 21 Jun 2019 22:22:59 +0000 (17:22 -0500)
committerJakob Cornell <jakob@jcornell.net>
Fri, 21 Jun 2019 22:22:59 +0000 (17:22 -0500)
.gitignore [new file with mode: 0644]
__init__.py [new file with mode: 0644]
common.py [new file with mode: 0644]
puzzles/__init__.py [new file with mode: 0644]
puzzles/allergic_cliffs/4.py [new file with mode: 0644]
puzzles/allergic_cliffs/__init__.py [new file with mode: 0644]
puzzles/allergic_cliffs/common.py [new file with mode: 0644]
troupe.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..bee8a64
--- /dev/null
@@ -0,0 +1 @@
+__pycache__
diff --git a/__init__.py b/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/common.py b/common.py
new file mode 100644 (file)
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 (file)
index 0000000..e69de29
diff --git a/puzzles/allergic_cliffs/4.py b/puzzles/allergic_cliffs/4.py
new file mode 100644 (file)
index 0000000..2650c16
--- /dev/null
@@ -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: <troupe-file>", 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 (file)
index 0000000..e69de29
diff --git a/puzzles/allergic_cliffs/common.py b/puzzles/allergic_cliffs/common.py
new file mode 100644 (file)
index 0000000..d8f8d75
--- /dev/null
@@ -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 (file)
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()