diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/cs101courseware/Report2_handin_0_of_5.token b/cs101courseware/Report2_handin_0_of_5.token
new file mode 100644
index 0000000000000000000000000000000000000000..b22456a48f6b99a1abe7ffca6488220f21a2b2e3
Binary files /dev/null and b/cs101courseware/Report2_handin_0_of_5.token differ
diff --git a/cs101courseware/Report2_handin_5_of_0.token b/cs101courseware/Report2_handin_5_of_0.token
new file mode 100644
index 0000000000000000000000000000000000000000..984da98d1e0010a7bfbf93d29eaac5f2cd2a4589
Binary files /dev/null and b/cs101courseware/Report2_handin_5_of_0.token differ
diff --git a/cs101courseware/Report2_handin_5_of_5.token b/cs101courseware/Report2_handin_5_of_5.token
new file mode 100644
index 0000000000000000000000000000000000000000..dba8a94bc2f0399b59e538765b683baf81260c06
Binary files /dev/null and b/cs101courseware/Report2_handin_5_of_5.token differ
diff --git a/cs101courseware/Report2_resources_do_not_hand_in.dat b/cs101courseware/Report2_resources_do_not_hand_in.dat
new file mode 100644
index 0000000000000000000000000000000000000000..70f3ef95826214c89a880cec61678ee63dc02b5e
Binary files /dev/null and b/cs101courseware/Report2_resources_do_not_hand_in.dat differ
diff --git a/cs101courseware/__pycache__/__init__.cpython-36.pyc b/cs101courseware/__pycache__/__init__.cpython-36.pyc
index f0120104402414327be5b9e01a5f44b6705be45c..edc9f166ab4535e9b841d33a70cc7d9cbeb6026b 100644
Binary files a/cs101courseware/__pycache__/__init__.cpython-36.pyc and b/cs101courseware/__pycache__/__init__.cpython-36.pyc differ
diff --git a/cs101courseware/__pycache__/cs101report1.cpython-36.pyc b/cs101courseware/__pycache__/cs101report1.cpython-36.pyc
index 10e0fe726c06a4bb64737430fb2635d6af031acd..578cd397e12827b6037e4c9a6ca83283c311d2a4 100644
Binary files a/cs101courseware/__pycache__/cs101report1.cpython-36.pyc and b/cs101courseware/__pycache__/cs101report1.cpython-36.pyc differ
diff --git a/cs101courseware/__pycache__/cs101report2.cpython-36.pyc b/cs101courseware/__pycache__/cs101report2.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5803ebf4780d5db45c1803854af192f6394fd2da
Binary files /dev/null and b/cs101courseware/__pycache__/cs101report2.cpython-36.pyc differ
diff --git a/cs101courseware/__pycache__/cs101report2_grade.cpython-36.pyc b/cs101courseware/__pycache__/cs101report2_grade.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..06a7bac8eb7c4fdd20fbb069c10838580df93f46
Binary files /dev/null and b/cs101courseware/__pycache__/cs101report2_grade.cpython-36.pyc differ
diff --git a/cs101courseware/__pycache__/homework1.cpython-36.pyc b/cs101courseware/__pycache__/homework1.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bb6f4852c406c2c6dea4f958bd019f5f3073e78f
Binary files /dev/null and b/cs101courseware/__pycache__/homework1.cpython-36.pyc differ
diff --git a/cs101courseware/cs101report1.py b/cs101courseware/cs101report1.py
index 1ee2d8c634d1d4022b65e90bdb033b63bb0986dc..d1136303d49fa6ed2bab183337d73939c63fa5b6 100644
--- a/cs101courseware/cs101report1.py
+++ b/cs101courseware/cs101report1.py
@@ -31,10 +31,10 @@ class LinearRegressionQuestion(QuestionGroup):
         def process_output(self, res, txt, numbers):
             return numbers[-1]
 
-class Report0(Report):
+class Report1(Report):
     title = "CS 101 Report 1"
     questions = [(ListReversalQuestion, 5), (LinearRegressionQuestion, 13)]
     pack_imports = [homework1] # Include this file in .token file
 
 if __name__ == "__main__":
-    evaluate_report_student(Report0())
+    evaluate_report_student(Report1())
diff --git a/cs101courseware/cs101report2.py b/cs101courseware/cs101report2.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3d8691c950c1a6a4da8500112822ff4952ae622
--- /dev/null
+++ b/cs101courseware/cs101report2.py
@@ -0,0 +1,46 @@
+from unitgrade.unitgrade import QuestionGroup, Report, QPrintItem, Hidden
+
+class ListReversalQuestion(QuestionGroup):
+    title = "Reversal of list"
+    class ListReversalItem(QPrintItem):
+        l = [1, 3, 5, 1, 610]
+        def compute_answer_print(self):
+            from cs101courseware.homework1 import reverse_list
+            print(reverse_list(self.l))
+
+    class ListReversalWordsItem(ListReversalItem):
+        l = ["hello", "world", "summer", "dog"]
+
+    class ListReversalWordsItemHidden(ListReversalItem, Hidden):
+        l = ["hello", "world", "summer", "dog"]
+
+# class LinearRegressionQuestion(QuestionGroup):
+#     title = "Linear regression and Boston dataset"
+#     class CoefficientsItem(QPrintItem):
+#         testfun = QPrintItem.assertL2
+#         tol = 0.03
+#
+#         def compute_answer_print(self):
+#             from cs101courseware_example.homework1 import boston_linear
+#             boston_linear()
+#
+#         def process_output(self, res, txt, numbers):
+#             return numbers[:-1]
+#
+#     class RMSEItem(CoefficientsItem):
+#         def process_output(self, res, txt, numbers):
+#             return numbers[-1]
+
+class Report2(Report):
+    title = "CS 101 Report 2"
+    questions = [(ListReversalQuestion, 5), ]
+    pack_imports = [] # Include this file in .token file
+
+if __name__ == "__main__":
+    # from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report
+    from unitgrade.unitgrade_helpers import evaluate_report_student
+    # setup_grade_file_report(Report2, minify=True, bzip=True, obfuscate=True)
+    # evaluate_report_student(Report2())
+    evaluate_report_student(Report2())
+
+
diff --git a/cs101courseware/cs101report2_grade.py b/cs101courseware/cs101report2_grade.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0324e60978addc5e54feb198dacda40d5e3fdef
--- /dev/null
+++ b/cs101courseware/cs101report2_grade.py
@@ -0,0 +1,421 @@
+
+__version__ = "0.0.4"
+import os
+import compress_pickle
+
+def cache_write(object, file_name, verbose=True):
+    dn = os.path.dirname(file_name)
+    if not os.path.exists(dn):
+        os.mkdir(dn)
+    if verbose: print("Writing cache...", file_name)
+    with open(file_name, 'wb', ) as f:
+        compress_pickle.dump(object, f, compression="lzma")
+    if verbose: print("Done!")
+
+
+def cache_exists(file_name):
+    # file_name = cn_(file_name) if cache_prefix else file_name
+    return os.path.exists(file_name)
+
+
+def cache_read(file_name):
+    # file_name = cn_(file_name) if cache_prefix else file_name
+    if os.path.exists(file_name):
+        with open(file_name, 'rb') as f:
+            return compress_pickle.load(f, compression="lzma")
+            # return pickle.load(f)
+    else:
+        return None
+
+
+
+"""
+git add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade
+
+"""
+import unittest
+import numpy as np
+import os
+from io import StringIO
+import sys
+import collections
+import inspect
+
+myround = lambda x: int(x)  # required.
+
+def setup_dir_by_class(C,base_dir):
+    name = C.__class__.__name__
+    # base_dir = os.path.join(base_dir, name)
+    # if not os.path.isdir(base_dir):
+    #     os.makedirs(base_dir)
+    return base_dir, name
+
+class Hidden:
+    def hide(self):
+        return True
+
+class Capturing(list):
+    def __enter__(self, capture_errors=True):
+        self._stdout = sys.stdout
+        sys.stdout = self._stringio = StringIO()
+        if capture_errors:
+            self._sterr = sys.stderr
+            sys.sterr = StringIO() # memory hole it
+        self.capture_errors = capture_errors
+        return self
+
+    def __exit__(self, *args):
+        self.extend(self._stringio.getvalue().splitlines())
+        del self._stringio    # free up some memory
+        sys.stdout = self._stdout
+        if self.capture_errors:
+            sys.sterr = self._sterr
+
+
+class QItem(unittest.TestCase):
+    title = None
+    testfun = unittest.TestCase.assertEqual
+    tol = 0
+
+    def __init__(self, working_directory=None, correct_answer_payload=None, *args, **kwargs):
+        self.name = self.__class__.__name__
+        self._correct_answer_payload = correct_answer_payload
+        super().__init__(*args, **kwargs)
+        if self.title is None:
+            self.title = self.name
+
+    def assertL2(self, computed, expected, tol=None):
+        if tol == None:
+            tol = self.tol
+        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )
+        if np.max(diff) > tol:
+            print("Not equal within tolerance {tol}")
+            print(f"Element-wise differences {diff.tolist()}")
+            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")
+
+    def assertL2Relative(self, computed, expected, tol=None):
+        if tol == None:
+            tol = self.tol
+        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )
+        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )
+        if np.sum(diff > tol) > 0:
+            print(f"Not equal within tolerance {tol}")
+            print(f"Element-wise differences {diff.tolist()}")
+            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")
+
+    def compute_answer(self):
+        raise NotImplementedError("test code here")
+
+    def test(self, computed, expected):
+        self.testfun(computed, expected)
+
+    def get_points(self, verbose=False):
+        possible = 1
+        try:
+            computed = self.compute_answer()
+        except Exception as e:
+            print("\n=================================================================================")
+            print(f"When trying to run test class '{self.name}' your code threw an error:")
+            print(e)
+            print("=================================================================================")
+            import traceback
+            traceback.print_exc()
+            return (0, possible)
+        correct = self._correct_answer_payload
+        try:
+            # print(computed, correct)
+            self.test(computed=computed, expected=correct)
+        except Exception as e:
+            print("=================================================================================")
+            print(f"Test output from test class '{self.name}' does not match expected result. Test error:")
+            print(e)
+            print("-------------------------Your output:--------------------------------------------")
+            print(computed)
+            print("-------------------------Expected output:----------------------------------------")
+            print(correct)
+            print("=================================================================================")
+            return (0, possible)
+        return (1, possible)
+
+    def score(self):
+        try:
+            self.test()
+        except Exception as e:
+            return 0
+        return 1
+
+class QPrintItem(QItem):
+    def compute_answer_print(self):
+        raise Exception("Generate output here")
+
+    def process_output(self, res, txt, numbers):
+        return txt
+
+    def compute_answer(self):
+        with Capturing() as output:
+            res = self.compute_answer_print()
+        s = "\n".join(output)
+        numbers = extract_numbers(s)
+        return self.process_output(res, s, numbers)
+
+class OrderedClassMembers(type):
+    @classmethod
+    def __prepare__(self, name, bases):
+        return collections.OrderedDict()
+    def __new__(self, name, bases, classdict):
+        classdict['__ordered__'] = [key for key in classdict.keys() if key not in ('__module__', '__qualname__')]
+        return type.__new__(self, name, bases, classdict)
+
+class QuestionGroup(metaclass=OrderedClassMembers):
+    title = "Graph search"
+    items = None
+    partially_scored = False
+
+    def __init__(self, *args, **kwargs):
+        self.name = self.__class__.__name__
+        if self.items is None:
+            self.items = []
+            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__] if inspect.isclass(gt) and issubclass(gt, QItem)]
+            for gt in members:
+                self.items.append( (gt, 1) )
+        self.items = [(I(), w) for I, w in self.items]
+
+class Report():
+    title = "report title"
+    questions = []
+    pack_imports = []
+
+    def __init__(self):
+        working_directory = os.path.abspath(os.path.dirname(inspect.getfile(type(self))))
+        # working_directory = os.path.join(pathlib.Path(__file__).parent.absolute(), "payloads/")
+        self.wdir, self.name = setup_dir_by_class(self, working_directory)
+        self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")
+        # print(self.computed_answers_file)
+        self.questions = [(Q(working_directory=self.wdir),w) for Q,w in self.questions]
+        if os.path.isfile(self.computed_answers_file):
+            self.set_payload(cache_read(self.computed_answers_file))
+
+    def set_payload(self, payloads):
+        for q, _ in self.questions:
+            for item, _ in q.items:
+                if q.name not in payloads or item.name not in payloads[q.name]:
+                    continue
+                item._correct_answer_payload = payloads[q.name][item.name]['payload']
+
+def extract_numbers(txt):
+    import re
+    numeric_const_pattern = '[-+]? (?: (?: \d* \. \d+ ) | (?: \d+ \.? ) )(?: [Ee] [+-]? \d+ ) ?'
+    rx = re.compile(numeric_const_pattern, re.VERBOSE)
+    all = rx.findall(txt)
+    all = [float(a) if '.' in a else int(a) for a in all]
+    return all
+
+
+import numpy as np
+from tabulate import tabulate
+from datetime import datetime
+import pickle
+import pyfiglet
+
+# from unitgrade.unitgrade import Hidden
+# import unitgrade as ug
+# import unitgrade.unitgrade as ug
+import inspect
+import os
+import argparse
+
+
+parser = argparse.ArgumentParser(description='Evaluate your report.', epilog="""Example: 
+To run all tests in a report: 
+
+> python report1.py
+
+To run only question 2 or question 2.1
+
+> python report1.py -q 2
+> python report1.py -q 2.1
+
+Note this scripts does not grade your report. To grade your report, use:
+
+> python report1_grade.py
+
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+As an example, suppose the report file is Documents/course_package/report1.py, and `course_package` is a python package. Change directory to 'Documents/` and execute:
+
+> python -m course_package.report1
+
+see https://docs.python.org/3.9/using/cmdline.html
+""")
+parser.add_argument('-q', nargs='?', type=str, default=None, help='Only evaluate this question (example: -q 2)')
+parser.add_argument('--verbose', nargs='?', type=bool, default=False, help='Show expected output')
+# parser.add_argument('integers', metavar='N', type=int, nargs='+',
+#                     help='an integer for the accumulator')
+# parser.add_argument('--sum', dest='accumulate', action='store_const',
+#                     const=sum, default=max,
+#                     help='sum the integers (default: find the max)')
+
+
+
+def evaluate_report_student(report, question=None, qitem=None):
+    args = parser.parse_args()
+    if question is None and args.q is not None:
+        question = args.q
+        if "." in question:
+            question, qitem = [int(v) for v in question.split(".")]
+        else:
+            question = int(question)
+    results, table_data = evaluate_report(report, question=question, qitem=qitem, verbose=args.verbose)
+    if question is None:
+        print("Provisional evaluation")
+        tabulate(table_data)
+        table = table_data
+        print(tabulate(table))
+        print(" ")
+    print("Note your results have not yet been registered. \nTo register your results, please run the file:")
+
+    fr = inspect.getouterframes(inspect.currentframe())[1].filename
+    print(">>>", os.path.basename(fr)[:-3] + "_grade.py")
+    print("In the same manner as you ran this file.")
+    return results
+
+
+def upack(q):
+    # h = zip([(i['w'], i['possible'], i['obtained']) for i in q.values()])
+
+    h =[(i['w'], i['possible'], i['obtained']) for i in q.values()]
+    h = np.asarray(h)
+    return h[:,0], h[:,1], h[:,2],
+    #
+    # ws, possible, obtained = (np.asarray(x).squeeze() for x in zip([(i['w'], i['possible'], i['obtained']) for i in q.values()]))
+    # return ws, possible, obtained
+
+def evaluate_report(report, question=None, qitem=None, verbose=False):
+    now = datetime.now()
+    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")
+    b = "\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )
+    print(b + " v" + __version__)
+    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
+    print("Started: " + dt_string)
+    print("Evaluating " + report.title, "(use --help for options)")
+    print("")
+    table_data = []
+    nL = 80
+
+    score = {}
+    for n, (q, w) in enumerate(report.questions):
+        q_hidden = issubclass(q.__class__, Hidden)
+        if question is not None and n+1 != question:
+            continue
+
+        print(f"Question {n+1}: {q.title}")
+        print("="*nL)
+        q.possible = 0
+        q.obtained = 0
+
+        q_ = {} # Gather score in this class.
+        for j, (item, iw) in enumerate(q.items):
+            if qitem is not None and question is not None and item is not None and j+1 != qitem:
+                continue
+
+            ss = f"*** q{n+1}.{j+1}) {item.title}"
+            el = nL-4
+            if len(ss) < el:
+                ss += '.'*(el-len(ss))
+            hidden = issubclass(item.__class__, Hidden)
+            if not hidden:
+                print(ss, end="")
+            (current, possible) = item.get_points()
+            q_[j] = {'w': iw, 'possible': possible, 'obtained': current, 'hidden': hidden}
+            # q.possible += possible * iw
+            # q.obtained += current * iw
+            if not hidden:
+                if current == possible:
+                    print(f"PASS")
+                else:
+                    print(f"*** FAILED")
+
+        ws, possible, obtained = upack(q_)
+        possible = int(ws @ possible)
+        obtained = int(ws @ obtained)
+        obtained = myround(np.floor((w * obtained) / possible )) if possible > 0 else 0
+        score[n] = {'w': w, 'possible': w, 'obtained': obtained, 'ítems': q_, 'hidden': q_hidden}
+
+        q.obtained = obtained
+        q.possible = possible
+
+        s1 = f"*** Question q{n+1}"
+        s2 = f" {q.obtained}/{w}"
+        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )
+        print(" ")
+        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])
+
+    ws, possible, obtained = upack(score)
+    possible = int( sum(possible) )
+    obtained = int( sum(obtained) ) # Cast to python int
+    report.possible = possible
+    report.obtained = obtained
+    now = datetime.now()
+    dt_string = now.strftime("%H:%M:%S")
+    print(f"Completed: "+ dt_string)
+    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])
+    results = {'total': (obtained, possible), 'details': score}
+    return results, table_data
+
+
+from tabulate import tabulate
+from datetime import datetime
+import inspect
+import json
+import os
+import bz2
+import pickle
+
+def bzwrite(json_str, token): # to get around obfuscation issues
+    with getattr(bz2, 'open')(token, "wt") as f:
+        f.write(json_str)
+
+def gather_upload_to_campusnet(report, output_dir=None):
+    n = 80
+    results, table_data = evaluate_report(report)
+    print(" ")
+    print("="*n)
+    print("Final evaluation")
+    print(tabulate(table_data))
+    results['sources'] = {}
+    print("Gathering files...")
+    for m in report.pack_imports:
+        with open(m.__file__, 'r') as f:
+            results['sources'][m.__name__] = f.read()
+        print(f"*** {m.__name__}")
+
+    results['sources'] = {}
+    json_str = json.dumps(results, indent=4)
+    if output_dir is None:
+        output_dir = os.getcwd()
+
+    payload_out_base = report.__class__.__name__ + "_handin"
+
+    obtain, possible = results['total']
+    token = "%s_%i_of_%i.token"%(payload_out_base, obtain, possible) # + str(obtained) +"_" +  ".token"
+    token = os.path.join(output_dir, token)
+    bzwrite(json_str, token)
+
+    print(" ")
+    print("To get credit for your results, please upload the single file: ")
+    print(">", token)
+    print("To campusnet without any modifications.")
+
+def source_instantiate(name, report1_source, payload):
+    eval("exec")(report1_source, globals())
+    report = eval(name)()
+    pl = pickle.loads(payload)
+    report.set_payload(pl)
+    return report
+
+report1_source = '__version__ = "0.0.4"\nimport os\nimport compress_pickle\n\ndef cache_write(object, file_name, verbose=True):\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        with open(file_name, \'rb\') as f:\n            return compress_pickle.load(f, compression="lzma")\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nfrom io import StringIO\nimport sys\nimport collections\nimport inspect\n\nmyround = lambda x: int(x)  # required.\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Capturing(list):\n    def __enter__(self, capture_errors=True):\n        self._stdout = sys.stdout\n        sys.stdout = self._stringio = StringIO()\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = unittest.TestCase.assertEqual\n    tol = 0\n\n    def __init__(self, working_directory=None, correct_answer_payload=None, *args, **kwargs):\n        self.name = self.__class__.__name__\n        self._correct_answer_payload = correct_answer_payload\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        if np.max(diff) > tol:\n            print("Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def compute_answer(self):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False):\n        possible = 1\n        try:\n            computed = self.compute_answer()\n        except Exception as e:\n            print("\\n=================================================================================")\n            print(f"When trying to run test class \'{self.name}\' your code threw an error:")\n            print(e)\n            print("=================================================================================")\n            import traceback\n            traceback.print_exc()\n            return (0, possible)\n        correct = self._correct_answer_payload\n        try:\n            # print(computed, correct)\n            self.test(computed=computed, expected=correct)\n        except Exception as e:\n            print("=================================================================================")\n            print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n            print(e)\n            print("-------------------------Your output:--------------------------------------------")\n            print(computed)\n            print("-------------------------Expected output:----------------------------------------")\n            print(correct)\n            print("=================================================================================")\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        raise Exception("Generate output here")\n\n    def process_output(self, res, txt, numbers):\n        return txt\n\n    def compute_answer(self):\n        with Capturing() as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        numbers = extract_numbers(s)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        classdict[\'__ordered__\'] = [key for key in classdict.keys() if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Graph search"\n    items = None\n    partially_scored = False\n\n    def __init__(self, *args, **kwargs):\n        self.name = self.__class__.__name__\n        if self.items is None:\n            self.items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for gt in members:\n                self.items.append( (gt, 1) )\n        self.items = [(I(), w) for I, w in self.items]\n\nclass Report():\n    title = "report title"\n    questions = []\n    pack_imports = []\n\n    def __init__(self):\n        working_directory = os.path.abspath(os.path.dirname(inspect.getfile(type(self))))\n        # working_directory = os.path.join(pathlib.Path(__file__).parent.absolute(), "payloads/")\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        # print(self.computed_answers_file)\n        self.questions = [(Q(working_directory=self.wdir),w) for Q,w in self.questions]\n        if os.path.isfile(self.computed_answers_file):\n            self.set_payload(cache_read(self.computed_answers_file))\n\n    def set_payload(self, payloads):\n        for q, _ in self.questions:\n            for item, _ in q.items:\n                if q.name not in payloads or item.name not in payloads[q.name]:\n                    continue\n                item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n\ndef extract_numbers(txt):\n    import re\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if \'.\' in a else int(a) for a in all]\n    return all\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pickle\nimport pyfiglet\n\n# from unitgrade.unitgrade import Hidden\n# import unitgrade as ug\n# import unitgrade.unitgrade as ug\nimport inspect\nimport os\nimport argparse\n\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python report1.py\n\nTo run only question 2 or question 2.1\n\n> python report1.py -q 2\n> python report1.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nAs an example, suppose the report file is Documents/course_package/report1.py, and `course_package` is a python package. Change directory to \'Documents/` and execute:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""")\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (example: -q 2)\')\nparser.add_argument(\'--verbose\', nargs=\'?\', type=bool, default=False, help=\'Show expected output\')\n# parser.add_argument(\'integers\', metavar=\'N\', type=int, nargs=\'+\',\n#                     help=\'an integer for the accumulator\')\n# parser.add_argument(\'--sum\', dest=\'accumulate\', action=\'store_const\',\n#                     const=sum, default=max,\n#                     help=\'sum the integers (default: find the max)\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n    results, table_data = evaluate_report(report, question=question, qitem=qitem, verbose=args.verbose)\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n    print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    print(">>>", os.path.basename(fr)[:-3] + "_grade.py")\n    print("In the same manner as you ran this file.")\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n    #\n    # ws, possible, obtained = (np.asarray(x).squeeze() for x in zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]))\n    # return ws, possible, obtained\n\ndef evaluate_report(report, question=None, qitem=None, verbose=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    print("Evaluating " + report.title, "(use --help for options)")\n    print("")\n    table_data = []\n    nL = 80\n\n    score = {}\n    for n, (q, w) in enumerate(report.questions):\n        q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n\n        print(f"Question {n+1}: {q.title}")\n        print("="*nL)\n        q.possible = 0\n        q.obtained = 0\n\n        q_ = {} # Gather score in this class.\n        for j, (item, iw) in enumerate(q.items):\n            if qitem is not None and question is not None and item is not None and j+1 != qitem:\n                continue\n\n            ss = f"*** q{n+1}.{j+1}) {item.title}"\n            el = nL-4\n            if len(ss) < el:\n                ss += \'.\'*(el-len(ss))\n            hidden = issubclass(item.__class__, Hidden)\n            if not hidden:\n                print(ss, end="")\n            (current, possible) = item.get_points()\n            q_[j] = {\'w\': iw, \'possible\': possible, \'obtained\': current, \'hidden\': hidden}\n            # q.possible += possible * iw\n            # q.obtained += current * iw\n            if not hidden:\n                if current == possible:\n                    print(f"PASS")\n                else:\n                    print(f"*** FAILED")\n\n        ws, possible, obtained = upack(q_)\n        possible = int(ws @ possible)\n        obtained = int(ws @ obtained)\n        obtained = myround(np.floor((w * obtained) / possible )) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'ítems\': q_, \'hidden\': q_hidden}\n\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( sum(possible) )\n    obtained = int( sum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n    print(f"Completed: "+ dt_string)\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n    results[\'sources\'] = {}\n    print("Gathering files...")\n    for m in report.pack_imports:\n        with open(m.__file__, \'r\') as f:\n            results[\'sources\'][m.__name__] = f.read()\n        print(f"*** {m.__name__}")\n\n    results[\'sources\'] = {}\n    json_str = json.dumps(results, indent=4)\n    # now = datetime.now()\n    # dname = os.path.dirname(inspect.getfile(report.__class__))\n    # dname = os.getcwd()\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    token = "%s_%i_of_%i.token"%(payload_out_base, obtain, possible) # + str(obtained) +"_" +  ".token"\n    token = os.path.join(output_dir, token)\n    bzwrite(json_str, token)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    report = eval(name)()\n    pl = pickle.loads(payload)\n    report.set_payload(pl)\n    return report\n\n\n\nclass ListReversalQuestion(QuestionGroup):\n    title = "Reversal of list"\n    class ListReversalItem(QPrintItem):\n        l = [1, 3, 5, 1, 610]\n        def compute_answer_print(self):\n            from cs101courseware.homework1 import reverse_list\n            print(reverse_list(self.l))\n\n    class ListReversalWordsItem(ListReversalItem):\n        l = ["hello", "world", "summer", "dog"]\n\n    class ListReversalWordsItemHidden(ListReversalItem, Hidden):\n        l = ["hello", "world", "summer", "dog"]\n\n# class LinearRegressionQuestion(QuestionGroup):\n#     title = "Linear regression and Boston dataset"\n#     class CoefficientsItem(QPrintItem):\n#         testfun = QPrintItem.assertL2\n#         tol = 0.03\n#\n#         def compute_answer_print(self):\n#             from cs101courseware_example.homework1 import boston_linear\n#             boston_linear()\n#\n#         def process_output(self, res, txt, numbers):\n#             return numbers[:-1]\n#\n#     class RMSEItem(CoefficientsItem):\n#         def process_output(self, res, txt, numbers):\n#             return numbers[-1]\n\nclass Report2(Report):\n    title = "CS 101 Report 2"\n    questions = [(ListReversalQuestion, 5), ]\n    pack_imports = [] # Include this file in .token file'
+report1_payload = b"\x80\x03}q\x00X\x14\x00\x00\x00ListReversalQuestionq\x01}q\x02(X\x10\x00\x00\x00ListReversalItemq\x03}q\x04X\x07\x00\x00\x00payloadq\x05X \x00\x00\x00[610, 1, 5, 3, 1, 'hi', 'thasd']q\x06sX\x15\x00\x00\x00ListReversalWordsItemq\x07}q\x08h\x05X2\x00\x00\x00['dog', 'summer', 'world', 'hello', 'hi', 'thasd']q\tsX\x1b\x00\x00\x00ListReversalWordsItemHiddenq\n}q\x0bh\x05X2\x00\x00\x00['dog', 'summer', 'world', 'hello', 'hi', 'thasd']q\x0csus."
+name="Report2"
+
+report = source_instantiate(name, report1_source, report1_payload)
+output_dir = os.path.dirname(__file__)
+gather_upload_to_campusnet(report, output_dir)
\ No newline at end of file
diff --git a/cs101courseware/deploy_cs101.py b/cs101courseware/deploy_cs101.py
index 9416cad5602eaf1258639f856d03a3a4efa96112..ed5c4c0ceb14e2cdf3535a48fc6aa912e87fe7a5 100644
--- a/cs101courseware/deploy_cs101.py
+++ b/cs101courseware/deploy_cs101.py
@@ -1,15 +1,14 @@
 import inspect, os
 import shutil
 import unitgrade as ug
-from cs101courseware.cs101report1 import Report0
+from cs101courseware.cs101report1 import Report1
 from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report
 from unitgrade.unitgrade_helpers import evaluate_report_student
 
 if __name__ == "__main__":
-    report = Report0()
-    setup_answers(Report0())  # hidden for students; re-computes the answers file.
-    setup_grade_file_report(Report0, minify=True, bzip=True, obfuscate=True)
-    evaluate_report_student(Report0())
+    setup_answers(Report1())  # hidden for students; re-computes the answers file.
+    setup_grade_file_report(Report1, minify=True, bzip=True, obfuscate=True)
+    evaluate_report_student(Report1())
 
     # now copy scripts to public directory
     dest = os.path.join( os.path.dirname(inspect.getfile(ug)), "../cs101courseware_example")
@@ -18,3 +17,12 @@ if __name__ == "__main__":
         if f == "__pycache__" or f.endswith(".token") or "deploy" in f:
             continue
         shutil.copy(f, dest)
+
+    ## Report 2
+    from cs101courseware.cs101report2 import Report2
+    # from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report
+    # from unitgrade.unitgrade_helpers import evaluate_report_student
+    setup_grade_file_report(Report2, minify=True, bzip=True, obfuscate=True)
+    # evaluate_report_student(Report2())
+
+
diff --git a/cs101courseware/deploy_report2.py b/cs101courseware/deploy_report2.py
new file mode 100644
index 0000000000000000000000000000000000000000..5974ad50c561a7c665458b9c8276172fdab5af86
--- /dev/null
+++ b/cs101courseware/deploy_report2.py
@@ -0,0 +1,29 @@
+import inspect, os
+import shutil
+import unitgrade as ug
+from cs101courseware.cs101report1 import Report1
+from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report
+from unitgrade.unitgrade_helpers import evaluate_report_student
+
+if __name__ == "__main__":
+    # setup_answers(Report1())  # hidden for students; re-computes the answers file.
+    # setup_grade_file_report(Report1, minify=True, bzip=True, obfuscate=True)
+    # evaluate_report_student(Report1())
+    #
+    # # now copy scripts to public directory
+    # dest = os.path.join( os.path.dirname(inspect.getfile(ug)), "../cs101courseware_example")
+    # source = os.path.dirname(__file__)
+    # for f in os.listdir(source):
+    #     if f == "__pycache__" or f.endswith(".token") or "deploy" in f:
+    #         continue
+    #     shutil.copy(f, dest)
+
+    ## Report 2
+    from cs101courseware.cs101report2 import Report2
+    # from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report
+    # from unitgrade.unitgrade_helpers import evaluate_report_student
+
+    setup_grade_file_report(Report2, minify=False, bzip=False, obfuscate=False)
+    # evaluate_report_student(Report2())
+
+
diff --git a/cs101courseware/dist/Report2_handin_5_of_5.token b/cs101courseware/dist/Report2_handin_5_of_5.token
new file mode 100644
index 0000000000000000000000000000000000000000..a08c292b62bbee54114687e86fb66adda3ebd983
Binary files /dev/null and b/cs101courseware/dist/Report2_handin_5_of_5.token differ
diff --git a/cs101courseware/dist/__init__.py b/cs101courseware/dist/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1ebbeccc271cb92b6db10e6bd006fb2787a84c7
--- /dev/null
+++ b/cs101courseware/dist/__init__.py
@@ -0,0 +1 @@
+__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\xad\x00\x00\x00\x00\x00\x00\x10\x78\x6a\x3a\xbd\xc0\x4b\x66\xe8\xda\xc8\xfc\x5a\x8e\x28\xf6\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x65\x84\xea\x32\x96\xe5\xe1\x61\xe0\xed\xcc\x1f\x68\xe0\xa6\x19\xb5\x50\x43\x0d\x9b\x78\x18\x7a\x3d\xa1\x95\xec\xca\x3a\x9f\x55\xf2\x4d\x27\xc3\x91\xec\x37\x2c\xaf\x59\xa6\x5f\xec\xcc\x3f\x4e\x7a\x88\xa4\x0c\x08\xd0\x38\x85\x82\xe2\x96\x0e\xb2\xd7\x33\x78\xe3\x88\x20\x5a\xb5\x58\x58\x1b\x19\x89\xe7\xc4\xfd\xa9\x21\xf5\x3e\x6f\x67\xb8\x65\xed\x51\xa9\xb4\x6d\xf4\x27\x89\x03\x7a\xfc\x9c\xa6\xcb\x3d\x6c\xb6\x3c\x57\x7b\xb5\x17\x16\x3f\x36\x55\xb7\x74\xc1\xe4\xe1\xc2\x32\xf6\x6a\x43\x39\x28\xbe\x4d\x11\xd8\x65\x69\xdf\x73\x18\x7b\xe7\xbd\xd2\x9b\x90\x98\x1a\xb2\x57\x00\x54\x9a\x31\xed\x70\xb6\x77\x20\xef\xa4\x97\x8e\x36\x16\x21\x59\x23\xb3\x93\x15\x69\x16\x49\xf1\xb3\x1c\xa3\x5c\x00\x02', 2)
\ No newline at end of file
diff --git a/cs101courseware/dist/cs101report1.py b/cs101courseware/dist/cs101report1.py
new file mode 100644
index 0000000000000000000000000000000000000000..faaaca396b58d83702fe5f6dca4593738afb4b0e
--- /dev/null
+++ b/cs101courseware/dist/cs101report1.py
@@ -0,0 +1 @@
+__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\x1e\x0e\x00\x00\x00\x00\x00\x10\x77\x89\xb4\xc8\x07\xe3\xcc\xe2\xbb\xeb\xff\x91\x82\x41\xd1\xab\x00\x00\x00\x00\x00\x00\x00\x00\x57\xb9\x70\xe7\xdb\x7a\xc9\xf6\x5f\x71\x69\xed\xe2\xb5\x54\xf3\x40\x56\x41\xd8\x8f\x44\xbb\x6d\xc8\x85\xcc\x3b\x17\x1b\x0e\xbb\xc3\xc5\xc4\x52\x61\xd7\x4d\x60\xb3\x24\xf7\xee\xeb\xee\x4e\x0a\x94\x53\xba\xf7\x9b\x4e\x02\xee\x81\xe3\xc6\xa6\x92\x12\x11\x92\x3e\x43\xf0\x39\xd6\x91\x05\xbd\x00\xb7\xae\x6f\x53\xce\xdf\x0e\xe8\xe0\x36\xcc\x31\xb1\xfb\xc0\xab\x71\x5f\x74\x2e\x92\xf3\x6d\xb0\x66\xdd\xfa\x6b\x6b\xf8\xd5\x16\xeb\xae\x30\x39\xcb\x26\xaa\xb8\x1a\xea\x8f\x9e\xac\x16\x72\x51\xd3\x22\x80\xdd\xda\xcc\x55\xfe\xfc\x3d\xbd\x5c\x27\x8e\xb5\xc9\x77\xb5\x40\x18\xec\x2e\xfa\xd2\x19\x49\xdd\x47\x83\xe6\x1a\x05\x80\x59\x64\x79\x5b\x07\xdf\x73\x0e\x63\xdf\x38\x3c\x97\xc8\x7b\xd6\xae\x96\xf3\x05\x0a\xf5\x7e\xe2\x65\xfe\x61\x73\x0c\xa7\x3a\x28\xa4\xc2\xbe\x23\x2d\xdf\x75\x05\x71\xe5\x54\x21\x13\xf7\x87\x79\x62\xcc\xda\x1d\xd8\xc5\x6f\x45\x8f\x99\xeb\x3a\x05\x37\x1a\x4b\x87\x76\xa0\x4a\xf9\x0e\x55\xe7\x8e\xe5\x1c\x20\xbe\xbb\xcb\xd4\x6f\xd4\x0a\x72\x8f\xfc\x0a\xc4\x92\x89\xd2\xfd\x67\x85\xe8\x4e\x6f\x56\xe4\x63\xb6\x43\x28\x52\x9f\x05\x16\x7f\x35\xad\xcb\x6e\xee\x6e\x49\x4c\x44\xdf\xc0\x65\xc0\x38\xab\x46\xa6\x0a\x15\x79\xf0\x72\x02\xd7\xa2\x16\x43\xee\xa5\x72\x6f\x84\x0d\xfd\xe9\xfd\x85\xdc\x0d\x4c\xa2\x1f\x6c\xdc\x58\xc7\x6b\xd2\x6f\xdd\x33\xdf\x47\x76\x24\x08\xc7\xb2\x4f\x8b\x55\x15\xd4\x6d\xc8\xcd\xc7\x39\xff\x0a\x38\xd9\x8d\x23\xfa\x5e\x3a\xda\xd7\xea\xc1\x49\x3f\x54\x56\x20\x7b\x3d\x06\x79\xb0\x7f\x5e\xfe\x22\x14\x57\x81\x79\xea\xfb\xd7\x15\x1f\x05\x67\x8c\x7d\xf5\x30\x8b\x0c\x94\x51\x7d\x44\xb2\x36\x5c\x5e\xd1\x7e\x4f\xf5\x97\x13\x7d\xe3\x63\xd0\x06\x57\xcd\x43\x3c\xd0\xbe\x12\x12\x9f\x24\xf0\x4f\x00\x50\xc8\x17\x6f\x80\x7b\xc6\x75\x8f\x96\xd8\xa9\x23\x73\xe9\xc8\xe3\xae\xa3\x4f\x0e\xb0\x1e\x29\x0d\x06\x82\x32\x78\xf3\xbc\x6d\x50\x84\xe4\xe3\xff\xfe\xa5\xbf\xad\x5a\x08\xe7\xae\x84\x91\xac\x88\x0d\xad\xda\xb6\xc3\x59\x4d\xc2\x69\x03\xfe\x4d\xd5\xed\x59\xd9\xca\x89\x59\x2a\x50\xf5\x0f\x22\x81\xf9\x32\x90\x3d\xd8\x0d\x46\xce\x5d\x6f\xe4\x04\xf7\x5d\x76\x15\x5b\xb7\xa9\x1b\xea\xc4\x1a\xcf\x5c\x10\x23\xe0\xa2\x85\xa1\xbe\x06\x62\xeb\xf7\x36\x3f\xd4\xc8\x19\x4e\xa5\xc9\x01\x1c\xf5\x8e\xa3\x60\xef\xb9\xf5\x32\xdc\xd8\xb9\x21\x83\x34\x29\x1b\xbd\x2a\x4f\xac\xe5\x4b\xb1\xf7\xf0\xae\x02\xa2\x2e\xc9\xd9\x19\x91\x0b\x86\x99\x75\xf9\xef\x0c\xe4\x0a\x25\xd8\xde\x2e\x42\x7f\x8e\x6f\x35\xde\x72\x15\x87\xac\x1e\x96\x20\x4c\x94\x7a\x75\x87\xc2\x3b\x5d\x43\x0e\xa5\x5a\x1f\xdd\x2a\xf1\xf4\x27\xe5\x01\xea\xec\x4a\x17\xc3\x7c\xbf\xc4\xc5\xb8\x64\x5f\xbb\x11\x7d\x31\x2e\xdd\x66\x4d\x91\x7f\x4a\x0e\xf0\x56\x43\x4f\x70\xfb\x97\xcc\x11\x73\x8a\xf4\x71\xc4\xfc\xc7\x45\x10\x2b\xb0\x3f\xb5\x69\x6d\x41\x19\x83\x15\x5a\x31\x39\x68\xf4\x9a\xa1\x89\x62\x56\x91\xa7\xcd\x42\x12\x0b\xba\x1c\xc0\x30\xb8\xc4\xc2\xf2\x98\x17\x33\x59\x03\x21\x84\x1b\xf2\xf3\x7d\xac\xfb\x0f\x3b\x81\xeb\x05\xe7\xba\x8a\x59\xed\xdb\x2b\x27\x39\x5f\xd6\x0e\x4a\x36\x91\xe5\x34\x63\x7a\x27\xf6\x61\x13\xba\x06\x31\xef\x4e\x37\x15\xcb\x3c\x44\xd2\xd1\xa6\x66\xa2\x42\xa7\xdf\xc5\xe8\xf5\x64\xfc\x07\x44\x70\x3f\xac\x0e\x4a\x4c\x13\xea\x34\xb9\xec\x0a\xb3\x0e\x51\xc0\x2c\xe1\xc7\x43\xb3\x5f\x01\x69\xef\xef\x56\x80\xf9\x7a\x93\xdc\xfa\x48\x8e\x1d\x51\x3a\x29\xb6\x27\xe0\x5f\x9f\x8b\x14\xac\xb9\xa3\xaa\xb7\xdd\x64\x6d\x99\x75\xb0\xeb\x63\x98\x09\x8e\x27\x69\x65\x2a\x5c\x9b\xbf\xe6\x3b\x6f\xac\x28\x22\xb9\x6b\xb2\xd5\x1a\x1a\xe3\x3e\x08\xb4\xdc\xce\x7f\xcb\x26\xcf\x24\x59\xe9\x56\x4d\x65\xd8\x3b\x58\xca\xec\x21\x44\x5e\x65\xdd\x29\xe2\xc9\xe1\x88\x49\xdb\x90\x1b\x75\x2d\x63\x2f\xe0\x3a\xce\x1c\x3f\x5b\x2c\xba\x23\x9b\xc4\xf7\xb8\xf3\x25\x81\x47\x51\xad\xed\xa4\xae\xeb\x50\xc9\x2e\x87\x2b\xed\xf0\x40\xfc\x64\xae\x71\x03\xd8\x6a\x77\xe8\x59\xbb\x7d\x16\xb2\x95\xce\xaf\x4f\x1c\xe9\x97\x19\xe9\xf9\x3a\xe5\xc7\x8d\xb3\x60\xb2\x88\xff\xaa\x6c\x55\xa8\xe7\xb8\x1a\xf7\x4f\xc2\xf9\x83\xc0\x55\xc9\x2c\xed\x42\x5f\x67\x28\x1a\xf8\x82\x5c\x57\x58\x96\x96\xb8\xec\x34\xac\x0b\xc8\x0d\x86\x74\xe7\x0c\x3b\x8e\x1a\x26\x85\x8e\xd2\xaf\x49\x50\x66\x6e\xbe\x8b\x07\xea\x5d\x7f\xc3\x14\xb3\x12\x8e\x27\x3f\xc8\x57\x09\xd6\xe4\x21\x74\x48\x4b\x0c\x21\x5d\xde\x9c\x94\x7c\x53\x67\x09\x9f\xf6\x69\x83\x78\x8b\xbb\xc7\x79\x40\xef\xbd\x42\x96\xa3\x5f\x3f\xc4\xff\x67\x23\x20\x13\x3a\x5f\x8e\xe4\x3d\x29\xc3\xdd\xa9\x39\x44\x28\x0b\x6a\x89\x37\x4a\x1b\x78\x04\x98\x27\xfb\x1a\x88\x58\xa7\xae\xac\x35\x69\xbd\x42\x52\x76\x65\x1a\x97\xe4\x49\xcc\xb2\x7f\x9f\x2b\xd6\x3f\xdd\xba\xfd\x03\xea\x5f\x05\xaf\x03\x81\x52\xc1\x49\x6f\x6e\x25\x56\xcb\x86\x8d\x47\xa5\xb5\x67\x26\x62\xc7\x93\xd3\xc1\x55\xe5\x8d\x4c\xfa\xd2\xe4\xbd\xc8\x1b\xb0\x45\xfe\x9e\xa7\x91\x2f\x9e\xcf\x4f\x28\x53\xb9\xc8\x52\x06\xf9\x8a\x64\x1d\x23\xb4\x9f\xaa\x7c\x0e\xee\x4f\xb7\x70\x45\x2c\xaf\x40\xc0\xc8\xf6\x1e\xd9\x0d\xd8\xdf\x1e\x0b\x7b\x89\xb1\xeb\x4e\x5c\x0c\x08\xb2\xbe\x57\x60\x12\x4a\x9a\x37\xcb\xf0\xea\xe3\x81\x06\x0f\xc3\xb3\xc8\xb0\xc7\xa8\xa5\xbb\x36\xc0\x23\xfa\x07\xd8\x27\x63\xde\xa4\x6f\xb2\xc6\xe0\x96\xf8\x05\xa0\xca\x8d\x0f\x17\x57\x8c\xbc\x24\xd0\x39\x41\x6d\xa2\x64\x15\xf8\x1b\x2c\x86\x71\xe9\x6c\xb9\x6b\x13\xd8\x01\xc7\x48\x26\x3f\x2a\xde\xc9\xba\x00\x7a\x85\x3a\xdd\x43\x95\xe0\x2e\x12\x9e\x2c\xe4\x69\xab\x2e\x41\x76\xd7\xe4\x93\x6b\x96\xdf\xcc\xca\xb0\xd5\x66\xc5\x79\x21\x27\x51\x43\x0a\x76\xbd\x7f\x2d\xd4\x3d\xcf\xf7\x3a\x6c\x5d\xc0\xb8\x54\x56\x51\xd3\x84\xa4\xb7\xa7\xa6\xee\x4c\xf6\x69\x8a\xf9\x4f\x20\x86\x5c\x7c\x47\x1e\x89\xae\x56\x67\x2b\x44\xb7\xc7\xac\x06\xe2\x58\x2d\xe9\xe5\x55\xc5\xa8\x05\x63\x63\x2e\xd6\x45\x9f\xf0\xe0\xdb\x16\x9e\x4d\x13\xd3\x0a\x08\x5e\x8a\xb7\xae\x98\x74\x06\x3b\xee\x5b\x3c\x9d\xe1\xbb\xd5\x52\xe4\x31\xca\x9a\xf9\xcf\x6c\x75\xe0\x5e\x65\x18\xae\x3e\xf2\xcd\x7c\x40\x03\x27\x53\xc2\x1e\x60\x8b\x21\xf2\x00\x27\x0f\x0e\xe8\x9e\xdf\x40\xf7\xff\x20\xff\x33\x48\xdc\xa1\x80\xed\xa7\x1a\xc3\xe4\xf7\xcc\xf4\xd7\x41\x97\xbb\xde\xdc\x2c\xf0\x6e\x74\x3b\x34\x34\x94\x29\x5e\x07\x68\x76\xd0\xd3\x19\xdc\xfd\x05\x19\x18\x72\x82\x5a\x5d\xb0\xa1\xaa\x67\x58\x74\xcf\xa2\x2b\x3e\xdb\xd7\x3a\x19\x96\x75\xbb\x73\x90\xc5\x5f\x8b\xde\x06\xe5\x50\x8b\x70\x95\xa9\x24\x79\x14\xff\xef\x9a\x6b\x71\x51\xc6\xc3\xaf\x5c\xff\x4d\xe4\x95\x6f\x66\x05\xe1\x9e\x31\x0a\x45\x05\xa4\x3e\x72\x71\x81\x9d\x42\x5f\x6a\xf5\xad\xb0\xfe\x7c\x3b\xe0\xc5\x8f\xf3\xae\xfb\x0e\x5d\x0b\xe3\x2f\xa1\xb8\x53\xc0\x68\x7d\xe9\x8f\x5f\xe2\xd9\xe7\xe1\xb3\x18\xd9\xa9\x98\x6b\x16\x51\x5e\x51\xad\xce\xca\xdc\x71\x09\x54\xc2\xb0\x69\x58\x96\x04\x77\xb3\x63\x96\x82\x95\x7d\x6b\xf2\x3c\xac\xb3\x38\xbb\x09\xf7\xfe\x71\x71\xac\x90\x5c\x59\x0e\x00\x28\x8b\xd4\x6c\x0f\x80\xf4\xbe\x0a\x27\x1b\x26\x74\x6a\xc2\xa8\xa5\xc4\x3b\x80\x59\xc1\x40\xc7\xea\xdf\x7d\xad\x9d\xbb\x89\xed\x6c\xea\x5d\x50\x23\x21\x36\xc1\x54\x90\xba\x18\xff\x09\xd0\x31\xea\xbc\x37\x05\x20\x31\x20\xf6\x22\xe0\xf5\x72\x52\x1a\x91\x3f\x84\x41\x57\x6d\x47\x2d\x27\x88\x38\x3d\x96\xdc\x8e\xf3\xc6\x0c\xb5\xd4\x33\x86\xb3\xce\xdf\x9d\xe7\x8c\x2c\x4f\x72\x73\x7c\x73\xfe\x63\xfb\x98\x79\xd3\x81\xc9\xdc\x3a\x24\x63\x69\xa0\x37\x5b\x1a\x03\x38\x6d\x42\x83\xb6\x1c\x31\xf2\xea\xc7\x70\xb9\x3f\xe5\xb2\x23\x34\xdd\xc3\x9b\x0a\x15\xf5\xb8\x81\xc2\x5b\x27\x1f\xc0\xdc\xec\xf9\x2a\x92\x27\x34\x51\xa7\x2e\x43\xb1\x0e\xb5\xd4\xe7\x4e\xb0\xed\xd9\x5e\xac\x4e\x64\xde\x19\x74\x33\xcb\x7f\x0a\xb4\x72\x6f\x74\x1e\x35\x29\x71\x1a\xd0\xa0\x1d\x5b\xa8\x79\x0b\x34\xcc\x46\x8f\x91\x58\x91\x32\x2f\xed\xf0\x91\xc3\x3f\x7f\x26\x40\xa2\x4c\x50\x77\x9b\x11\x8f\x72\x25\x4c\xa8\x0f\x2b\x2f\x44\x36\xd4\x8b\x79\x73\x97\xea\x55\xb4\xff\xc0\x2f\xcd\x54\xc4\x90\xaa\x19\xc1\xce\x54\x98\xc7\xa6\x48\x48\xfc\xac\xf5\x4e\x48\xe1\x59\x98\x84\x51\xd3\x80\x51\x74\xb7\x58\xe7\x88\x0b\xd2\x48\x82\x58\xd4\xb6\x27\xeb\x60\xc5\xed\xc0\x2a\xe5\x06\xbc\xcd\xac\x65\xd4\x70\xd0\x86\x3c\x89\x90\xb6\x2f\x39\xd9\xad\x89\xca\xcb\x84\xe2\x11\x53\x03\x85\x98\x4b\x5c\xc4\x11\x40\x91\xe2\xef\x28\x78\xe1\x5e\x73\x48\x9b\xb4\x45\x4d\x87\xce\x63\x91\x62\x35\x0f\x45\x8a\x6f\xdb\xfd\x4a\xd1\x62\x54\x7d\x68\xf4\x71\x82\x58\x02\x84\x64\x24\x8a\xbb\x35\xe1\x42\x04\xa4\x65\x12\xca\xe0\x6e\x64\x25\xf0\x73\x96\x41\xbd\x3d\xa0\xea\x00\xc7\x98\xda\xbd\xc8\x4f\x0a\x4e\xa0\x3f\x69\xe9\xcd\x47\x54\xfb\x06\xc4\x62\x24\x58\x54\x9a\xfb\xb6\x8f\xe2\x2c\xa6\x7d\x6c\xa6\xf5\x43\x7d\x31\x5c\x9a\x96\x6d\xa4\xc3\x14\x06\x00\x54\x85\x27\x7f\x8f\x6b\xef\xe9\x98\x28\x93\xa1\x8f\xc2\x53\x4d\x11\x85\x97\xff\xee\x55\x6c\x3c\x57\x7a\x52\xc5\x1b\x58\xc6\xbd\x4c\xf5\xb6\x21\x89\x68\x80\xd4\x13\x6e\xee\xaf\xe4\xfa\x37\x48\x52\xde\x4a\x84\x4e\x53\x3b\x7a\x7c\x44\x5b\x9e\xab\xda\xd0\x4a\x2c\xf9\xf7\x69\xfd\xde\x0b\xb5\x9b\x97\xc6\x8f\xe1\xe0\xb7\x27\x37\xb7\xa6\xdc\xd1\x94\xe1\xee\xd1\xd3\x64\x49\x6a\xdf\x16\x5c\xca\x31\x37\x19\x64\x3d\x73\x51\xb5\xfc\xe4\x55\x61\xfc\xfb\x1b\xfe\x4a\x73\x69\xae\x9c\xd4\x39\xa9\x8a\xde\xbd\x11\xe5\xf5\x29\x74\xe2\xe5\x84\xa8\x5f\xc1\x2e\x81\xbc\x36\x08\x6d\x4d\x8e\x40\x6d\x76\x01\x70\xd5\x87\xd0\xcc\x3c\xc6\x45\xdb\x30\x08\x8a\x5e\xf7\x0a\xf6\x67\xa8\xfc\xed\x08\xe6\x37\x4f\x47\x8f\xd6\xed\x71\xce\xbd\x08\x8c\x13\x3d\x9c\xda\x6f\x83\x18\x7c\x11\x2c\x15\x0c\xfd\x95\xf5\x4a\x22\x72\xeb\xb5\xb6\xfd\x51\xc6\x69\x1b\xef\xa5\x2c\x59\x0d\xf2\xe6\x32\x5f\x5d\xb2\x47\xf4\x55\x52\xe7\x34\x39\x41\x10\x10\x72\xed\xf5\xa6\xb9\x11\x45\x28\xdb\x9d\x65\x19\x5b\x24\x5a\x68\x7e\xd1\xec\x62\x99\x4c\xa6\xb8\x34\xf6\x31\xab\x71\xbc\x6c\x1f\x9b\x02\xd0\x11\x98\xa3\x8f\x04\x95\x80\xd9\xdc\xcb\x7c\x7a\x6f\x38\x50\xd9\xd8\x87\xc5\x33\xa7\xc0\xcd\x8f\xf9\xdb\x56\x4b\x76\x96\x79\x77\x54\x0c\xdd\xf6\x75\x27\xf9\xce\x4a\x27\x1f\x48\x7d\xbb\x95\x89\xe3\x60\xc7\x4a\x70\x2a\x37\x6f\xe2\x65\x7f\x31\xd9\xc3\x71\x4b\x1a\x77\xdb\x06\x77\x6b\x14\xa6\x41\x56\x4f\x76\x64\x07\x04\x50\xe6\x1f\x5d\xa0\x5d\x94\x93\xd1\xf2\x2b\xf1\x7a\x3e\xdb\x1e\xd2\x0d\xd4\x48\x93\x55\x5f\xb0\xe1\xc4\xf7\xe3\x7d\xd0\xe9\x7c\x41\xd1\x94\x2a\x36\x78\x67\x9f\xa7\xbc\x7f\x11\xbf\x02\x3d\x84\xcb\x4d\xa0\x16\xcd\x2f\xc1\xda\x59\x44\x13\x3f\x83\xd9\x47\x21\x9b\x9b\xb2\xbb\x24\x7f\x0e\x76\x80\xff\x9e\xa5\x02\x41\xa3\xf2\x69\x2b\x0b\xb1\x69\xc3\x8b\xad\x3e\xa3\xc5\x25\xd3\xb3\x20\x8e\xd3\x65\x09\xf8\xf8\x6f\x44\x2e\xec\xc2\x17\xb3\xd9\xd3\x89\xce\xac\x3f\x36\xbe\x9f\x00\x6b\x5d\x7c\x66\xfd\xea\xb7\xdd\x1d\xf1\xf9\xc1\xa7\x59\x2a\x4b\x68\xe2\x97\xca\x30\x41\x94\x1b\x32\x5c\x19\x28\xa5\xee\x28\x6c\x27\xf1\x46\x20\x77\x44\x6c\x97\x30\x73\x01\xa6\xbb\x93\x0e\xad\xae\x7a\x83\x49\xc7\xd3\x28\x06\xcc\x13\x79\x12\xba\x42\xd6\xfd\x36\x8c\x3f\xb7\x82\x19\xbd\x0b\xa7\x43\xa1\x14\x70\x18\x02\x8d\xff\xc1\xec\x6d\x63\xe7\x6e\xbf\x5f\x61\x2e\x72\x6e\xb2\x78\xe9\x92\xb8\xca\xaa\x26\xd5\x84\x9e\xbd\xcf\x5a\x3d\x70\x03\xdb\xba\x67\xfe\x7d\x6e\x31\xdc\xa4\x2a\x0d\x59\x07\xdb\xad\x1d\xce\x28\x8d\x87\x3e\x7a\x01\x2d\x88\x9e\xc3\x87\x2b\x4c\xec\xeb\xac\x5e\x72\xc9\x0e\x2e\xbd\x42\x97\x71\x8f\x30\x41\x47\x42\x99\x5e\x63\xe1\x0a\x8e\xcc\x68\x1e\xc0\x0b\xef\x72\x90\x9e\xe7\x06\xba\x41\x3f\xb2\xbf\x99\x02\x1b\xc4\x73\xae\x40\xcb\x50\x68\x49\xfd\x90\x26\xc1\xda\x14\xa2\x9d\x59\x7b\x24\x3d\x3e\x95\xe9\x7a\x6c\xbb\x04\xff\x2e\x44\x68\xbe\xd1\x24\x4e\xf5\xfc\xee\x0a\x43\x2a\x4c\xfc\x35\xae\xb8\x33\xd4\x69\xf9\x4b\x2a\x71\x2e\x7d\x63\xb8\x09\xb9\xb9\x00\x95\x9f\x09\x35\xc1\x64\x9c\x6a\x2a\x05\x36\x81\x38\x02\x69\xdc\xf7\xc1\xd3\xde\xb6\x36\x3f\x26\x71\x84\xe3\xa6\x07\x8a\x49\xd8\x22\xe9\x40\x11\x44\x44\x3e\x91\x49\x60\xf2\xda\x11\xfe\xc0\x1b\xfc\xc2\x26\x0e\xa1\xee\x9e\x46\x6c\x59\x67\x23\x28\x89\x7f\x65\x0c\x94\x34\x06\x9d\x79\x7e\x02\x9c\x88\x18\x3b\x16\x8c\x5e\x8d\x60\x30\x5d\x9d\xbc\x6c\x06\x9c\x95\xc7\x9d\x48\xe9\x60\x1d\x38\xc1\x95\xbb\x1d\xa1\x94\x07\x4a\xf9\xb8\x62\xe7\x77\xfd\x66\xdc\x2e\xd6\xa8\xc1\xa0\x85\x11\xff\x18\xee\x59\xc4\xfe\x0a\x72\x61\xa1\x63\x92\xe0\x0d\x02\xbc\x78\x45\x68\xfa\x0a\x7b\x1c\xcc\xa8\x49\x72\xbd\xc9\x2c\xde\xac\xe1\x62\x92\xc4\xc7\x93\x72\x6e\x83\xe9\x9f\x41\x0a\x09\xff\xa0\x3a\xf3\x74\xa0\x29\xe3\x3c\xf0\xad\x77\xb4\xaa\x93\xf5\xb5\xd8\x0d\xe3\x9d\x2e\x93\x42\x60\x5d\xba\x3b\x2b\x59\x50\x30\x15\x47\xf2\x90\x5a\xfd\x2d\xcc\x04\x90\x9e\x11\x6c\x4b\xb2\x87\xf2\xd3\x02\x4d\x88\xc5\xba\x46\xbe\xc2\x32\x0c\x52\x67\xdf\x0c\x13\xbc\x87\xc7\x0f\x22\x36\xa3\xc1\x8f\x71\x6a\xa2\xec\x53\x36\xd1\x2a\xf1\xeb\x9b\xad\xc5\x95\xc9\xfa\x5e\x72\x96\x6a\x7d\x5c\x65\xf9\xd0\x54\xf4\xd8\x4f\x45\x00\xee\x96\xa5\x91\x9c\x16\xe9\x60\xd0\x72\x99\xdb\x61\x99\x8d\xc9\xaf\xb9\xc0\x2b\x2b\x35\xf7\x43\x40\xf6\x0a\xff\x8b\x40\x81\xde\xe1\xf5\xc9\x24\x32\x0f\x18\xb8\xd4\x22\xe7\x09\x52\x37\x66\x05\x2e\x24\xa2\x86\x72\x42\xf7\x81\x9f\xe5\xec\x23\x4e\x32\x9f\xd2\x30\x11\xd4\x74\x6e\x30\x0a\xe7\x04\x7a\x9b\xdb\xb2\xa2\x0f\x50\x2a\x44\xdc\x4e\xde\x9f\x34\x36\xf7\x8b\x92\x36\x39\x06\x8a\x24\xa5\xdd\x04\xfb\xe5\x74\xbd\x8a\xe1\xd7\x16\x4e\xae\x70\xd8\xb6\x80\x17\xd7\xa4\x4e\x4a\xae\xa4\x0f\x8a\x44\xe7\xa5\xee\x74\x21\x7b\x8f\xd7\xf7\x3e\xaa\xd2\x5d\xa5\xff\xae\x79\x6b\x69\xbd\x63\xac\x33\x50\xd5\x7b\x5f\xed\x58\xe1\xf9\xc8\x6b\xa0\x8f\x03\x29\x6c\x57\xad\xfd\x8d\x3b\x38\x76\x3f\x26\x2f\x41\x53\x54\xa0\x09\xb5\xa9\x35\x14\xcb\xe5\xa4\x9c\x1d\xf3\x77\x86\x67\x61\x70\xd2\xc6\x7e\xe5\x73\x1d\xcc\x05\xf6\xda\x72\x79\xd1\x84\xdf\x96\x15\x11\x1d\x49\x07\x2a\x53\x88\x1f\x1e\xf4\x49\xe8\xa0\x32\x29\x68\x5c\x6f\x3d\x64\x4d\x95\x67\x3e\xc9\x4c\x63\xfd\x58\xbb\x8a\x52\x87\x0c\xb2\x20\xd6\xf8\xa4\x70\x75\xe1\x2f\x4c\x9c\x82\xe6\xe9\x4d\x78\x14\x4e\xf0\xd8\xe8\xfc\x95\x78\xc1\x91\xfb\x24\x78\xd2\x99\x94\xeb\xb9\x4d\xdd\x3f\x40\xa6\x26\x6a\xcf\xc3\x8a\x62\x78\x10\x00\x28\x77\x1d\x63\xa5\xe7\xc3\x0c\x92\xc7\xda\xdc\xb0\xfc\x64\x7e\x26\xfd\x58\xfe\xe0\x6a\x60\x41\x63\x54\xbd\xff\x88\xa9\xa6\x90\xa6\xb3\xdd\x7f\xc1\x62\x59\x94\x0a\x99\x55\x77\xd3\xbc\xdc\xa6\x2f\xb8\xc6\x79\xf6\xcb\x8a\xf0\x99\x4f\xb1\x1b\xc9\x83\xb3\xf5\xf0\x25\x60\x57\xc8\x8c\x39\xb4\x72\xad\xaf\xa1\xde\x6e\x45\xdc\xdb\x1d\x5d\xcb\xf9\x6e\xc9\xa5\xb6\x62\x62\xc1\x91\xac\x23\xc0\xf3\x9a\x1c\x80\x33\xf4\x5b\x6d\xf9\x5a\x86\x37\xb6\x3d\xd7\x93\xe8\x08\xd6\x6c\x29\x95\x44\x1a\x47\xf7\xbc\xfb\xe9\x59\xeb\xec\xb6\x80\x34\x8f\xab\x81\x4a\x9d\x39\xe9\x07\x47\xa6\x22\x8a\x5f\xf5\x97\x3a\x39\xb9\xd1\xfd\x1d\xdf\xab\x5e\x69\xc8\x4a\x6b\x50\x4d\x90\xbf\x5c\x53\x47\x63\x82\x94\x85\x0d\xfe\xe4\xc2\xd3\xa7\x69\xb2\x4a\x11\x44\x7c\xed\x56\x90\x4a\x98\xba\x55\xdf\x68\xa7\xf1\x85\xdc\xcc\x99\x19\x89\x6b\x3d\x36\xa1\x79\x79\xcb\x16\x23\x4f\x5a\xa1\x0e\xca\x7b\x02\x9c\x3b\x64\x57\xe9\x7c\x2f\xd0\x61\xe4\x94\x42\x02\xd3\xd4\x6b\xc6\x51\x4b\x9d\x4b\x75\xbd\xf0\xad\x14\xa0\xac\xfc\xaf\xe0\xe3\x84\xe5\x70\x29\x65\x18\x25\xc0\xb6\x06\x5d\x74\x6a\x1d\x30\x4a\x29\x6b\x89\xcb\xec\x0f\xa4\xe4\x66\xc2\xc5\xe2\xb5\x2a\x11\x3c\x3a\xe5\xd5\x7a\x3b\x48\xeb\x8c\x69\xa4\x5b\x7c\x61\x82\xc3\xb0\x08\xa2\x39\xff\xc2\x89\xbe\xe3\x2b\xd9\x66\xda\xb3\x1f\xc5\x6f\xa2\x11\x11\xf8\xc6\xb5\x36\xbd\x44\xfb\xfe\x01\x58\x7a\x4c\x5c\x4f\x5d\x47', 2)
\ No newline at end of file
diff --git a/cs101courseware/dist/cs101report1_grade.py b/cs101courseware/dist/cs101report1_grade.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d77ac9bef42c73a3a4157a8ac9fdbb6cbdc4bcd
--- /dev/null
+++ b/cs101courseware/dist/cs101report1_grade.py
@@ -0,0 +1 @@
+__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\x8c\x0a\x00\x00\x00\x00\x00\x10\x8e\x9b\x14\xed\xf6\x8b\xed\xa0\x07\xe4\xca\xe9\x03\xce\x6f\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x12\xdb\x9a\xf0\x9f\xfc\x5d\x10\x62\x42\x1c\x0e\x28\x15\xd7\x34\x95\x7f\x0c\x8b\xc3\x63\x53\x00\x3a\xbf\x40\x6f\x60\x06\xe5\x7a\xdf\x36\x75\x38\xd7\xa8\x25\xb2\xa8\xd2\x2c\x86\xee\x6e\xb2\xf2\xdf\x52\x5e\x79\x58\x3c\xfb\x3d\x56\x1a\x7e\x59\x9a\x4a\x1b\xdb\xe8\xaa\x3b\xbf\x2a\x22\xe0\xcf\x69\xe1\x03\x35\x8b\xa2\xf3\x28\x7b\x9a\xb2\x13\x28\x53\x81\x75\xda\xdc\x68\x8a\x2c\xf3\x64\xad\x88\x12\x5b\x09\xe1\xb3\x92\x28\xc7\x85\xd6\x11\x1b\x9c\xfc\xb6\x8a\x54\x45\x7b\x55\x65\x2f\x87\x56\xef\xfc\xd8\x0e\x38\x47\x12\xf1\x34\x29\x93\x2b\x5b\x1e\x85\x37\x8e\x33\xf7\x3d\x9b\x4f\xf6\x37\x55\x33\xb8\x28\xec\x87\x73\x15\xcb\xde\x5f\x85\x21\xca\xee\x0a\xa7\x4a\x2d\xa3\xf9\xce\xe1\xf1\xa4\x6a\x5c\x7b\x84\xb2\xee\x09\xf5\xde\x22\x69\xb1\x24\x9e\x65\x2f\x3d\x62\xfe\xb5\x44\xb7\x3b\x6b\x20\x36\x9b\x54\xe2\x4a\xb9\xfd\x43\x49\xf5\x64\xeb\x3e\x32\x5d\x36\xa5\xad\xef\x1d\xb3\xa2\xad\x29\x94\x7b\x35\xd3\xdf\x75\x83\x9f\x56\x86\x81\x97\xf9\xa7\x22\xff\x8d\x55\x26\x82\xdb\x82\xc8\x7b\x9f\xab\x3f\x22\x7f\xcc\x23\x41\x4d\x2e\xc2\x90\xd6\x0d\x86\xa0\x06\x8a\x9f\xa1\x02\xdd\xe5\xc6\xa9\x84\xe5\x0b\xec\x0a\xa3\xac\xe6\x86\x58\xc7\x2a\x0d\x5c\x1d\xd5\x10\x8a\xbd\x1a\x79\x5c\x87\x61\x06\x41\xd7\xe0\x0d\x79\x1b\x36\xd9\xf7\x27\x8d\x5b\xab\x7e\xc1\x33\x52\x7f\x00\xd5\xe4\xec\xe0\x28\xb7\x62\x53\x6f\xee\x4e\x5b\x5d\x25\x30\x46\xd8\x60\xf6\xe8\x5a\xa3\xb9\x44\xe4\xe4\x0f\x05\xd8\x86\xbf\xf7\x3e\x17\x0e\x1c\xae\x71\x88\xdf\x41\x0a\x23\xe2\x87\xa3\x5a\xa6\xc8\x8f\x89\x55\xd9\x57\x2d\x37\xf6\xfe\x4e\x9f\xc5\x0e\x1a\xd7\x82\xdb\x4c\x86\xae\x58\x6b\xb1\x80\x71\x92\x95\x47\x47\x61\x1e\x42\xb9\x22\x7a\xb1\xcc\xc0\xf4\x75\xf5\x16\x77\xd2\xab\x9a\x7e\xb0\x2d\x66\x91\x49\x37\xcf\xb3\xf9\xd2\x87\x1c\xea\xb0\x05\x23\x12\xf7\xb7\x5b\x6d\x76\xb8\x31\x0c\x77\xa7\x2f\xa9\xbe\x53\xb9\x65\x6f\x4a\x51\xaa\x9d\x85\x9e\x69\x14\x37\xca\x79\xdc\xe1\x41\x4e\x33\xc0\x54\xaf\xd9\x67\x1d\x7e\x15\x0d\x00\x72\xea\x68\x6c\x87\x1a\x91\xae\xdc\xa7\xff\x7d\x40\x16\x83\x1b\xc0\x8c\xbc\x69\xc8\x98\xed\x76\xb1\x4d\x69\x14\x83\x1e\x2e\xc9\xb3\xb6\x6f\x0f\x67\xaa\x4a\x2c\x33\x7f\xa7\x71\xc5\x1b\xed\xc2\x2c\x1d\x15\xdb\x0a\xb1\xa2\xed\x65\x33\x39\xc2\xf7\x80\xfe\x99\x9b\x66\x47\x19\x43\x41\xb6\xaf\xe8\xb2\x1d\xba\x1e\xb9\x48\xdb\x73\x5b\x83\x0f\x28\xce\x5a\x0c\xe3\x48\x9c\xdd\xb2\x61\x39\xb0\x04\x25\x26\x2c\xf6\x6a\x5c\x62\x70\xd6\x27\x21\x08\x6b\xd6\x7a\x4e\x7f\xfb\x8b\xd9\xcc\x80\x6e\xa3\xcf\xf3\x3d\xd0\xd4\xed\x30\x63\xce\xaf\x35\x1b\x69\xed\x27\x30\x8d\x95\x61\xef\x79\xd6\xe5\xdd\x72\x4e\x61\x4c\xbc\xc7\xa2\x05\x3b\xff\x96\x1c\x19\x32\xfe\x00\xa5\xf0\x92\xad\xc0\x70\x87\x24\xd8\xcf\xd6\x0f\x3e\x35\x24\x5b\x8c\xb2\x4a\xe1\x1d\xb7\xc7\x4b\xef\x8d\xe1\x33\x8f\x3d\x1e\x44\x46\x3e\xf6\x1c\x0f\x19\x8f\x61\x22\x1a\x46\x23\x5e\x13\x0a\xc3\xd3\xb0\xcd\x84\x35\xec\x2d\x0b\xd5\x08\x42\x9a\x38\xbb\x1a\x5b\x35\x91\xae\x62\x19\x49\xfd\xa5\x1c\xd1\xba\x35\x73\x7d\x95\x07\x8c\x47\xd4\x25\xce\xcf\x4b\xcf\x2b\x50\x60\xeb\x04\x54\x69\x5b\x9a\xd9\xce\x73\x07\xc6\x10\x47\x72\x09\xa2\xff\xd0\xdd\x2f\xc7\x37\x0d\x25\xef\xf3\xb9\x1b\xe5\x73\x4b\x1f\x89\xc2\xce\x97\x0c\xba\x51\xc5\x3a\x6e\xd3\x97\xa5\xbf\x4d\x54\x0d\x5a\x7f\x6a\xed\x0f\x4e\x99\x32\x9b\xcc\x47\xb3\x92\x93\x9e\x61\x42\x6a\x4c\x24\x29\xfa\x39\xaf\x91\xde\xb0\xea\xc3\x02\xfa\x73\x7c\x6b\xe1\x11\x60\x22\x5d\xaf\x91\xda\xf5\x33\x71\xa6\xc7\x93\xe5\xc3\x23\xb4\x4b\xa2\xff\xbc\xeb\xbe\xfe\xc1\x67\x55\x3c\xa2\x59\x9c\x21\x55\xc7\x81\xe7\x87\xbb\x0d\x6c\x8c\xb3\xf3\x97\xa4\x88\xca\x13\x87\xb3\xff\xb6\xab\xca\x03\xb4\x91\x92\xbc\xd6\x4c\x7c\xe8\x00\x41\x85\xe8\x43\xf4\x76\x3d\xe5\xf2\xf9\x2a\x38\x4a\x65\xf3\x5a\x4e\x1e\x04\x11\x76\x4d\x86\x07\x67\x28\x4f\x95\x59\xa5\x47\xce\x0a\xa6\x17\x55\xdd\xbe\x92\x4d\xc7\xeb\x27\xb5\x12\x93\x2e\xd9\x37\x53\xcc\x35\x86\x21\xaf\x3b\x9c\x3b\x1d\x50\x36\x43\x09\xb1\x51\x7e\xc8\x1c\xf8\x49\x30\x6d\xc2\x31\x91\x83\xfd\x28\x7c\x50\xcf\x77\x05\x5b\x64\x2a\x54\x44\xab\xc6\x34\x7d\xdd\x41\x8f\xaf\x20\x1f\x7f\xa8\xaf\x00\x79\x0b\x73\xb5\x08\xb8\xb3\x78\xa7\xc4\xb5\xcd\xb0\x44\xb5\x51\xbd\x9b\xdb\x3f\xb8\xb0\x3a\xac\xf4\x03\xb3\x06\x2a\xf3\xdf\x02\x01\xcd\xcd\x1c\xbb\x0d\xa9\x93\xf0\xde\xfe\x19\x39\xb4\xf6\x44\xb9\x68\x04\xb7\xc4\x07\xda\x01\x19\x2c\x98\x41\xe5\xdb\x62\xa0\x52\xdb\x6c\x02\x04\xcf\xbb\x23\xf1\x98\x3c\x09\x11\xe8\x89\x63\xa4\xed\x21\x1e\xb6\x48\x57\x32\xba\x53\xdd\x92\x40\xa6\x32\xec\xd4\xb8\x0f\xf0\x1f\xb6\xa0\xfb\xce\x24\x21\xe3\xfa\x35\xd4\xea\x1d\xe6\x4b\x27\x54\x16\xc9\x88\xdd\x9b\x92\x13\x0d\xbc\x66\x33\x7c\x32\x4a\x71\xa4\xaf\x4c\xb1\x4d\x21\xfd\xc7\xd5\xb5\xd9\x46\x61\xa3\x21\x02\x4c\xfe\x38\xbf\xcf\x63\x6b\x25\x28\xed\xf6\x6e\x87\x62\x09\x96\x2b\x82\xd5\x03\x2e\x85\x8b\x8c\x1a\x5d\x81\x0a\x40\xf9\x97\x59\xfd\x05\x73\xbb\x7d\xd6\x02\x59\xf5\x48\x9e\x3b\x72\x87\x30\x18\x0d\x02\xf4\x71\xd8\x41\xbe\x94\x43\xad\x0b\x10\x1a\xe0\x53\x1c\x9d\x2e\x2a\x1f\xf0\x45\x81\x39\x45\xec\xaa\xef\x57\x48\x22\xb7\xbe\x9a\x23\x51\x5d\x94\x40\xe2\xde\x52\xf0\xc3\x8c\x68\xa8\x7f\xdb\xd9\x5e\xe6\xcc\x06\x92\x42\x3e\x8c\xd4\x45\x89\xe7\xec\xd6\x6c\x18\xfe\x68\x0f\x61\x19\xc4\xc5\xb2\xf5\x7c\xd4\x58\x61\x9a\x6d\xe0\xc7\x9f\x07\xcf\x1b\xe3\xb0\xa9\xcd\x21\x99\x88\x07\xdc\x1e\x5b\x18\xdf\x9e\xee\x22\x53\x3e\x5d\x94\x6c\x2a\x28\x98\x1c\xf4\x7c\x44\x55\xfc\xa7\x0a\x46\x9c\xa4\xd2\x9b\x36\x94\x6f\x16\x85\x77\x1b\x5b\x97\xc5\xdd\x34\xc7\xf8\xac\x9d\x86\x1d\x26\xf6\x24\x8f\xda\x39\x2d\xeb\x72\xfa\xd3\xaa\xef\x08\x85\x9a\x0b\xf9\x20\x52\x0d\x38\x1d\x40\x0c\x4c\x99\xd8\x07\xe7\x35\xc5\x35\xda\xa7\x41\xc3\x6a\x3d\x98\x9d\xa9\xd4\xa1\xeb\xc3\x52\xfe\xcd\x6f\x04\x91\xcc\x4a\x7b\x66\xbf\x77\x96\x59\x0b\x02\xb7\xb0\x07\xf3\xb7\x3a\x2a\xcd\x17\x0e\x4e\x0a\xfb\x7d\xbc\xb4\x76\x00\xf5\x7c\x66\x83\xab\x49\x82\x75\x75\xc9\x67\xe2\xdf\xf6\x3c\x8a\xa4\x4f\x9e\xc5\x88\xf8\xb8\x21\xd2\x0f\x7b\x00\x8d\x11\xa9\x9a\x5c\xdd\xcb\x24\xd0\xde\x4f\xff\x08\x0f\xf5\xfe\x7c\xbe\x7a\x82\xdd\x31\xa0\xa1\x55\x0b\xf1\x97\x0c\x61\x6f\x38\x5f\xe2\x47\x05\x65\x30\x5a\x0b\x9b\xa6\xb2\x6f\xc3\xd2\xb5\x09\x16\x2f\x44\xd2\xc8\xd3\x80\x2e\x3b\xcf\x8d\xdf\x9e\x57\x5a\x31\x2b\xa0\x64\xd7\x76\x2f\x58\x33\x68\x02\x3b\x1c\x81\xf4\x96\xbe\x2d\x97\x15\xee\x31\xa8\xba\x86\x55\xb9\x26\xf0\xdb\xfe\x74\x8f\x74\x7d\x21\x79\xb6\xe2\xd9\xc4\xe4\x43\xd2\x9b\x26\xa6\x1d\x06\xc3\xa5\x71\x12\xd8\xc5\x52\x29\xfc\x84\xbb\x16\xcc\xe9\xb0\xbd\x5d\x95\x81\xa5\x4f\x48\x6e\xb3\xef\xdb\xb1\xdb\x0a\xcd\xc9\xc5\x09\xce\x4a\xd9\x06\x5c\x87\x3c\x9d\x42\xa4\x7d\x53\xba\x88\xa1\x4f\x35\x87\x14\x63\xa1\x81\xb4\xda\x0f\x6c\xec\xfb\xe2\xfd\xb8\x13\xb5\xeb\x79\x38\x3f\x06\xe6\x75\xb0\x6c\x4d\x7d\x36\x2b\x01\xa1\xa8\x79\xf3\x54\xa6\xc2\x5c\x5c\x62\x7a\x16\x85\x83\x1b\x1c\x9d\x68\x93\x10\xd0\x7a\xfc\xad\xf5\xab\x5a\xc3\xb4\x27\x92\x35\x10\x92\xee\x45\x04\x31\xd4\xa7\xb5\x28\xbe\x0c\x16\x82\x8f\xe9\x0d\xb5\xc7\xa2\x97\x7d\x14\xaa\x8e\x9e\x51\x3d\x06\x72\xe9\xd7\x70\x31\x26\x7b\xab\xa6\x86\x96\xf0\x1e\xb8\xee\xfb\xf8\xa2\x17\x60\x8b\x79\xe2\x3f\x8d\x5d\x93\x73\x2f\xb0\x00\x0a\x07\xd9\x20\x50\x60\x6e\xd1\x1d\xbc\xce\x0b\xb5\x3e\xc1\x22\x12\x66\x64\x41\xc0\xbe\x4e\x89\x89\xc9\xce\xe3\x40\x8e\xfd\x3f\x0e\xa4\x36\x6d\x42\xca\xac\x80\x9b\xde\x8a\xc7\x17\xa1\x91\x64\x23\xa0\x64\x4a\x0c\x28\xd5\x9b\x5b\xcf\xb8\x22\xc0\x3e\xac\xbc\x22\xfc\x0e\x18\x41\x11\xb3\xc7\xfb\x63\x9c\x43\x6d\xa8\x92\xae\x0d\xd8\x39\xa0\xe9\x33\xe1\xba\x95\x7f\xc4\x15\xdb\xd8\x0d\x5a\x3e\x3e\x23\xc4\xc8\xa5\x7e\x64\x0a\xc8\x97\xbb\xe5\xa2\xe2\x96\xcc\x55\x99\x18\x9b\x20\x26\x4e\xdd\x8e\x56\x33\xce\x62\x01\x62\x2d\x6a\xde\xd6\x50\xf4\xa6\x8d\xaa\x4b\x4d\xed\x56\x4a\xfb\xf6\xbb\x45\xda\x68\xd7\x40\x11\x1c\x2d\x90\xf6\x20\x9f\x1b\xb9\xa1\x4f\x42\x75\xda\xa4\x8d\xa9\x9f\xea\xe4\xb9\xb2\xa9\xfe\x35\x97\x0f\xbb\xa1\x51\x3e\x54\x08\xfb\xf2\xfc\x8b\x73\xca\x8a\x85\x01\x75\x98\x56\x7d\xfa\x6c\x08\x11\x05\xe5\x93\x7d\x43\x66\x22\x75\x0b\xb6\x8d\xa4\x09\x4b\xa7\x13\x45\x2b\x80\xc1\xb7\x66\xbe\x62\xce\xef\xf1\xa1\x57\xea\x39\x65\xf3\x05\x9d\x7f\xac\x17\x43\xf5\x20\x6b\xe8\x90\x08\xc1\x25\x08\x76\x6c\xbf\xbe\xea\x7c\xe7\xfb\x1e\x42\x63\xd8\x4c\x50\x4a\xb0\x5e\xbe\x2e\xd9\xeb\xef\x0e\x49\x22\x3e\x09\x4a\xd9\xa3\x15\xf9\x5a\x7d\xad\x0a\x51\x4f\x7c\x30\x3d\x6d\x3a\x91\x12\x0a\xc9\x66\x08\x06\x94\xad\xff\x7a\x6d\xfb\x70\x45\x22\x91\xe3\x15\x2f\x07\x45\x6b\xb8\x24\x38\x80\xb6\xdf\xe2\x05\x3b\x4b\xc1\x6b\x57\x11\x2d\xd3\x9d\x90\x9e\x4b\x4c\x7f\x2e\x44\x40\x8c\x50\x79\x0f\x97\x2d\x5d\xf0\xfd\xf6\xdb\x8a\x0e\x4e\x7a\xd3\xc8\x81\xe5\xa2\x0b\xbc\xce\xc9\xf0\xf6\x38\x87\x18\x08\x7d\x83\xfd\x6d\x70\x69\xac\x31\xc6\xdf\x56\x0f\xcd\xa0\xac\xb4\x0b\xeb\x8a\xdd\xbd\xbc\x39\x4e\x71\x3b\xf3\x56\x36\xb4\xd8\x60\x1f\xa5\xc6\xb3\xa1\x9e\xb5\xba\x92\x8a\xe2\x81\x5b\xa9\x09\x15\x46\x24\x84\x6a\xdf\x6f\xf7\x34\x60\xd4\xe2\xda\x92\x92\x64\x11\xa4\xfd\x84\xd6\xb7\xe5\x7a\xca\x2f\xdf\xa4\x47\xcb\xe1\x84\x6b\x0e\xe8\x2b\xda\xe3\xfc\x1a\x82\xaa\x46\x39\x7c\x85\x93\x35\xe3\xd4\x90\x4f\x8f\x2d\x51\x51\xf0\xcc\x1e\xd3\xfa\x5b\x97\x2c\x18\x1e\xcc\xd0\x7d\x29\x92\x33\x8a\x19\xc5\x70\x6d\xc3\xb6\xac\xda\x2e\xda\x28\x99\xb0\x76\x8e\x25\x86\x0d\x8e\x7c\xa5\x53\xa3\x44\x87\x7f\x4f\xbd\x79\x30\x29\xdc\x63\xca\xeb\x96\x29\x9f\x39\x3d\xc2\x75\x1f\x0b\x32\x69\x28\x41\x24\x99\x6b\x08\x4c\x46\xf3\xd5\xa1\x77\x33\x07\x3b\x43\x47\xd6\x75\xb2\x2d\xa5\x8b\xa5\xdc\x94\x71\x78\x40\x28\xc9\x8f\xca\x56\x23\xc3\x21\x58\x07\x4c\x25\x3f\xd3\xc9\x10\x47\xde\x2b\xf1\x23\x3f\x15\x70\xda\x2d\x0c\xb2\xd7\xa0\x13\xee\x6b\xd5\x0e\x45\x87\x19\x78\x17\x69\xb0\x59\xe7\x47\x09\x8c\xee\xf7\xd9\x3f\xd8\xb7\x4c\x52\x0f\x5e\x99\xe6\xdf\xb3\x1a\xd1\x6f\xbc\xe3\x73\x46\xbc\x81\xd6\xc8\x21\x87\x8e\xb3\xce\xec\x8b\x45\xea\x74\x0a\x1b\x24\x15\xa8\x7e\x24\x0a\x0f\x78\xe9\x39\x5b\x6b\x5f\x6b\xb2\x84\x1f\x3a\x00\x93\x61\x67\x51\xa0\x0d\xb7\x97\xdd\x8c\x42\x25\xfb\xce\x05\x58\x76\x1b\x87\xb1\x86\x17\x6e\xe6\x6b\x7d\x18\x93\x87\xf9\xe4\x20\xa8\x52\xc6\x40\xff\xfa\x8f\x60\x81\x69\xf4\x5d\xfe\x7c\x99\x07\x8a\x3d\x61\x65\x60\x4b\x37\x18\x15\x0c\xf0\x7f\x44\x4b\x32\x90\x03\xf4\xc0\x29\x0b\xfb\x26\xc4\xfa\x08\x01\x32\x68\x9b\xef\xc0\xcd\x58\x99\x84\x68\xb2\xf2\x0f\x22\xd8\xc6\x0f\xaa\xdc\x6e\x29\xe7\x84\x73\xe1\x2c\x58\x2c\xdd\x08\xd3\x3b\x89\xe3\x94\xa4\xe5\xaa\xe3\x8a\xde\xfb\x4d\x72\x74\xa6\x4b\xe2\xe8\x06\x72\xef\x69\xc9\x71\xa5\x8d\x35\xe7\x8e\x28\x12\x8a\x01\xff\x0d\x58\xe1\xb1\xaf\xd9\x99\xfc\x8a\x09\x87\x60\x73\xe7\x50\xfe\x8a\x7a\x1b\x2b\x14\x3d\xdf\x1f\x6f\xe4\x02\x4a\xfd\x64\x5a\x81\x8f\xcd\x13\x6c\x2a\xab\xbf\x3f\x15\x37\x95\x8d\xc9\x18\x67\x27\x09\x31\x34\x39\xda\x6d\x4b\x98\xca\xa4\xa0\x50\x88\x32\xe5\x8b\x22\x7c\x14\xdb\x8d\x60\x10\x29\x18\xd3\x1f\x37\x9f\x17\x7d\x34\x07\x5b\x84\x94\x14\x23\x85\x47\xde\x6d\x93\xda\xfc\xd1\x08\x85\x12\xe0\x66\xf5\x09\x04\x12\xe7\x83\x74\x72\xf4\x32\xa0\x66\x1a\xc5\x12\x40\x98\xa8\x90\xa1\x4f\x39\x16\xe2\xb6\x61\x52\x7f\x38\x19\xa6\xc2\xfb\xf0\x08\x0b\x5d\x40\x12\x8b\x1b\xb5\x48\x22\x04\x77\x7f\xf7\x42\xcd\xbe\x9e\xe8\x54\x4e\xb8\x99\xeb\x91\x0c\x93\xa0\x6c\x08\x5e\x98\x74\x5b\xce\x99\xbf\x01\x0f\xad\xc0\x49\xcd\x4c\x3b\x1f\x07\xc8\x54\x7d\xef\xf1\x4b\x29\x8f\xe9\xba\xef\xce', 2)
\ No newline at end of file
diff --git a/cs101courseware/dist/cs101report2.py b/cs101courseware/dist/cs101report2.py
new file mode 100644
index 0000000000000000000000000000000000000000..f8c2ab91d9e5f17dcce19c5ce8cb006b28cae79a
--- /dev/null
+++ b/cs101courseware/dist/cs101report2.py
@@ -0,0 +1 @@
+__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\x51\x09\x00\x00\x00\x00\x00\x10\xb2\x65\x2f\xdc\x18\xb6\x5f\xe6\xb1\x27\xb4\xd3\x2d\xd1\x68\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x30\x24\xbd\xb2\xb5\xb4\x43\x59\x7e\xa5\x88\xe6\x16\x47\x79\x7c\x72\xa8\x07\x8b\x95\x11\x07\xf7\x06\x22\x25\xfe\x10\x66\xd2\x0d\x6e\xe8\xff\x0b\x06\xc1\xb5\xa1\x58\xd1\xbe\x31\x79\x9d\xf3\x6c\x56\x85\x4c\x31\x8e\x5f\xe5\x36\x99\x70\x3c\xa8\xd8\xb1\xe5\x6f\xd4\xb9\x51\xfa\xa7\x94\xd5\x6d\xc2\x4b\xe2\x4f\x21\x6e\xfa\x3b\x65\xb7\x39\x7d\x8f\xe6\x6d\xd1\xaf\x03\xaa\xa8\x85\x57\xaf\x1c\xc5\x03\xfd\x67\x14\x53\x13\x95\x93\x20\x82\x22\x15\xc2\xbd\x1a\xe2\xe0\xdf\x8e\xb8\x28\xb7\xdd\x96\x6b\x81\xb4\x83\x19\x91\x9d\x96\x1d\xa7\x62\x94\x58\x96\x81\x18\x29\xea\x22\xd0\x73\xc7\x78\xcf\x03\x2e\x52\x6d\x0c\xdf\x09\x36\xdb\xfc\xfc\x22\x39\xcc\xac\x83\x3d\xa2\xd8\xf6\x2f\xf1\x3b\x9f\xf6\x6d\xb2\x49\x0c\x35\xf8\x23\x07\xc8\xad\xc3\x69\x95\xc4\x71\xf1\x26\x4b\xd8\x70\x05\x29\x4b\xe8\x64\xf4\xb0\xd7\xc9\x4a\x60\xea\xb4\xc2\xb4\x65\x8c\xcc\x28\x6d\x45\x62\x35\xa8\xd8\x03\xb1\x87\x89\x52\xd6\xcd\x39\x35\x73\xc7\x44\x8c\x8e\x69\xf1\xda\xe8\xda\xe1\x13\xf8\xe3\x83\xc2\xdd\xb9\x00\xf6\xfc\xf9\x6e\x9b\x10\x2c\xab\x74\xa5\xe8\x53\x3c\x64\x61\xf1\xd7\xed\x8a\x27\x2a\x1a\x22\x26\xc9\xce\xb4\x6e\x04\x27\xca\x89\xd5\x20\x18\x76\xa2\xa0\x81\x44\xd2\xac\x9f\x2d\xf1\x7b\x6e\x0d\xc4\xa3\x02\xcc\xa5\x0f\xb6\x19\x1b\x0d\x1a\x56\xa0\x50\x05\x54\xd7\xb7\xef\x7a\x75\xd0\x11\x3d\xaa\x4b\x07\x94\x0c\x4a\x11\xa4\xfb\xd0\x77\xd5\x0f\x6d\xd9\x5e\xd0\x08\xb1\xef\x15\xa4\xee\x53\x19\x33\x79\x8e\x1d\xa8\x35\xec\xd1\x69\x2a\xd6\x63\xd2\xd5\x09\x7f\x40\x19\x57\x52\xae\xc7\x93\x40\x01\x62\x17\xc9\x89\x75\x64\x02\xa0\xf9\xf3\x3a\xc1\x9e\x8d\x98\x44\x84\x6c\x95\x25\xf9\x61\x8d\xb9\x1c\x3d\x25\x7c\x7f\xa3\xa9\x33\x84\xee\xde\xaa\x3a\xd7\xfa\x3a\x47\xe9\xe8\xa2\x2a\xcf\x73\x84\x8f\xcf\x44\xb2\x29\x8e\x6a\x05\x43\x24\x66\xf3\x51\xd0\x0b\x69\xc8\xf6\xef\x59\x19\x9d\xc2\x48\xb8\x12\x14\xf3\xaf\x72\x44\x21\x48\x81\x78\x93\xa7\x08\x69\x02\xc7\x99\x1d\x6b\x44\xa1\xb0\x8c\xaa\x62\xd2\x2e\xd2\x2f\x00\xf1\x45\x5e\xb1\x56\x83\x0e\x2a\xb0\x86\x03\x8d\x83\x29\x30\x99\xa9\x03\xe6\x1c\x2e\x8d\xac\x95\x69\x8e\x1e\xcf\xb4\x0b\x40\xb7\x59\x7e\xec\xd3\x9a\x68\xf6\xd0\xa1\x73\x06\x72\xec\x1d\xe1\xde\xe9\x8a\xbd\x3a\xe6\xeb\x3b\x99\xc7\x2d\x6a\x3d\x92\xd9\x52\x45\xf7\x0c\x2c\x67\x63\xe5\xb1\xb4\x89\xeb\x9f\x33\x73\x12\x0a\xfb\x67\xc2\xe4\x61\x34\x7d\x44\xd6\x1b\xd0\x52\x3a\x9e\xa9\xb3\x3f\x5d\x4d\x02\x2e\xab\xab\x65\x5f\xce\xea\x25\x24\x20\xed\xbe\x21\xfa\x76\x3f\x47\x43\xb1\x62\x31\x0b\x91\x4e\x54\x82\x93\xcd\xe0\xc6\xb2\xf5\x86\x7e\xb1\x8c\x8a\xa6\x6d\x81\xa5\x2f\xc8\xec\x13\x53\xce\xdd\x76\xef\xab\x7c\x88\xeb\x84\xf6\x8e\x59\x7c\x0f\x9b\xab\x17\x8d\x9b\x91\x5b\x1f\x47\x88\x56\x8a\xba\xcb\x04\xa0\xe7\xf3\xa0\x2a\x63\xfb\x57\x9d\x17\xfb\xfe\x87\x34\x68\x48\x84\x71\x65\x77\x80\x13\x65\xb4\x2d\xb3\x74\x17\x93\x5a\x3b\x1b\x05\xe0\x85\xc6\xf2\xb1\xe1\x59\x76\x95\x19\x0b\x21\xfe\xa6\x2d\x39\x00\x6e\x19\xe7\xe2\xe3\x81\xde\xbb\x90\x3f\x90\x24\x9f\x19\x0c\xc9\x9e\xa5\x1c\x2a\x98\xb0\x86\x73\x17\x89\x7b\x4e\x55\x16\xe5\x75\x6b\x45\x15\xf3\x26\x33\xb0\x9a\x95\x9c\x47\x15\x71\x87\x04\xa3\xe0\xa3\xa0\x84\x84\x74\x3e\xf9\x8f\xcf\x2b\x38\x5b\xb6\x21\x80\xf4\x24\xb0\xb6\x41\x3d\x81\xeb\x1b\x41\x06\xb2\xb7\x99\xfc\x5d\xa3\xd4\x9f\xf2\x5b\x60\x72\xf5\x50\x09\x3f\xff\x06\xfc\x46\xbd\xa9\x96\x45\xa2\x37\xa6\xfc\xc9\x48\x52\x0b\x35\x73\xc3\xc0\x9d\xd6\xd6\xe0\x2f\x24\x47\x97\xb0\x56\xbe\x5b\xc6\x31\x90\x98\xd7\x6f\xc1\x03\x49\xfe\x76\x1e\xf8\x45\x6e\xfb\xae\xa0\xc0\xff\xcb\x70\x63\xd6\xe9\x2c\xe0\x5e\x8f\x0f\x58\xc2\x63\xa9\x67\xcb\xe8\x7e\xd6\xff\x89\x35\x35\xaa\x98\x96\x4e\xf9\x3b\xba\x03\xf7\xa4\xff\xdd\xd4\xe6\x7d\x7b\x52\x3a\x2b\xa1\x18\x91\x90\xc8\x05\xa1\x73\xd2\x06\x42\x7c\x32\xf5\x7c\x74\x39\xab\xa7\x2f\x27\x08\x98\x44\xfe\xfa\x1b\x2d\xf3\xf3\x89\xd7\x98\x27\x09\xb3\xaa\x2f\x20\x2e\xd5\xc8\xc0\x15\xeb\x92\x04\x5a\x09\x7f\x2e\x6b\xd7\xbb\x93\xe7\x02\x01\xe5\x12\x9d\xe0\x2b\x71\x7b\x19\x8d\xae\x9f\x21\x8c\xf5\xe7\x2e\xa0\x26\xc1\x4f\x26\x13\x66\xa3\x84\x8c\x6a\xa7\x7b\xd3\xd1\xda\xd5\x5b\x6c\xbb\xa4\xb6\x28\x2e\x6f\x33\x5e\x42\xd6\x76\xfa\x80\xd7\xd2\xf3\xcb\x8b\x6d\x48\x20\x7a\x21\xeb\x03\x73\x64\x2e\x7f\x5e\xae\x29\x41\x80\xbb\xcf\xd6\x18\x20\x94\x32\x05\x06\x3c\xba\x92\xcb\x28\xd0\x02\x00\x9c\x5b\xbd\x33\x47\x53\xd4\x01\xad\x69\xa7\xfd\x41\xce\x3f\xc3\x48\xf6\xd4\x82\x93\x97\x23\x99\xd4\x6f\x31\xb4\x8c\x63\xb8\xe2\x0b\x23\xed\x48\xf8\x18\x57\x89\x15\x8f\xc5\x1d\xd0\x04\x2b\x67\x96\xd1\x70\x79\xbe\x27\x79\xc0\xc4\x69\x86\xe6\x72\x6f\x32\x38\xc9\x37\x19\xa1\xc0\x0c\x43\x89\x17\xd3\xe0\x02\x98\x9a\x9d\x8f\xad\x1d\x96\x7b\x62\x8a\x4b\x74\xe8\xf1\xf3\xa7\x63\xbc\x7d\xea\x55\x77\x11\x92\xa8\x3a\x49\x9f\xc0\xbb\xea\x39\x7d\xdf\x3c\x72\x08\x18\x55\x83\xc5\x6d\x73\x3d\xcf\x1d\x9e\xe0\x24\x0e\xa1\xb9\xf6\x6d\x32\x11\xfc\xd4\x36\x59\x89\x11\x5d\xad\x1b\x5a\x44\x02\x17\x37\xcb\xde\x2c\xb2\x02\x1b\x57\x1f\xa3\x18\x36\xa8\xd0\x5f\x96\x5c\x5f\xcb\xe2\x55\x9c\x74\x67\xf5\x63\x6d\x07\xed\xdc\xaf\xca\x28\xa8\x76\x2a\xcf\xa8\x1f\xef\x9e\xbf\xad\xc1\x72\x66\x17\x51\xfe\x32\x1e\xbc\x0f\x38\x66\x05\x36\x7e\x29\x2f\x1e\x88\xdc\xec\x92\x16\x13\x3e\xde\xa5\x5e\x21\x3e\x6c\xd3\xdf\xa9\xeb\xbb\xb0\xc9\x33\x38\x92\xae\x1f\x72\x87\x63\x13\x6c\xde\xa5\x24\xbb\xf6\xb2\x65\x4f\xf1\x13\xc7\xb7\xf7\xbb\x15\x0a\xfd\x03\x19\xb1\x38\x2a\x91\xe0\x2c\x08\x39\xc7\x28\x00\x64\xc1\x17\xab\x6c\x0b\x92\xb7\x52\xbe\x25\x3b\x08\x87\x70\xa5\x2e\xb3\xfa\x33\xf1\x8d\x68\x04\x92\x43\xb8\xc4\x99\x1a\x56\xf1\xc7\x43\x21\x04\xfd\xaa\x42\xe9\x0d\x35\x94\x71\xd9\xf7\x1c\x46\x30\x95\xb1\xe7\xb9\x63\x8b\x32\x0d\x0e\x17\x3c\x67\xae\x83\xc7\x2d\x75\xfc\xf5\x9a\x43\x70\xfb\x38\x8b\x84\xd5\xd0\xd5\x05\x0d\x8b\x4d\xd3\x65\x03\x34\x42\xee\xde\x3c\xba\x07\xda\x7d\x49\xec\x62\x2c\x1f\x7b\xda\x96\xb3\x54\x2a\x9c\x15\xdd\xb6\x1a\x51\x86\x9d\xe4\xe5\x9c\x58\xcd\x26\x63\x1e\xe8\x6f\x22\x6e\x91\x9d\xb9\xfc\x55\xd8\xa4\x96\xb2\x35\x7b\x40\x49\x40\x89\xd9\x99\x13\x59\x83\x54\xe6\x68\xcf\xc2\x89\x77\x85\x11\xaa\xd9\xf5\x94\x34\x18\x60\xcb\x42\xb4\xcb\xdb\x5b\x69\xbf\x3b\x20\xd9\x6a\x8b\x61\x49\x9a\x1e\xad\xa2\xd4\x68\x5b\xa6\xdd\x81\x75\xd0\xbb\x5c\x77\xc2\xe8\xd6\x55\x97\x3a\xc7\x62\x00\x90\xf0\xd8\x7c\xd7\xfb\x3b\x4d\xdd\xbb\x6c\xdc\xb3\x76\x1b\x44\x3f\xa4\xec\xf6\xc2\x1f\x2d\xd3\x25\x85\xa5\x75\xe6\x29\xbf\xb1\xcf\x35\x22\x68\x2d\xf5\x3b\x98\xd5\xe2\x28\x16\x08\x79\x0f\xa5\x45\xd6\x88\xb2\xe4\x2c\xf6\xe9\x34\x9c\xd9\xfb\xdc\xb6\xdb\x14\xad\x74\xbc\xbb\x6a\x53\xa5\x65\x08\x11\x03\xc9\x83\x97\x5e\xbf\x98\xce\xba\x0e\x7d\xef\x40\x99\xe3\xc6\x67\x8a\xa0\xf1\x55\x95\x19\x5a\x41\x06\x1b\xfc\xe5\x73\x85\x68\x82\x15\xe2\xa8\x5d\xa2\x6b\x96\xcf\xea\x74\xff\x3f\x06\x1b\x35\x3b\x6d\xae\x4b\x15\x3a\x05\x02\x49\x34\x67\x46\xf1\x9a\xce\x2c\x9b\x73\xb5\x9d\xa9\xb9\x4c\xb4\x98\x57\xc8\xa0\x39\x6f\x16\xc3\xec\x98\x57\x92\x1f\x48\xdb\xe0\x13\x8d\x6e\x0e\x80\x69\x89\x36\x56\x9c\xab\xa7\x92\x12\x79\x64\xd0\xf7\x61\xb8\x29\xf9\x46\x8d\x02\x3d\x7d\x47\xc8\x4f\xe9\xb6\x0b\x42\xf1\xd3\x03\x68\xd4\x2d\x13\xe3\xc3\x8e\x59\x97\x61\xa8\x9d\x94\x90\x80\xd0\xea\x15\xfa\x4c\x74\xc3\x5e\x5e\x19\xc4\x4c\xc2\x10\x23\xc7\x0b\x92\x43\xe2\x0e\x97\x49\x5d\x01\xae\xad\xe6\x49\xa5\x3d\x04\xa9\xc6\x50\x7c\x8e\x09\x9d\x3c\x33\x49\x6b\x53\x76\x3f\x89\x12\x5b\xf5\xf2\xab\xda\x6b\x95\x6a\xd9\x63\xc1\x13\x30\x00\x92\xa2\x8c\x7e\x18\x45\xbc\xb8\xd5\x52\xf1\x48\x61\x44\x86\xac\x3e\xec\x1b\xc2\xa3\x91\x02\xc6\x8f\x3c\x28\xaa\xf2\x1c\x05\x9f\xa4\xeb\x32\x90\x68\x2f\xbf\x1f\xcb\x32\x71\xae\xbc\x4a\x83\x4e\x22\x8f\xdb\xdd\xd0\xbe\xcb\xe8\xf6\x71\x64\x4d\x09\xc6\xc2\xc8\xe1\x3e\xa6\x25\xa1\x87\xb4\x46\x43\x40\x50\x8a\x34\xf6\xef\x4c\xac\x55\x68\xe3\xee\x49\x3f\xea\xb3\x50\xa0\x1b\xb0\x7b\x7e\x0a\x22\xad\xcf\x83\x1b\x54\x28\x1f\xfd\x38\xa7\xf4\xe0\x77\x9a\xb5\x45\xac\x32\xb4\x6e\xd0\xfe\x5d\x86\xaa\xdf\x8e\xd9\x07\x73\x45\x4b\xc0\xcb\x54\x44\xe1\xf6\x7d\x4d\xe4\x1c\xc7\xfa\x50\xd3\xd5\x2f\x04\x12\x46\x7f\xf2\x46\x73\x86\x5e\xa4\x37\x63\x5a\xc0\xcd\x85\xff\x96\x40\xda\x35\xb4\x1f\xf9\x3b\x43\x4a\x37\x3d\xcf\x05\x05\x8d\xd1\x42\xad\xb0\x1a\xa9\x19\xb3\xfb\x16\xb5\x25\x4b\x21\x9b\x4b\xb6\x59\xf1\x7b\xed\xe8\x45\xf3\x62\x4b\x7b\xa9\x8d\x0b\xc1\x0a\xd8\x35\x09\xb5\x22\x7e\xc4\xac\x63\x22\x7c\xaf\x42\x91\x78\x1f\x25\x61\xeb\xf3\x64\xb1\xaa\xd3\x24\x78\xfc\x6b\xc1\x12\x91\x86\xce\x4e\xb7\x8f\xd7\x2b\xeb\xab\xc9\xa7\x33\xb1\x52\x35\xed\x19\x90\x26\xfd\x14\x3b\x9e\xfa\x92\x21\x64\xce\x96\x5b\x59\x56\xb4\xfb\xc3\x70\xcd\xbd\xd4\x7a\x80\x01\xba\x5e\x38\x8d\xf3\x07\x73\xfb\x26\x27\xba\xe9\x11\xb8\xda\x91\xb8\xc9\x4c\x84\x41\xa3\xae\xf4\x77\x72\xcf\x7d\xff\x24\xbf\xd0\x80\x5b\xbf\x23\x3e\x43\x2e\xd2\x8b\x05\x8e\xfc\x05\x04\xdb\x04\x41\xff\x2f\xea\xf9\xa7\x78\xa7\xee\xf8\x03\x3d\x9c\x7b\xe0\x5f\x9b\x8d\x3d\xfd\xf3\xf3\x54\x95\xf8\xd0\x9e\x9f\x71\x87\xc4\xb8\x91\xfe\xb0\xf4\xcd\x06\xa4\x9f\x06\x78\xbd\x28\xa6\xc9\x9f\x1e\xc5\xcf\x7b\x58\x52\xbb\x06\x7d\xbc\x7b\x14\xba\x69\xa9\x26\x3e\x88\xbc\xfb\x0e\x2a\x17\x46\x00\x5b\x0e\x56\xfe\x8d\x58\xca\x82\x82\x6f\x28\xc6\x0d\x32\x59\x95\x15\x73\x1c\xd8\x4d\x62\x48\x8f\x9b\x4e\xe1\x38\xb2\x3d\x28\xdf\x47\x5d\x17\x89\x9e\x73\x69\x30\xc6\x75\x11\xb3\xa5\x50\x90\xd0\xf6\x36\xe6\xf2\xf8\x54\x5f\xc2\x71\x59\xb8\xc0\x7d\x7f\x0c\x88\xb2\x9e\xa7\x46\xad\x22\x0e\x0f\xf6\xfb\xdf\x45\xb5\x2b\xff\xbc\x4b\xcd\x49\x7c\x1b\x72\x37\x6e\xec\xdd\x0f\xda\xf3\x92\xbe\x99\x3f\xa3\x87\x7d\x94\x6f\x86\x44\xf9\x3c\xd0\xab\x2a\x9e\xa6\xc3\x39\x24\xa7\xfe\x83\x04\xef\xf7\xb1\x95\x11\x6c\xd5\x96\x48\x54\x6b\x27\xdf\x3d\x34\xc9\x97\x8e\x21\xcc\x94\x39\xac\x99\x36\xdf\xe0\x9b\xac\xc4\x24\xe4\x21\xdf\x9d\xb5\x51\x80\x47\x36\x49\x0e\x84\x2b\x77\x3a\x36\x62\x36\xe4\x3d\x78\x62\x65\x43\xa7\x6c\x1c\xec\xf6\xec\x48\x31\xbe\x8b\x9c\xdc\x6b\xb4\x04\x48\x07\xd8\x94\x24\xe2\xd9\xeb\x1f\x9e\xea\x82\xc3\xd8\xb5\x4a\x49\x3b\x0a\x2d\x20\xd6\x95\xa8\x62\xaf\xd8\x14\x6e\xab\x55', 2)
\ No newline at end of file
diff --git a/cs101courseware/dist/cs101report2_grade.py b/cs101courseware/dist/cs101report2_grade.py
new file mode 100644
index 0000000000000000000000000000000000000000..a70d6e1cfd4a1eab819b9547dd8d13a87ebde423
--- /dev/null
+++ b/cs101courseware/dist/cs101report2_grade.py
@@ -0,0 +1,3 @@
+from pytransform import pyarmor_runtime
+pyarmor_runtime()
+__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\x5e\x11\x00\x00\x00\x00\x00\x18\xd7\x18\x08\xba\x0d\xc9\xff\x2d\x72\xc4\x7b\x63\x27\x39\x52\x6c\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x62\x1b\x63\x8f\x73\x1e\x0d\x35\x9c\xe8\x12\xf8\x74\x24\xd0\x17\x14\x8e\x76\x67\xd8\xe1\x9e\xa8\x29\x65\x65\x91\x9e\xcc\x78\xdb\x26\xb5\x86\x51\xe2\x84\x14\x06\x2b\x08\x53\xeb\x11\xc5\x27\x95\x62\x38\xd9\x74\x45\x83\xdc\x09\xd5\xc8\x68\xdb\x30\x9b\x97\x27\xdf\xfe\xac\x67\xd6\x07\xa6\xe2\x09\xfd\x07\xf7\x8b\x63\xe8\x58\x36\x4e\x54\xd4\xac\xa4\x75\x94\x9b\xcf\xcc\xba\xe6\x3d\xaa\x6f\x60\x2f\x73\x5b\x57\xb4\x7c\x4d\x23\xa6\xcd\xa3\xf4\xd4\x21\x46\xaa\x13\x67\xe4\x0e\xa3\x21\x2e\xa5\x29\xac\xa1\xf1\x6a\x33\x89\x28\x16\xdd\xd1\x6a\xf8\xe4\x31\x43\x9f\xd5\x6d\xda\xeb\x30\x7c\x46\xda\xb8\x7e\xf3\x17\x43\x52\x8d\x1d\xcb\x9c\xd4\xbe\xbd\xae\xde\x6e\x55\x17\x56\x2d\xb7\x69\x1f\xc0\x36\xfd\x98\x9c\xb9\x1d\xbf\xb3\x8c\x95\xf2\x30\x13\xac\xfb\x31\x3c\x57\xb3\x71\xac\xe4\x5c\x33\x12\x83\x54\xca\x59\x83\x12\x34\xdf\x6c\x94\x46\x27\x56\x9d\x80\xaf\x54\x33\x83\x47\xc8\xc5\x5f\x1c\x5d\x4d\xa5\x3f\x8b\xc1\x57\x41\x5f\x26\x8b\xa9\xac\x3d\x11\xb0\x33\x31\x90\x37\xec\xdf\x9f\x65\x01\xfe\x57\x69\xe1\xde\xf8\x75\xc8\x1b\x36\x2b\xc8\xfd\x1f\x0f\x51\xed\x6b\x86\x7f\x65\xd0\x3f\x75\x02\x5b\x83\xeb\xda\xf6\xce\xd7\xe4\xc0\x85\x50\xf2\x33\x14\x75\x6b\x88\x07\x98\xb9\x8a\xa2\x37\x53\xad\x89\x1a\x99\x58\xb4\xcf\xaa\xbe\xd3\xfb\x34\x4e\xfc\xc2\x7e\x93\xbf\x3d\xf0\x5f\xbf\xbd\x26\x0a\x65\xd1\x52\xf4\xb4\xb7\x9f\xbd\x3c\xf2\x71\x62\x83\x43\x76\x39\x30\xb0\xe9\x26\x39\x3d\x35\x45\xe2\x5d\xe2\x90\xdf\x75\x7f\xc3\xe7\x23\xfe\x09\x63\xc4\xff\x72\x21\x5d\xf0\x0d\x6d\x82\xa4\xaf\xf5\x45\x1d\xec\xf9\x60\x92\x08\xdc\x8b\x59\xf7\xaa\xfe\x47\x70\x62\xaf\xa5\xbe\x0b\xef\xa9\xef\x42\x99\x14\x39\xc2\xc6\x8c\x36\x89\x20\xc1\x17\x5b\x87\xe5\xdb\xac\x70\x56\xd5\x50\x62\x6e\x6b\x3d\x61\x76\x26\xbd\x4c\xcb\x02\x2c\x76\xed\x4d\x74\x5e\xb0\xc8\x89\xd1\x55\x02\x9f\xff\x35\xb4\xe6\x98\x30\xa1\x91\xe8\xe3\x1b\x8f\x05\xa0\x5f\xe7\x6d\xc2\xf7\xe9\xcb\x7c\x75\x24\x5d\x15\xdb\xf1\xe8\x14\xaf\x0b\x77\x0b\x27\x7e\x98\xe5\xad\x91\xb0\x9e\x00\xc3\xd8\x91\xd1\x57\x2f\x81\x13\x8b\x91\xc7\x73\xe7\x4f\x5a\xff\x52\x7a\xfc\xea\x0d\x77\x5d\x40\xd3\xee\xe2\xde\x1b\x57\xda\x27\x9b\x3b\x02\xf7\x08\x3b\x5f\x05\xec\xf7\xc9\x23\xaa\xb9\xb1\xc1\x65\xf0\x3e\xb8\xda\xa5\x92\x7d\x64\xec\xa7\x1c\x43\x67\x18\x07\x8f\xa7\xf0\xe1\xf3\xd5\x9e\x23\x8a\x7f\xfc\xd4\x55\xf5\x3a\x8d\x74\x79\x70\xfc\xf8\xac\x04\xa1\x9f\x4a\x51\x61\x1a\x1a\x17\x3c\x46\xd1\xd6\xe2\x5a\xc9\xec\x6d\x4e\x10\x2e\x42\xae\x5f\x33\xc5\x75\x75\xec\x6e\xc5\x9e\xfc\x4e\x9c\x04\x20\x92\x51\xd4\x4a\x81\xdb\x53\x39\x43\x94\x5b\xa2\x24\x0f\xbf\x37\x68\x70\x95\xde\x29\x46\x0d\x90\x18\x16\x34\x10\xf1\x80\xb7\x63\xf8\x24\xab\x0f\x15\xfa\x40\x62\x08\x1f\xb1\xc3\x47\xd2\x4d\x76\x8d\x56\x13\xf9\xe0\x04\x28\x13\x86\x33\x77\x53\x0b\x2b\x49\x05\x69\x06\xee\x42\xa1\xae\xf5\x10\x31\x88\x64\xe0\x14\x66\xea\xb5\xb9\xe2\x8a\x10\xf0\xa6\x68\x5c\x7c\xcc\x20\x1a\xd9\xac\xda\x7e\xfa\x24\x3b\xed\x8e\x6a\x5e\xf1\x0f\x20\xe3\x51\x01\x92\x8b\xd1\xc2\x32\x49\xe6\xde\xee\x02\x18\xc7\xdd\xb1\x66\x41\xa5\x8e\x90\x87\xc1\x2e\x3b\xc1\x8c\x3f\xec\xe2\xf4\x24\x26\xc9\x03\xb1\x23\xa2\x27\xd7\xc2\xa9\xf7\x55\x54\x1d\x74\x20\x7d\x1c\x53\xa5\xd0\x5f\xb4\x31\xae\x80\xc6\xf1\x85\x79\x3f\x8f\xa3\xa1\x64\xc0\x4a\x5c\xad\x35\xcf\xce\x09\xaa\x8d\xe8\xfa\x24\x86\xfc\x85\xac\x8b\xee\x4c\xb7\x21\xa0\x61\xd5\x41\x00\x9e\x3b\x7c\x0e\xe3\x07\xd8\xec\xb7\xfb\x2d\xe8\x3c\x06\xcb\x4f\xd8\x19\xf2\xdf\xa1\x44\xbb\x5c\xb3\x83\x2f\x37\xff\xed\xd9\x88\xf7\xe4\x63\x7b\xc5\xef\x73\x0b\x77\x8f\xe9\x68\x67\x5d\x59\x3c\xcb\x59\x59\xdc\x1f\xae\x1d\x19\x82\x23\xd7\x8c\x3e\xc7\xae\xdb\x96\x78\xc6\x6d\x7f\xc6\x42\xf9\x0f\xcb\x95\x34\xf1\x12\xfe\xaa\x51\x99\x85\x74\xb7\x86\xf2\x12\x33\x4f\x70\xa1\x39\xfd\xa1\x32\x67\x84\x95\x16\x90\xe0\x72\xd7\xd3\xbf\xf0\xc1\x35\xe4\x26\x92\x09\xf0\x05\x6e\x11\xed\x68\x63\xb9\x61\x1a\x3d\xc1\xb1\x3a\x59\x90\x35\x69\xf3\x1d\xf1\xe2\xf0\x07\xc3\x71\x1f\x93\xe2\x9b\x18\xc7\xb6\xb3\x20\x6f\x4e\x16\xbc\xdc\x08\x55\xaa\x72\x30\x16\x07\x40\xfb\x9e\x0b\x87\x99\xe1\xae\xaa\x97\xdd\x73\xa5\x02\xad\x3e\xb7\x1c\xdf\xbe\x10\xbd\x57\x3e\xa8\xeb\xf3\x33\x47\x4e\x3f\xa1\x6d\xa6\x75\x88\xcc\xbd\x60\xb0\x32\x7f\x5c\xe3\x03\xed\x5b\x40\x06\x89\x2e\xe8\xba\x9f\x79\x48\xd8\xbc\xdf\xc5\x67\xcf\x2e\xa1\x84\x49\xe4\x19\xee\x3a\x81\xd8\xeb\x11\x8d\x50\xc7\xb7\x5c\xb6\x01\xed\x20\x54\x17\xeb\xeb\x58\xb6\xb1\xd4\x65\xf0\x2f\x6f\x2d\x25\xdd\x1d\xd3\x97\xbd\x93\x3a\x96\x57\xe6\xd8\xfb\x97\x53\xd4\x0f\x61\xcd\xc5\x94\xcf\xd2\x03\x0a\xe1\x40\x01\x6d\x3c\x9e\xb1\x6e\x8b\xcb\x73\xf7\x0a\xe8\x8d\x8f\x7c\xbc\x34\xdd\x79\x29\x15\x10\x14\x38\xd6\x80\x77\x75\xec\xfe\x90\x75\x81\x05\x34\xe9\x02\x35\x44\x0e\x2a\xa0\x70\xcc\x6e\x62\xd3\x5d\xa7\x69\x0b\x39\xe1\x9c\x64\x33\x6a\xca\x9d\xa7\xf4\x43\x8b\x4d\x33\x8e\xcc\x6d\xd8\x05\x8f\x43\x69\x21\x8c\x9e\xe8\x19\x10\xc3\xd9\x71\x47\x1c\x79\x80\x7c\x18\x30\xf0\xef\x56\xff\xdd\xea\x47\xd4\x87\xf8\x59\xbf\x04\x88\x5c\xf3\xc9\xef\xc2\x2e\x7c\x3c\xdd\xa0\x88\x52\x5f\x05\xeb\x87\xc1\xfa\xb9\xc7\x07\xce\xdc\xa4\x71\x2c\xca\x53\xf3\x73\x49\x26\x9e\x8a\x88\x12\x78\x03\xcb\xdb\x02\xf7\xf3\x12\x46\xe1\x2b\xf1\xa1\xeb\x1e\x6a\x94\x01\xe5\x7e\xaf\x26\xa6\xa6\x32\x74\xfd\xef\x0c\x43\x74\x11\x26\x46\xfc\x0a\xcb\xf6\x8c\x73\x0a\x17\xff\x27\xf8\x8a\x74\x60\x2d\xd2\x2a\x12\xbd\x0e\xb0\x30\x96\x2a\x05\xd8\xe3\x84\xfd\xd4\xdd\xfc\x46\x32\x3e\x44\xdd\xbb\x6f\x7b\xc8\xb8\xe1\x9d\x7c\xec\xb9\x30\x44\x14\xd6\xf2\xac\xce\x72\xf5\x01\x4d\xfe\x2a\x11\x13\x58\xe0\x1c\x0f\x4d\xcf\xf5\x60\x61\xa0\x2c\x1e\xee\x79\x7d\x73\x01\x13\x69\x4a\xa9\xac\x65\x35\x8b\x0a\xc0\xac\x36\x0b\x6a\x4d\x13\x93\xe8\x58\x4f\xf7\x57\x1c\x23\x3a\x2e\x3e\x11\x9f\x42\x85\x55\x3a\x57\xe2\xc9\x56\x76\xc8\xa9\x49\x63\x81\x3e\xf2\x81\xc5\xd4\xc9\x59\x95\xa5\xb5\x48\xac\xfe\x5d\xce\xd5\x0a\xea\x44\x67\x5f\x81\x17\x98\x4c\x6a\x43\x28\x2e\xdf\x4a\x77\x17\xfd\xd2\x76\x0c\x8e\xa5\xad\x45\x63\x73\x72\x2e\xa9\xaf\x68\x78\x77\x12\xbb\x03\xac\xb0\x8d\x7f\x4f\xe8\x81\x9f\xbc\x91\xf7\xaf\xdb\x24\x7c\x51\xa8\xa5\xf3\xaf\xc0\x95\x95\xff\x14\x6e\x41\xb0\xda\x73\xa4\x5a\xa0\x37\x6b\x59\x40\xd9\x11\xa5\xe9\x38\xde\x08\xd2\xee\xe6\x2e\x8b\x43\x3a\x35\xca\xa3\x6c\x2d\x19\x02\xcf\xaa\x72\x4f\xac\x8c\xe2\xdb\x44\x6d\x9d\x86\x08\xde\x50\x5f\xcc\x2a\x4c\x0e\xc2\x3c\x3b\xf8\x68\x5c\x9a\xf4\x3e\x72\xcc\x17\x3b\x77\xbf\x1b\x69\x94\xd2\x41\x9e\xb4\x3b\xa8\x72\xa6\x6a\x73\xa0\xff\x57\xff\xc4\x72\x0f\x02\xbc\xba\xe1\x55\x2e\x07\x23\xb8\xee\xfc\x42\x12\xa3\x0a\x73\xcd\x28\xe6\xcd\x47\x20\x36\xe1\xe9\x69\x63\x00\x97\xb6\x76\x90\x4e\x9f\x54\xea\x61\x5f\x93\x32\xea\xda\xc8\x26\xcc\xa7\x7f\x73\xe3\xc9\xff\x11\x6d\xe5\xa8\x34\x7b\x17\xc9\x42\xb5\xcc\x90\x0b\x6b\x38\xe6\x4c\x66\xc1\xfd\x2f\x0a\x8c\xe6\x50\x77\xfe\x83\x9a\x69\x77\x3e\x4c\x3a\x22\xbe\x0e\x1a\x8b\x4e\xae\x46\xaa\x04\x5e\xe6\xf4\xf1\xd9\xc5\x52\xaa\x7f\x82\x1b\x2a\x2c\xbf\x40\xae\xd4\x1e\xad\x44\xeb\x1c\x79\x05\xad\x95\x47\x75\xd1\x0c\x62\x90\x13\x6f\x61\x3e\xe3\xd1\xfc\xef\x6b\x5a\xc3\x1f\xfa\x12\x55\x82\x30\xf0\xf3\x20\x2f\x19\x6f\x8f\x07\xad\xca\x30\x97\xc6\x50\x96\x9b\x3f\x45\x8a\x5d\x54\x43\xcc\xb6\x7c\x19\x24\x6b\x19\xe9\x5b\xb4\x6e\xec\x6e\x0f\xf2\x17\x49\xa5\xb7\x98\x60\x76\xb0\x81\x36\x8d\xce\x15\x72\xa5\x26\x3c\xe8\x39\xa9\xc9\x94\x9f\xe7\xcb\xa0\x34\x57\xc2\xdc\x21\x57\xad\x00\xf6\xf9\x8c\xa7\xb0\x8e\xd3\x2a\x17\x3a\x7d\x68\x44\xad\x76\x61\xe6\x80\x40\xdf\xe7\xa5\x4f\x57\x0b\xce\xb6\x50\x66\x37\xfe\x08\x36\xf0\x32\x6b\x52\xc9\xbb\xad\xb0\xc5\x66\x36\xc8\xb6\x6d\x16\x57\x13\xab\x02\xf3\x2e\x35\x33\x76\xb1\x88\x41\x7b\x94\x60\x25\x01\x96\x92\x3a\x57\x66\x0d\xc8\x0f\x58\x03\xf6\xab\xbd\xcd\x76\xc4\x1d\xb1\x67\x04\x73\x24\x23\x83\xf4\x68\xbc\x7e\xab\x9f\xf9\x62\x7d\x67\x47\xfc\x03\xf0\x09\x67\xd4\x81\xdc\xe6\xdf\x9b\xce\x40\xef\x93\x9c\x25\x1a\x56\x0e\xdf\x50\x86\x3f\x57\xb5\xcf\x58\x00\xe6\xc9\x7b\x19\xd7\x38\xdd\x57\x40\x07\x96\xe0\xea\x29\x45\x9c\x8e\xba\x09\xbd\x9d\x0f\x75\x2e\x9e\xda\x27\x49\xbf\x29\x57\x6e\x9c\x9f\x0f\x44\x2a\x8c\x9a\x2a\xfc\x75\x02\x33\x73\xfc\x18\xb9\x0a\xe4\x5d\xfb\xea\xb7\x1a\xeb\xb4\xfd\x29\x04\x39\x22\x73\xc3\x45\xfd\x9d\x5f\xe6\x45\x68\x80\x08\x28\x58\x5e\xc9\x7b\xcc\x30\x00\x07\x94\x78\x22\x1d\x31\xf1\x9e\x90\x76\x0b\xd7\x5a\x54\x54\x57\xd6\x85\xd7\x7a\x77\xa4\x59\xf9\x93\xe5\xef\xe9\xb4\x8e\xd8\xed\xf1\x1a\xab\x77\xd4\x08\x40\x22\x45\x82\xe7\xc0\x1f\xc0\x12\x6f\xd5\x53\x59\xc5\x6f\x2a\x27\xcb\x89\x50\xc3\x84\x9e\x84\xa3\x73\x92\x70\x3c\xb6\x50\x9d\x6b\x30\x39\x44\xf3\xe7\x61\x1c\x7e\x82\x32\xb2\x08\xc1\xec\xae\x21\x7b\xd2\xed\x6c\x3b\x0e\x9a\xca\xf7\xea\x96\x30\xf8\x1c\x38\x41\x58\xa6\x41\xcc\x07\x70\x7a\xd5\x2d\xfc\x29\xe9\xe8\x31\x91\xb8\x19\x79\x1e\xb5\x8e\x2b\xc6\x11\x0d\x54\x08\x4e\x49\x66\x6d\xfd\xf4\x29\x41\x5e\x25\xcf\xea\xe3\xf4\x33\x64\x04\x37\x82\xc2\x4b\xef\xcc\xae\x16\x9a\x74\xa7\xf4\xf7\x6c\x4d\xa7\x61\xaa\xd8\x14\x33\xd9\xcc\x16\xc7\x96\xf8\x95\x3d\x91\x7b\x97\x9b\x2d\x36\x20\x66\xd5\xa2\x48\x11\x95\x39\xd1\x0c\x33\x86\x8e\x73\x2d\xfc\xd3\x05\x97\x61\xb0\x58\x64\x03\x4d\x23\xe0\x38\xd0\xaa\x36\x89\xa8\xd7\x37\x4c\x71\x71\x22\xcc\x0c\xf2\x1b\xe2\xd4\xc8\x21\x17\xbc\xc0\xf7\xda\x82\x4f\x55\x31\x85\x18\x9f\x21\x3d\x4f\x98\x43\x0f\x9a\x17\x1f\x11\xd2\x51\x54\xd0\x23\x8d\xc7\x14\x2c\x71\x9c\x3e\x22\x6d\x3d\xaa\xef\x0c\x15\x8a\x93\x51\x9f\x48\xc3\xed\xb6\x77\xe3\x69\xe1\xd2\x5e\x9e\x47\x3b\x15\x07\xf5\x8f\x4a\x56\x07\xb2\x07\x15\x1c\xdc\x05\x04\x01\x91\xab\x66\x73\xc8\x75\xe5\xc9\x96\x5e\x5f\x57\xa8\x15\x25\x36\xbc\x8a\x92\xcb\xc2\xc9\x5d\x3b\x95\xe3\xa0\x06\xb7\xbe\x22\x7f\x90\x7b\x64\x66\x9b\xc8\x88\x26\x00\x71\xf8\x6d\x41\xd7\xc8\x07\x9f\xb5\x63\x61\x5e\x58\x38\xb5\x35\x57\x3f\x12\x78\x9a\x00\xec\xc4\x18\x67\x83\xce\xd2\x44\x66\x5e\xa1\xb6\x43\x6b\xb3\xa7\x77\x18\x0f\x54\xbb\x8c\xa4\x58\x0e\x5a\xe3\xed\x68\x55\x00\x23\x93\x4b\x37\xe2\xe5\x4e\x13\x7e\x2a\x5d\x15\xb1\x3a\x04\x09\xe2\x21\xc4\x35\x75\x7f\x4f\x9a\xef\x5e\xe5\xa6\x1b\x0a\x8d\xc6\x1a\x2c\xae\x84\x82\x49\x24\xfd\x2e\x5a\x15\x4e\x8b\xa3\x28\x46\x49\x0c\xc5\x0f\xb5\x3e\xba\xf3\x4c\x43\xa0\xf1\xac\x78\x20\xeb\x55\x41\x7f\x2c\x3a\x39\x85\x73\xc9\x8f\xbb\x3d\x10\xc2\xe6\x37\xf0\x1d\x09\x85\x33\x47\xce\xbd\x99\xaf\x10\x11\xd9\xef\xd4\xad\x65\x3a\xc4\xcc\x39\x81\xa0\x01\x01\xba\x71\x0d\x9c\x5f\x9d\x2b\xed\x79\xde\x77\x65\x10\x94\x1e\x10\xa1\x23\x0f\xc6\x42\xd8\xb5\x9a\x51\x78\x4e\x4b\xb4\x66\x3a\x0b\x14\x94\x64\xf3\x3c\x67\xf7\x5c\xdd\x13\xb1\x8c\xf3\x01\x43\xdf\x22\x5a\xb7\xcf\x04\xc2\xfc\x14\x68\xda\x0b\x8b\x84\x09\x32\x49\x92\xb7\x82\x4e\x8f\xb7\xb7\x17\x52\x62\xed\xa9\xdb\x43\x01\xfe\x2f\xf3\x98\xdc\xc8\xf8\xc2\x18\x93\xda\x22\x85\x15\x12\x2c\xff\x0f\x0a\xd4\xb2\x23\x1a\x92\xcd\xc6\x01\xa5\xb8\x09\x64\xdf\xcf\x47\x36\xb1\x92\x69\xac\xb0\x4f\x01\x14\x02\x16\x25\xfc\xe0\xb3\x22\x9b\x52\x96\x64\x96\x1a\xf2\x43\x64\xa2\x7e\xc7\x6d\x9e\xeb\x5e\xea\x9e\x11\x08\x92\x48\x8f\x3e\xa7\x36\x86\x51\x4d\xc4\xbf\xbf\x98\x82\xf7\xe4\x94\x65\xf9\x20\x6e\x7c\x03\x8f\x38\x6e\xe8\x98\x54\x0b\xaa\x21\x39\x2b\x4e\x8d\x6e\xcb\xb8\x09\x2c\x3b\xa6\x8e\x8d\x96\x6d\xcd\xe0\xdb\x74\xd4\xb0\x20\xcc\x14\x47\x34\x26\x18\x6e\xdc\x0a\xa7\xe3\xcd\xa5\xd0\x78\xf0\xc3\x6d\x08\x9b\x0b\x1f\xa1\x44\xe7\xff\x59\x3b\xb9\x7f\xd4\x49\x4b\x65\xe6\xd6\x43\xa3\x98\x5f\xcc\xe4\x6d\x63\x42\x8e\xc0\xdf\x38\xf1\xf3\x4d\x09\xf9\x72\x68\x98\xce\x48\x0a\xfc\x3c\x64\xc1\xc4\x52\x2d\xe0\xdc\x81\x41\x7e\x2f\xe6\x3e\x43\xbf\x61\xb2\x38\xb1\x73\xd1\x20\xae\xb2\x5b\x3e\x7f\x46\x85\x2c\x52\x29\x02\x89\x31\x0b\x44\xc3\x64\x8b\x6a\xa8\x2e\x0f\x0d\x72\xe5\x14\x9e\xf4\xcb\xcc\x5c\x2e\x75\x86\xe3\xd3\x3b\x41\x0d\xe8\x2f\x25\x64\xd9\xf6\xe5\x13\x94\x87\xf6\xde\x24\x04\xc3\xb0\xe5\x75\x06\x71\xad\xbb\xed\x7d\x21\xe0\xcc\x0b\xea\x79\xcb\x6e\x4e\xa7\x0f\x6f\x9f\x45\x45\xf2\x30\x02\x98\x16\x61\xd5\xc4\x2c\x19\x73\x30\xfe\xeb\x77\x28\x5d\x86\xaa\xb3\x2a\x10\x70\x15\x99\x77\x3e\x4b\xb1\x54\x40\xaf\x3d\x35\xab\x3d\xd1\x92\x9d\x09\x4a\x99\x55\xae\xa4\xfb\xd9\x91\x6c\xf7\xdb\x55\x52\x67\xcc\x2a\xce\xeb\x92\x89\x17\x88\x1f\x66\xf0\xd3\x35\xd8\xfb\x67\x7b\x01\xf0\x6d\x95\x0e\x36\xa9\x68\x39\x19\x6f\x94\x86\x7e\xb8\xb3\x4b\x46\x83\xf2\xe2\x5b\x07\x01\x6b\x0c\x3b\xa8\x3e\xa5\x57\xc5\xdd\x5d\x6d\x56\xa7\xf5\x93\x2d\x8e\x4e\xd7\x2f\x14\xa5\x23\xcc\x4e\x90\x11\xda\xce\x1b\xf3\x32\x86\x01\xa0\x3c\xfb\x30\xea\x34\x16\x18\x22\xe0\xf0\x33\x76\x1c\xfe\x90\x1e\x93\xa3\x58\xf9\x79\x7a\xab\xf1\xf9\xfe\xa9\xe3\x1c\x59\x4a\xc0\xf2\xf9\x98\xdd\xe9\xb7\x71\xae\x11\x20\xc8\x04\x8c\x96\xeb\xc4\x31\xee\x37\x5d\x78\x27\x67\xf8\x6b\x5f\x12\xf4\x60\xa8\x8a\xa7\xa5\xf6\x2e\x6b\x2b\x8f\x04\x99\x32\x2a\x7c\x43\x02\x17\xf1\x53\x14\x8b\x76\x9e\x76\xca\xad\xac\x5e\xe2\x38\x24\x6c\x52\xe3\xe5\x4e\xdf\x3c\xe1\x0b\xb1\xab\x8f\x5e\x40\x9c\xff\xc6\x54\xcd\xed\x2d\x13\xc6\x13\xaf\xba\x51\xce\x05\x50\xa4\x53\xc7\x93\x6a\x25\x24\x6e\x04\x3d\xbe\xf3\x62\x9d\x0f\x98\x6c\x17\x48\xbe\x0d\x0a\x0a\x35\x53\x48\xa5\x87\xe6\x70\x9b\x66\x04\x2f\x2c\x1a\xac\xdb\xf4\xea\xb1\xfe\x11\x0f\x78\x86\xaf\xdd\xee\x92\xad\x49\x89\x72\x52\x46\xf6\x1b\x1e\x27\x9a\x0f\x88\x74\x8f\xff\xe1\xfd\x98\x85\xd3\x7f\xe4\x36\x43\x6a\x0a\xec\xee\x87\x84\x86\x2f\x6b\x1e\x22\x3c\xc3\x22\xfa\x65\xc7\x5b\x5a\xf2\x94\xdb\x82\x5d\x9f\xe9\x2c\xd3\x83\xd1\x9c\x1b\x73\xf6\x5d\x4d\x9a\xc1\x7b\xb8\x5a\x66\x6d\xa1\x99\xda\xd2\x52\xe4\xaa\x1e\x0a\x84\xf2\x46\x8e\xea\xa6\x0c\xe7\x19\xb1\x10\xf0\xd6\x39\x45\x69\x8c\x31\x1a\x06\xa9\xf1\x49\x89\x9f\x08\x95\x85\xe3\xe8\x7b\x70\x23\xb1\x5f\xd2\x9f\x00\x83\xf1\xb5\xa6\x1b\x89\x48\x08\xa5\xfd\x9f\x9d\x11\xb8\x00\xff\x71\xdb\x46\xf0\x0a\xa5\xb1\x87\x07\x24\x4c\x4f\x16\x5a\xa8\x28\xe8\xbf\x61\xc4\xfc\x0c\xca\xf2\x22\x6d\x42\x49\x6c\xf7\x95\x05\x81\x5f\x08\x8d\x4f\x2a\x9e\xcb\xb1\x38\x75\x65\x32\x9f\x98\x71\x93\x66\xa8\x2a\x2f\x4e\xc6\xdf\x76\x64\x3f\x6b\x68\x5b\x50\x67\x47\x09\xa9\x18\x74\x63\xb8\x25\xb2\x9c\x2c\xc2\xe0\x0a\xf4\xb0\x1f\xfe\x65\x47\x17\x69\x6b\xc4\xb1\x0a\x14\x3d\xcc\x1c\x0d\x95\xe2\x54\xc3\xff\x42\x52\xb5\xa9\x86\xba\x01\xee\x2a\x9e\x3e\x76\xad\x3f\xa6\xbe\x0e\x64\xfa\x46\x36\x8a\x45\xbf\x09\x15\xbe\xaf\x31\xed\x2e\xd5\x90\x85\xd0\x7a\xba\x2f\xb3\x28\x88\xc8\x94\x14\x7e\x82\x0a\x2f\x66\xd9\x74\xe6\x69\x09\x28\x45\xd7\xd0\xf5\x45\x51\x35\x43\x15\xc5\xef\x75\xa8\x9d\x57\x10\xf8\x56\xb1\x37\x02\x67\xfe\xb7\x01\xd6\x2f\xd3\x2a\x80\x78\x14\x90\xf6\x1f\x07\xcd\xe1\xa7\x29\xa2\xcb\xe4\xc1\x2a\xd4\x4d\xac\x46\xf2\x28\xd2\xfb\xf8\x42\xd8\xb0\xb6\xb5\xe6\x63\x26\x65\x10\x25\xb0\x17\xaf\x35\x6f\xa4\x06\x10\x3d\x8f\xcc\x5f\x81\xae\x41\x13\x00\x0d\x65\x7b\x2d\x37\xad\x8f\xbe\x8a\x9e\x3e\x6c\x47\xad\x50\xe3\x00\xfa\x77\x00\xb8\x52\x2b\x0c\xe7\x45\xfa\xfa\xda\xdd\xf3\x84\x6a\xeb\x34\xe0\xc0\xa6\x52\x87\x58\x1a\xd9\x55\xdb\x31\xee\x7b\x2d\x38\xcc\xf5\xf0\x03\x5f\xe0\x46\xc3\x0c\x34\xc3\x84\x7a\x0e\x15\x48\x10\x4a\xf2\xc7\x29\xd7\x1e\x84\xa9\x4f\xed\x30\x60\xf6\xa6\xd9\x7f\x0b\xc0\x7b\xc4\xc7\xd9\x46\x59\x98\xba\x1f\x8b\x34\x76\x34\xc7\xc5\x25\x0f\x42\xca\x97\xe4\x7b\xfb\x36\x9b\x48\x63\x7b\xb5\x4f\x31\x73\x01\xcd\x87\x8d\x08\xd1\x37\x73\x21\xd4\xe3\x25\xf4\x49\x0c\x38\x6d\x18\xda\x38\xb7\xb0\xaa\x05\x28\x35\xcc\x36\xac\xe2\xc8\xbb\xde\x18\x71\x4d\x98\x7a\x65\x8c\xa0\xff\x0e\x52\x8e\x83\x03\x6f\x56\x06\x44\x00\x66\x48\xe7\xfb\x20\x10\xac\x01\x70\x33\x20\x65\xdf\xbb\x92\x43\xdd\xaa\xe7\x89\xdd\x55\x65\xe8\x15\x5d\x84\x77\xe3\x68\xf7\x09\xda\x6f\xc3\x0b\xd8\x03\x16\x56\x05\xbb\xb6\x3a\x3d\xd9\x02\xce\x77\x02\x32\x86\x30\xee\x8b\xc5\x4b\x65\xf7\x17\xc3\xc0\x5d\xc2\xb3\xed\x3f\xca\x56\x36\x13\xfa\x8b\x71\x48\xe3\xa8\xa2\x9a\x3e\xc1\x96\x7c\x55\x82\xf6\x17\x25\x97\x5b\xdd\xb0\x52\x18\x58\x35\xc9\xb7\x49\x61\x88\x9e\x44\x7c\x71\xd8\x45\x86\x3c\x2b\xfc\x73\x90\x2d\x5e\xae\x0c\xca\xa1\x65\xc8\xa1\x31\xd0\x5b\xe5\xd9\x4f\xc4\x37\xfe\xc5\xee\x30\x3c\x57\xb4\xac\xbc\xae\x9b\x9e\xdc\x34\x12\xe0\x4d\x8f\x1c\x1f\x2a\x82\x80\xa2\xd7\x8e\x17\x20\x0f\xbf\x2b\xd4\x66\xe4\xab\x68\x20\x79\x6b\x76\x05\x65\x4a\x0e\x05\x6e\xaf\xaa\x3b\xfc\x41\xdf\x2c\xe8\x58\x7d\xdb\xd4\x01\x67\xe6\x4a\x68\x5b\x9a\x59\x49\x7e\x10\xac\xbb\xd4\x21\x78\xf5\x36\xa1\x9a\x64\x2c\x84\x24\xb7\x65\x81\xbe\x4a\x40\xc2\xd7\x45\x27\x4b\xec\x2e\x9a\x55\xb2\xba\xd0\x85\x1a\x85\xd9\xa5\x11\xe0\x68\xc3\x8b\x61\x34\xa1\x7f\x43\x2c\xaf\x06\xb7\x5f\x35\xd9\x92\xcb\xa5\x2b\xb3\xde\x4f\x1d\x76\xc1\x2d\xf8\xd5\xc6\xd0\x9b\x0f\xb4\x80\x60\x66\x97\x74\xfe\x82\x20\xc5\x46\xfb\xb6\x47\x29\x7f\x8b\x72\x49\x0e\xd5\xc7\xba\x5a\x65\xe5\xc7\xb6\x21\x8c\x85\x20\xd3\xb3\x21\x1b\x98\x03\x92\x0c\x40\x7a\x84\xa6\xa4\x0e\x8e\xb7\xb6\xf1\xac\x88\x8b\xb6\x61\x40\x36\x72\xa7\xe5\x44\xc2\x54\xd7\x92\xbe\x5c\x41\xfe\xc4\x15\xf2\x29\x85\xbf\x24\x9c\x5d\x10\x53\x9a\xb9\xde\xcc\x4e\xbe\xa5\xa9\xf1\x59\xa8\xc0\x52\x3f\x7d\x54\x88\x38\xdd\xbf\x0e\x6a\xe8\x1e\x48\x77\x63\x9c\x70\x44\x15\xd9\x26\x51\xc7\xfd\x73\x46\x3a\x45\x71\xb6\x59\xf0\x85\xcc\x9f\x4a\xb5\x31\xa0\xdc\x18\xe5\x57\x86\x04\x7b\xef\xb2\x14\x1d\xf3\x47\xa9\xff\x9a\x47\x69\x14\x1a\x99\x20\x00\xc2\xa5\x85\x87\x1b\x1d\x6a\xeb\xd9\x5c\x68\xd8\x8a\x57\xa9\xa2\x15\x5f\x8f\x54\x18\xb4\x71\x95\x41\x22\xb6\x6c\xca\xcf\x46\xd9\xb9\x13\x09\xec\x69\x6c\x1c\x89\xe2\x45\x0f\x66\x9c\xcb\x47\x2c\x6d\xae\xc6\xbd\x8f\xec\x78\x0a\x2b\x73\xe4\x64\xe4\xc8\x31\xd7\xa0\x1a\xe5\x8f\xca\xf9\x68\xde\x89\xad\x4d\x96\x18\xdc\x36\x3c\xaf\x83\x40\x34\x11\xc9\x48\x50\x94\x80\x67\xa4\x07\x6a\x78\x63\xe4\xe9\x8d\xb5\xe7\x1b\xb0\x55\xe7\xb3\x85\x3e\xbb\x26\x4b\x9b\x59\x63\x44\x09\x90\x4c\x3c\x55\x59\x92\x57\x76\xb0\x57\x92\xcd\x81\x11\xdd\x9a\xa5\x6d\x62\x44\x94\x2c\x75\xf0\x12\x26\x77\xdd\x6c\xba\x70\xe1\x8c\x8e\xd3\x15\x2c\x34\xec\x1b\xf7\x23\x89\xde\xcc\x6c\xe9\x36\xf0\x74\xf8\x2a\x41\xbf\x94\xad\xcf\x4e\x97\x40\xdf\x51\x35\x5c\xb3\x1d\x0d\x03\xa6\xe9\x5c\x79\xf5\x51\x65\x13\x88\x28\xe8\xec\x0b\x52\x9b\x2b\x58\xd6\x21\x54\x7f\x97\xf6\xe9\x50\x0f\x2d\x90\xde\x82\x5d\x34\x06\xe8\x76\xbc\x7e\x29\xe6\xd3\xa8\x98\x97\x01\xb2\x10\xce\xae\x9a\x91\xa8\x91\xac\x6c\x36\x6c\xa4\x06\x3c\xb8\xee\x20\x63\x12\xaf\x07\x03\xed\xc1\xd3\x7c\x25\xff\xa3\x54\x2a\xb5\x56\x15\x02\x8c\x4d\x9b\x67\x13\x8a\x27\xb6\x2f\xb8\x3d\x97\x3b\x8f\xc8\xe2\xfb\x11\x44\x78\x61\xec\xba\xea\x81\xc5\x48\x96\xff\xe5\xca\xc1\xcf\x50\x92\xcc\x71\x86\xa2\xa1\xaa\x8d\x36\x3c\x8d\x41\xad\x97\x18\x20\x9f\xf2\x62\xed\x02\x19\xba\xf3\xd5\xe8\xaf\xfd\xbd\xfa\x97\xa5\xc5\x08\x1d\x40\x7d\xac\x6a\xbf\x19\xc1\x85\xa2\x2e\x0a\xd0\x42', 2)
\ No newline at end of file
diff --git a/cs101courseware/dist/deploy_cs101.py b/cs101courseware/dist/deploy_cs101.py
new file mode 100644
index 0000000000000000000000000000000000000000..e91aa9d11f0f6e027322a56e891313bf3ea8ce22
--- /dev/null
+++ b/cs101courseware/dist/deploy_cs101.py
@@ -0,0 +1 @@
+__pyarmor__(__name__, __file__, b'\x50\x59\x41\x52\x4d\x4f\x52\x00\x00\x03\x06\x00\x33\x0d\x0d\x0a\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00\x5a\x03\x00\x00\x00\x00\x00\x10\x01\x31\x7d\x8d\x12\x6f\xeb\x54\x4e\x3b\x00\xfe\xf2\x81\xbc\x33\x00\x00\x00\x00\x00\x00\x00\x00\x90\x71\x7e\x4b\xe3\xcb\x29\xa4\x3b\xc9\x84\x93\x60\x52\x24\x27\x5b\x3d\xc8\xac\xd2\x68\x02\x56\xd0\xfe\x5c\x48\x26\x94\x99\xa0\x3d\x35\x45\x9a\x9f\xfd\x3a\x9e\x17\xfd\x43\xd1\xec\x81\x6c\x71\xc5\xdb\x7b\xd7\x61\x5e\xb8\xa2\x77\xe3\x72\xc8\x87\x11\x6a\xb9\x8b\x51\xf9\xb2\xfa\x77\x49\x02\x9d\x7e\xec\x59\x4f\x8c\x77\x10\x1c\xda\xe4\x5e\xdc\xef\x50\x69\xf0\x94\x74\x57\x31\x91\xa9\x24\x0a\x43\xbc\xee\x41\x06\x6f\xec\x81\x38\x83\x4f\x45\x57\xc8\x68\x04\x88\xdd\x2e\xd9\x67\xda\xaa\xb1\xe4\x09\xae\xb0\x45\x9f\x62\x5f\x9c\xe8\x4d\x02\x93\x7f\xb2\x93\xd6\xe8\x4c\xc9\x09\xa8\x51\xbe\xde\xf6\xd1\x37\x9f\x37\x68\xbe\xbf\xe6\xd5\x29\xb7\x03\xd0\xfb\x29\x91\xce\xe2\xfe\xd7\xbb\x73\xbd\x09\x07\xfd\x43\x40\x0e\xd2\xb0\xc9\xd4\xc7\x22\x99\xb3\xf2\x92\x61\x30\x99\xe7\x90\xb3\x1f\xfe\x20\xd6\xf3\x9d\x18\xed\x06\x22\xd4\xe7\xc4\xb9\x9c\x3f\x34\x82\xe7\x85\xd6\x29\x56\xbc\x1b\x6b\xbd\x66\x22\x53\x83\xd9\x5d\x46\xf5\x8f\x42\x35\x25\x91\x71\x91\x7b\x04\x33\xea\xf3\x51\x1e\x26\x80\x84\xda\x61\xfb\x8a\x17\x84\xc5\x10\x5b\x20\xac\x72\xf6\xa2\x94\x44\x17\x27\x1a\xad\x04\x9b\x3a\x17\x08\x6a\x68\x1f\xc7\x26\x6c\xd9\x9b\x65\xde\x4e\x44\x2d\x62\xa8\x3f\x5c\xfc\xd5\xf4\xdb\x1d\x61\xca\xeb\x9e\xed\x8e\xb6\x63\xca\xf4\x67\x16\x8e\x44\xd1\xf0\xd5\xec\x42\x54\x88\x0d\x47\xe1\xe8\x3a\x78\xd9\x07\x91\x2d\x3d\x42\x24\xbf\x0f\x34\xa2\x53\x0b\xc2\x00\x3e\xd4\x04\x71\x0f\x45\xa1\x45\x13\x36\x6a\x54\xa4\x20\x5b\xa6\x49\x76\xaf\x47\xdf\x33\xb7\xeb\x42\x21\x3b\xab\x8f\xfb\x3a\x4e\x2e\x19\x1a\x17\x79\xf4\xc7\x62\x02\xb2\xee\xe6\x25\x95\x01\x57\x68\xcc\x54\x44\x1b\xd3\x85\x9b\x8d\x31\x75\xf3\xc3\x61\xd1\xb1\x13\x72\xcf\xde\x3c\xfb\xb6\x03\x89\xdd\x63\x26\xee\x57\x4c\x9d\x46\x35\x8a\xd4\x4f\xcb\x8b\xce\x79\xaa\x59\xaa\xf2\x6b\xab\xb4\x43\x63\x01\xd0\x43\xc1\x7d\xcd\xe2\xb5\x77\x17\xe7\x72\xf1\x7b\x54\xe6\x3e\x0d\x4c\x44\x21\x3d\xe6\xe8\xf6\x97\x40\xb1\x67\x6c\x66\x85\x33\xe5\xad\x99\x23\x07\x04\xb2\x83\xe9\xf4\xe0\x74\x31\xc3\x73\xbe\xce\x4b\xf5\x61\x16\x3e\xc1\x30\xad\x18\xe4\x30\xb1\x13\x9c\x0b\x2a\x58\xd5\xce\x99\xc4\x33\x1d\x9e\x58\x56\x3a\x46\xfa\x68\x5e\x8a\xe7\xa6\x27\xf9\xa4\x2f\x08\x51\xf1\x76\x4d\x03\x0e\x52\x99\x62\x13\x18\x2b\x2d\x8e\x08\x8e\xdb\x0b\xf8\xfc\x87\x82\xe1\x57\x84\x00\x38\x95\x8d\x4f\xa3\x2b\x87\xdd\x7f\x5d\x3c\x45\x18\xc5\xcc\xee\x48\x65\xdf\x21\x99\x74\x92\x42\xa3\xd8\xa0\x54\x99\x3e\xe4\x5c\x0d\x37\x4d\x4a\x2a\x8d\x1f\x0a\x4c\x7f\x6d\x9c\x65\x01\x1e\x52\x01\x26\x40\xc2\x66\xca\x5d\x5e\x92\xba\x1e\x2b\x86\x1a\xc2\xed\xbd\x25\xc2\x12\x7c\x00\x47\xc4\x28\xdc\xf8\x0a\x32\x51\x5a\x96\xdf\x79\xfe\xc1\x08\x8b\x15\x16\x09\x62\x37\x5a\x60\x13\x88\xdd\x9a\xeb\xb7\xd5\x0d\xf2\xbb\x76\x11\x16\x0f\xb1\x4c\x00\xd9\x22\x3c\x24\x0f\x59\x74\x75\x58\x23\x7f\x86\xaa\x84\xb1\x6b\x0d\x68\xc9\x67\xa8\x86\xc2\x3c\xaa\xa5\x86\xa5\x49\x62\xa0\xc0\xdf\x53\x79\x2f\x5c\x28\x36\x65\x30\x32\xe2\xfa\x8c\x26\x63\xbc\x11\x8f\xfc\x57\x68\x6a\xf9\x08\xb6\xc0\x55\x98\x59\x6e\x4e\xad\xff\xcb\xaf\x51\x6d\xb9\x00\x71\xba\x16\x46\x01\x9c\xbe\x25\x46\x40\x7f\x32\x51\xf1\xee\xe3\x39\x7f\x20\x65\x58\xb1\xc8\xff\xfd\xc4\x01\x8a\x59\x36\x81\xee\x78\x3f\xc5\x1b\x8c\x93\xdd\x60\x22\xf6\x02\x95\x28\xbd\x2a\xe6\x68\x87\x24\x4e\x7e\xa1\x7d\x67\xc9\xef\xb2\xeb\x82\x3e\x35\x36\x66\x62\xe1\xff\x5b\xcf\x43\x47\x36\x3b\xba\x49\xe4\xef\x58\xfd\xd1\x96\x9e\x15\x12\x52\x9c\xae\x78\x68\x6b\xa0\x11\x44\x1c\x19\xce\x80\xe2\x2a\xfd\x1e\x92\xf8\x69\xf9\x84\xc2\x22\x8e\xf1\xea\x55\x36\x8e\x3e\xd0\xf3\xdb\x1f\x00\x44\xcc\x77\x0c\xb8\x1a\x50\x7f\x1c\xb2', 2)
\ No newline at end of file
diff --git a/cs101courseware/dist/homework1.py b/cs101courseware/dist/homework1.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ed701c0f7870469469d51c0ae354474d8a880a6
--- /dev/null
+++ b/cs101courseware/dist/homework1.py
@@ -0,0 +1,40 @@
+import numpy as np
+from sklearn.datasets import load_boston
+from sklearn.linear_model import LinearRegression
+from sklearn.metrics import mean_squared_error, r2_score
+
+####################
+# Question 1. Write a function reverse_list which accepts a list, and returns a new list
+# with the same elements but in opposite order.
+####################
+def reverse_list(mylist):
+    # TODO: Your solution here
+    result = []
+    for k in mylist:
+        result = [k] + result
+    return result
+
+def simple_list_question():
+    print("The reverse list function can reverse a list")
+    l = [1, 2, 3, 4]
+    print("List was:", l, "reversed version", reverse_list(l))
+
+
+####################
+# Question 2: Write a function which performs linear regression on the Boston housing dataset using scipy
+####################
+def boston_linear():
+    X,y = load_boston(return_X_y=True) # Load the dataset here
+    y += np.random.randn(y.size ) * 0.01
+    # TODO: Fit a linear regression model and print the coefficients
+    lin_model = LinearRegression()
+    lin_model.fit(X, y)
+    print("Coefficients are", lin_model.coef_)
+    # TODO: Compute the RMSE here (on the training set) and print it
+    y_predict = lin_model.predict(X)
+    rmse = (np.sqrt(mean_squared_error(y, y_predict)))
+    print("RMSE is", rmse)
+
+if __name__ == "__main__":
+    simple_list_question()
+    boston_linear()
diff --git a/cs101courseware/dist/pytransform/__init__.py b/cs101courseware/dist/pytransform/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f656a22d3028cd47aedc8a79e8c9607a847957ac
--- /dev/null
+++ b/cs101courseware/dist/pytransform/__init__.py
@@ -0,0 +1,454 @@
+# These module alos are used by protection code, so that protection
+# code needn't import anything
+import os
+import platform
+import sys
+import struct
+
+# Because ctypes is new from Python 2.5, so pytransform doesn't work
+# before Python 2.5
+#
+from ctypes import cdll, c_char, c_char_p, c_int, c_void_p, \
+    pythonapi, py_object, PYFUNCTYPE, CFUNCTYPE
+from fnmatch import fnmatch
+
+#
+# Support Platforms
+#
+plat_path = 'platforms'
+
+plat_table = (
+    ('windows', ('windows', 'cygwin-*')),
+    ('darwin', ('darwin', 'ios')),
+    ('linux', ('linux*',)),
+    ('freebsd', ('freebsd*', 'openbsd*')),
+    ('poky', ('poky',)),
+)
+
+arch_table = (
+    ('x86', ('i?86', )),
+    ('x86_64', ('x64', 'x86_64', 'amd64', 'intel')),
+    ('arm', ('armv5',)),
+    ('armv6', ('armv6l',)),
+    ('armv7', ('armv7l',)),
+    ('ppc64', ('ppc64le',)),
+    ('mips32', ('mips',)),
+    ('aarch32', ('aarch32',)),
+    ('aarch64', ('aarch64', 'arm64'))
+)
+
+#
+# Hardware type
+#
+HT_HARDDISK, HT_IFMAC, HT_IPV4, HT_IPV6, HT_DOMAIN = range(5)
+
+#
+# Global
+#
+_pytransform = None
+
+
+class PytransformError(Exception):
+    pass
+
+
+def dllmethod(func):
+    def wrap(*args, **kwargs):
+        return func(*args, **kwargs)
+    return wrap
+
+
+@dllmethod
+def version_info():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('version_info', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def init_pytransform():
+    major, minor = sys.version_info[0:2]
+    # Python2.5 no sys.maxsize but sys.maxint
+    # bitness = 64 if sys.maxsize > 2**32 else 32
+    prototype = PYFUNCTYPE(c_int, c_int, c_int, c_void_p)
+    init_module = prototype(('init_module', _pytransform))
+    ret = init_module(major, minor, pythonapi._handle)
+    if (ret & 0xF000) == 0x1000:
+        raise PytransformError('Initialize python wrapper failed (%d)'
+                               % (ret & 0xFFF))
+    return ret
+
+
+@dllmethod
+def init_runtime():
+    prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int)
+    _init_runtime = prototype(('init_runtime', _pytransform))
+    return _init_runtime(0, 0, 0, 0)
+
+
+@dllmethod
+def encrypt_code_object(pubkey, co, flags, suffix=''):
+    _pytransform.set_option(6, suffix.encode())
+    prototype = PYFUNCTYPE(py_object, py_object, py_object, c_int)
+    dlfunc = prototype(('encrypt_code_object', _pytransform))
+    return dlfunc(pubkey, co, flags)
+
+
+@dllmethod
+def generate_license_file(filename, priname, rcode, start=-1, count=1):
+    prototype = PYFUNCTYPE(c_int, c_char_p, c_char_p, c_char_p, c_int, c_int)
+    dlfunc = prototype(('generate_project_license_files', _pytransform))
+    return dlfunc(filename.encode(), priname.encode(), rcode.encode(),
+                  start, count) if sys.version_info[0] == 3 \
+        else dlfunc(filename, priname, rcode, start, count)
+
+
+@dllmethod
+def generate_license_key(prikey, keysize, rcode):
+    prototype = PYFUNCTYPE(py_object, c_char_p, c_int, c_char_p)
+    dlfunc = prototype(('generate_license_key', _pytransform))
+    return dlfunc(prikey, keysize, rcode) if sys.version_info[0] == 2 \
+        else dlfunc(prikey, keysize, rcode.encode())
+
+
+@dllmethod
+def get_registration_code():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('get_registration_code', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def get_expired_days():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('get_expired_days', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def clean_obj(obj, kind):
+    prototype = PYFUNCTYPE(c_int, py_object, c_int)
+    dlfunc = prototype(('clean_obj', _pytransform))
+    return dlfunc(obj, kind)
+
+
+def clean_str(*args):
+    tdict = {
+        'str': 0,
+        'bytearray': 1,
+        'unicode': 2
+    }
+    for obj in args:
+        k = tdict.get(type(obj).__name__)
+        if k is None:
+            raise RuntimeError('Can not clean object: %s' % obj)
+        clean_obj(obj, k)
+
+
+def get_hd_info(hdtype, name=None):
+    if hdtype not in range(HT_DOMAIN + 1):
+        raise RuntimeError('Invalid parameter hdtype: %s' % hdtype)
+    size = 256
+    t_buf = c_char * size
+    buf = t_buf()
+    cname = c_char_p(0 if name is None
+                     else name.encode('utf-8') if hasattr('name', 'encode')
+                     else name)
+    if (_pytransform.get_hd_info(hdtype, buf, size, cname) == -1):
+        raise PytransformError('Get hardware information failed')
+    return buf.value.decode()
+
+
+def show_hd_info():
+    return _pytransform.show_hd_info()
+
+
+def assert_armored(*names):
+    prototype = PYFUNCTYPE(py_object, py_object)
+    dlfunc = prototype(('assert_armored', _pytransform))
+
+    def wrapper(func):
+        def wrap_execute(*args, **kwargs):
+            dlfunc(names)
+            return func(*args, **kwargs)
+        return wrap_execute
+    return wrapper
+
+
+def get_license_info():
+    info = {
+        'ISSUER': None,
+        'EXPIRED': None,
+        'HARDDISK': None,
+        'IFMAC': None,
+        'IFIPV4': None,
+        'DOMAIN': None,
+        'DATA': None,
+        'CODE': None,
+    }
+    rcode = get_registration_code().decode()
+    if rcode.startswith('*VERSION:'):
+        index = rcode.find('\n')
+        info['ISSUER'] = rcode[9:index].split('.')[0].replace('-sn-1.txt', '')
+        rcode = rcode[index+1:]
+
+    index = 0
+    if rcode.startswith('*TIME:'):
+        from time import ctime
+        index = rcode.find('\n')
+        info['EXPIRED'] = ctime(float(rcode[6:index]))
+        index += 1
+
+    if rcode[index:].startswith('*FLAGS:'):
+        index += len('*FLAGS:') + 1
+        info['FLAGS'] = ord(rcode[index - 1])
+
+    prev = None
+    start = index
+    for k in ['HARDDISK', 'IFMAC', 'IFIPV4', 'DOMAIN', 'FIXKEY', 'CODE']:
+        index = rcode.find('*%s:' % k)
+        if index > -1:
+            if prev is not None:
+                info[prev] = rcode[start:index]
+            prev = k
+            start = index + len(k) + 2
+    info['CODE'] = rcode[start:]
+    i = info['CODE'].find(';')
+    if i > 0:
+        info['DATA'] = info['CODE'][i+1:]
+        info['CODE'] = info['CODE'][:i]
+    return info
+
+
+def get_license_code():
+    return get_license_info()['CODE']
+
+
+def get_user_data():
+    return get_license_info()['DATA']
+
+
+def _match_features(patterns, s):
+    for pat in patterns:
+        if fnmatch(s, pat):
+            return True
+
+
+def _gnu_get_libc_version():
+    try:
+        prototype = CFUNCTYPE(c_char_p)
+        ver = prototype(('gnu_get_libc_version', cdll.LoadLibrary('')))()
+        return ver.decode().split('.')
+    except Exception:
+        pass
+
+
+def format_platform(platid=None):
+    if platid:
+        return os.path.normpath(platid)
+
+    plat = platform.system().lower()
+    mach = platform.machine().lower()
+
+    for alias, platlist in plat_table:
+        if _match_features(platlist, plat):
+            plat = alias
+            break
+
+    if plat == 'linux':
+        cname, cver = platform.libc_ver()
+        if cname == 'musl':
+            plat = 'musl'
+        elif cname == 'libc':
+            plat = 'android'
+        elif cname == 'glibc':
+            v = _gnu_get_libc_version()
+            if v and len(v) >= 2 and (int(v[0]) * 100 + int(v[1])) < 214:
+                plat = 'centos6'
+
+    for alias, archlist in arch_table:
+        if _match_features(archlist, mach):
+            mach = alias
+            break
+
+    if plat == 'windows' and mach == 'x86_64':
+        bitness = struct.calcsize('P'.encode()) * 8
+        if bitness == 32:
+            mach = 'x86'
+
+    return os.path.join(plat, mach)
+
+
+# Load _pytransform library
+def _load_library(path=None, is_runtime=0, platid=None, suffix='', advanced=0):
+    path = os.path.dirname(__file__) if path is None \
+        else os.path.normpath(path)
+
+    plat = platform.system().lower()
+    name = '_pytransform' + suffix
+    if plat == 'linux':
+        filename = os.path.abspath(os.path.join(path, name + '.so'))
+    elif plat == 'darwin':
+        filename = os.path.join(path, name + '.dylib')
+    elif plat == 'windows':
+        filename = os.path.join(path, name + '.dll')
+    elif plat == 'freebsd':
+        filename = os.path.join(path, name + '.so')
+    else:
+        raise PytransformError('Platform %s not supported' % plat)
+
+    if platid is not None and os.path.isfile(platid):
+        filename = platid
+    elif platid is not None or not os.path.exists(filename) or not is_runtime:
+        libpath = platid if platid is not None and os.path.isabs(platid) else \
+            os.path.join(path, plat_path, format_platform(platid))
+        filename = os.path.join(libpath, os.path.basename(filename))
+
+    if not os.path.exists(filename):
+        raise PytransformError('Could not find "%s"' % filename)
+
+    try:
+        m = cdll.LoadLibrary(filename)
+    except Exception as e:
+        if sys.flags.debug:
+            print('Load %s failed:\n%s' % (filename, e))
+        raise
+
+    # Removed from v4.6.1
+    # if plat == 'linux':
+    #     m.set_option(-1, find_library('c').encode())
+
+    if not os.path.abspath('.') == os.path.abspath(path):
+        m.set_option(1, path.encode() if sys.version_info[0] == 3 else path)
+
+    # Required from Python3.6
+    m.set_option(2, sys.byteorder.encode())
+
+    if sys.flags.debug:
+        m.set_option(3, c_char_p(1))
+    m.set_option(4, c_char_p(not is_runtime))
+
+    # Disable advanced mode by default
+    m.set_option(5, c_char_p(not advanced))
+
+    # Set suffix for private package
+    if suffix:
+        m.set_option(6, suffix.encode())
+
+    return m
+
+
+def pyarmor_init(path=None, is_runtime=0, platid=None, suffix='', advanced=0):
+    global _pytransform
+    _pytransform = _load_library(path, is_runtime, platid, suffix, advanced)
+    return init_pytransform()
+
+
+def pyarmor_runtime(path=None, suffix='', advanced=0):
+    if _pytransform is not None:
+        return
+
+    try:
+        pyarmor_init(path, is_runtime=1, suffix=suffix, advanced=advanced)
+        init_runtime()
+    except Exception as e:
+        if sys.flags.debug or hasattr(sys, '_catch_pyarmor'):
+            raise
+        sys.stderr.write("%s\n" % str(e))
+        sys.exit(1)
+
+
+# ----------------------------------------------------------
+# End of pytransform
+# ----------------------------------------------------------
+
+#
+# Not available from v5.6
+#
+
+
+def generate_capsule(licfile):
+    prikey, pubkey, prolic = _generate_project_capsule()
+    capkey, newkey = _generate_pytransform_key(licfile, pubkey)
+    return prikey, pubkey, capkey, newkey, prolic
+
+
+@dllmethod
+def _generate_project_capsule():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('generate_project_capsule', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def _generate_pytransform_key(licfile, pubkey):
+    prototype = PYFUNCTYPE(py_object, c_char_p, py_object)
+    dlfunc = prototype(('generate_pytransform_key', _pytransform))
+    return dlfunc(licfile.encode() if sys.version_info[0] == 3 else licfile,
+                  pubkey)
+
+
+#
+# Deprecated functions from v5.1
+#
+@dllmethod
+def encrypt_project_files(proname, filelist, mode=0):
+    prototype = PYFUNCTYPE(c_int, c_char_p, py_object, c_int)
+    dlfunc = prototype(('encrypt_project_files', _pytransform))
+    return dlfunc(proname.encode(), filelist, mode)
+
+
+def generate_project_capsule(licfile):
+    prikey, pubkey, prolic = _generate_project_capsule()
+    capkey = _encode_capsule_key_file(licfile)
+    return prikey, pubkey, capkey, prolic
+
+
+@dllmethod
+def _encode_capsule_key_file(licfile):
+    prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p)
+    dlfunc = prototype(('encode_capsule_key_file', _pytransform))
+    return dlfunc(licfile.encode(), None)
+
+
+@dllmethod
+def encrypt_files(key, filelist, mode=0):
+    t_key = c_char * 32
+    prototype = PYFUNCTYPE(c_int, t_key, py_object, c_int)
+    dlfunc = prototype(('encrypt_files', _pytransform))
+    return dlfunc(t_key(*key), filelist, mode)
+
+
+@dllmethod
+def generate_module_key(pubname, key):
+    t_key = c_char * 32
+    prototype = PYFUNCTYPE(py_object, c_char_p, t_key, c_char_p)
+    dlfunc = prototype(('generate_module_key', _pytransform))
+    return dlfunc(pubname.encode(), t_key(*key), None)
+
+#
+# Compatible for PyArmor v3.0
+#
+@dllmethod
+def old_init_runtime(systrace=0, sysprofile=1, threadtrace=0, threadprofile=1):
+    '''Only for old version, before PyArmor 3'''
+    pyarmor_init(is_runtime=1)
+    prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int)
+    _init_runtime = prototype(('init_runtime', _pytransform))
+    return _init_runtime(systrace, sysprofile, threadtrace, threadprofile)
+
+
+@dllmethod
+def import_module(modname, filename):
+    '''Only for old version, before PyArmor 3'''
+    prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p)
+    _import_module = prototype(('import_module', _pytransform))
+    return _import_module(modname.encode(), filename.encode())
+
+
+@dllmethod
+def exec_file(filename):
+    '''Only for old version, before PyArmor 3'''
+    prototype = PYFUNCTYPE(c_int, c_char_p)
+    _exec_file = prototype(('exec_file', _pytransform))
+    return _exec_file(filename.encode())
diff --git a/cs101courseware/dist/pytransform/__pycache__/__init__.cpython-36.pyc b/cs101courseware/dist/pytransform/__pycache__/__init__.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..75bb2539be65062814dda6c5080880056d0f8c70
Binary files /dev/null and b/cs101courseware/dist/pytransform/__pycache__/__init__.cpython-36.pyc differ
diff --git a/cs101courseware/dist/pytransform/_pytransform.dll b/cs101courseware/dist/pytransform/_pytransform.dll
new file mode 100644
index 0000000000000000000000000000000000000000..c121a4aa51ac71974b6ca0997006bfec3edb2673
Binary files /dev/null and b/cs101courseware/dist/pytransform/_pytransform.dll differ
diff --git a/cs101courseware/homework1.py b/cs101courseware/homework1.py
index 8ed701c0f7870469469d51c0ae354474d8a880a6..15620b0ffb74d843b1215536d7a9d2660d7617f0 100644
--- a/cs101courseware/homework1.py
+++ b/cs101courseware/homework1.py
@@ -12,6 +12,7 @@ def reverse_list(mylist):
     result = []
     for k in mylist:
         result = [k] + result
+
     return result
 
 def simple_list_question():
diff --git a/cs101courseware/pytransform/__init__.py b/cs101courseware/pytransform/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f656a22d3028cd47aedc8a79e8c9607a847957ac
--- /dev/null
+++ b/cs101courseware/pytransform/__init__.py
@@ -0,0 +1,454 @@
+# These module alos are used by protection code, so that protection
+# code needn't import anything
+import os
+import platform
+import sys
+import struct
+
+# Because ctypes is new from Python 2.5, so pytransform doesn't work
+# before Python 2.5
+#
+from ctypes import cdll, c_char, c_char_p, c_int, c_void_p, \
+    pythonapi, py_object, PYFUNCTYPE, CFUNCTYPE
+from fnmatch import fnmatch
+
+#
+# Support Platforms
+#
+plat_path = 'platforms'
+
+plat_table = (
+    ('windows', ('windows', 'cygwin-*')),
+    ('darwin', ('darwin', 'ios')),
+    ('linux', ('linux*',)),
+    ('freebsd', ('freebsd*', 'openbsd*')),
+    ('poky', ('poky',)),
+)
+
+arch_table = (
+    ('x86', ('i?86', )),
+    ('x86_64', ('x64', 'x86_64', 'amd64', 'intel')),
+    ('arm', ('armv5',)),
+    ('armv6', ('armv6l',)),
+    ('armv7', ('armv7l',)),
+    ('ppc64', ('ppc64le',)),
+    ('mips32', ('mips',)),
+    ('aarch32', ('aarch32',)),
+    ('aarch64', ('aarch64', 'arm64'))
+)
+
+#
+# Hardware type
+#
+HT_HARDDISK, HT_IFMAC, HT_IPV4, HT_IPV6, HT_DOMAIN = range(5)
+
+#
+# Global
+#
+_pytransform = None
+
+
+class PytransformError(Exception):
+    pass
+
+
+def dllmethod(func):
+    def wrap(*args, **kwargs):
+        return func(*args, **kwargs)
+    return wrap
+
+
+@dllmethod
+def version_info():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('version_info', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def init_pytransform():
+    major, minor = sys.version_info[0:2]
+    # Python2.5 no sys.maxsize but sys.maxint
+    # bitness = 64 if sys.maxsize > 2**32 else 32
+    prototype = PYFUNCTYPE(c_int, c_int, c_int, c_void_p)
+    init_module = prototype(('init_module', _pytransform))
+    ret = init_module(major, minor, pythonapi._handle)
+    if (ret & 0xF000) == 0x1000:
+        raise PytransformError('Initialize python wrapper failed (%d)'
+                               % (ret & 0xFFF))
+    return ret
+
+
+@dllmethod
+def init_runtime():
+    prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int)
+    _init_runtime = prototype(('init_runtime', _pytransform))
+    return _init_runtime(0, 0, 0, 0)
+
+
+@dllmethod
+def encrypt_code_object(pubkey, co, flags, suffix=''):
+    _pytransform.set_option(6, suffix.encode())
+    prototype = PYFUNCTYPE(py_object, py_object, py_object, c_int)
+    dlfunc = prototype(('encrypt_code_object', _pytransform))
+    return dlfunc(pubkey, co, flags)
+
+
+@dllmethod
+def generate_license_file(filename, priname, rcode, start=-1, count=1):
+    prototype = PYFUNCTYPE(c_int, c_char_p, c_char_p, c_char_p, c_int, c_int)
+    dlfunc = prototype(('generate_project_license_files', _pytransform))
+    return dlfunc(filename.encode(), priname.encode(), rcode.encode(),
+                  start, count) if sys.version_info[0] == 3 \
+        else dlfunc(filename, priname, rcode, start, count)
+
+
+@dllmethod
+def generate_license_key(prikey, keysize, rcode):
+    prototype = PYFUNCTYPE(py_object, c_char_p, c_int, c_char_p)
+    dlfunc = prototype(('generate_license_key', _pytransform))
+    return dlfunc(prikey, keysize, rcode) if sys.version_info[0] == 2 \
+        else dlfunc(prikey, keysize, rcode.encode())
+
+
+@dllmethod
+def get_registration_code():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('get_registration_code', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def get_expired_days():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('get_expired_days', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def clean_obj(obj, kind):
+    prototype = PYFUNCTYPE(c_int, py_object, c_int)
+    dlfunc = prototype(('clean_obj', _pytransform))
+    return dlfunc(obj, kind)
+
+
+def clean_str(*args):
+    tdict = {
+        'str': 0,
+        'bytearray': 1,
+        'unicode': 2
+    }
+    for obj in args:
+        k = tdict.get(type(obj).__name__)
+        if k is None:
+            raise RuntimeError('Can not clean object: %s' % obj)
+        clean_obj(obj, k)
+
+
+def get_hd_info(hdtype, name=None):
+    if hdtype not in range(HT_DOMAIN + 1):
+        raise RuntimeError('Invalid parameter hdtype: %s' % hdtype)
+    size = 256
+    t_buf = c_char * size
+    buf = t_buf()
+    cname = c_char_p(0 if name is None
+                     else name.encode('utf-8') if hasattr('name', 'encode')
+                     else name)
+    if (_pytransform.get_hd_info(hdtype, buf, size, cname) == -1):
+        raise PytransformError('Get hardware information failed')
+    return buf.value.decode()
+
+
+def show_hd_info():
+    return _pytransform.show_hd_info()
+
+
+def assert_armored(*names):
+    prototype = PYFUNCTYPE(py_object, py_object)
+    dlfunc = prototype(('assert_armored', _pytransform))
+
+    def wrapper(func):
+        def wrap_execute(*args, **kwargs):
+            dlfunc(names)
+            return func(*args, **kwargs)
+        return wrap_execute
+    return wrapper
+
+
+def get_license_info():
+    info = {
+        'ISSUER': None,
+        'EXPIRED': None,
+        'HARDDISK': None,
+        'IFMAC': None,
+        'IFIPV4': None,
+        'DOMAIN': None,
+        'DATA': None,
+        'CODE': None,
+    }
+    rcode = get_registration_code().decode()
+    if rcode.startswith('*VERSION:'):
+        index = rcode.find('\n')
+        info['ISSUER'] = rcode[9:index].split('.')[0].replace('-sn-1.txt', '')
+        rcode = rcode[index+1:]
+
+    index = 0
+    if rcode.startswith('*TIME:'):
+        from time import ctime
+        index = rcode.find('\n')
+        info['EXPIRED'] = ctime(float(rcode[6:index]))
+        index += 1
+
+    if rcode[index:].startswith('*FLAGS:'):
+        index += len('*FLAGS:') + 1
+        info['FLAGS'] = ord(rcode[index - 1])
+
+    prev = None
+    start = index
+    for k in ['HARDDISK', 'IFMAC', 'IFIPV4', 'DOMAIN', 'FIXKEY', 'CODE']:
+        index = rcode.find('*%s:' % k)
+        if index > -1:
+            if prev is not None:
+                info[prev] = rcode[start:index]
+            prev = k
+            start = index + len(k) + 2
+    info['CODE'] = rcode[start:]
+    i = info['CODE'].find(';')
+    if i > 0:
+        info['DATA'] = info['CODE'][i+1:]
+        info['CODE'] = info['CODE'][:i]
+    return info
+
+
+def get_license_code():
+    return get_license_info()['CODE']
+
+
+def get_user_data():
+    return get_license_info()['DATA']
+
+
+def _match_features(patterns, s):
+    for pat in patterns:
+        if fnmatch(s, pat):
+            return True
+
+
+def _gnu_get_libc_version():
+    try:
+        prototype = CFUNCTYPE(c_char_p)
+        ver = prototype(('gnu_get_libc_version', cdll.LoadLibrary('')))()
+        return ver.decode().split('.')
+    except Exception:
+        pass
+
+
+def format_platform(platid=None):
+    if platid:
+        return os.path.normpath(platid)
+
+    plat = platform.system().lower()
+    mach = platform.machine().lower()
+
+    for alias, platlist in plat_table:
+        if _match_features(platlist, plat):
+            plat = alias
+            break
+
+    if plat == 'linux':
+        cname, cver = platform.libc_ver()
+        if cname == 'musl':
+            plat = 'musl'
+        elif cname == 'libc':
+            plat = 'android'
+        elif cname == 'glibc':
+            v = _gnu_get_libc_version()
+            if v and len(v) >= 2 and (int(v[0]) * 100 + int(v[1])) < 214:
+                plat = 'centos6'
+
+    for alias, archlist in arch_table:
+        if _match_features(archlist, mach):
+            mach = alias
+            break
+
+    if plat == 'windows' and mach == 'x86_64':
+        bitness = struct.calcsize('P'.encode()) * 8
+        if bitness == 32:
+            mach = 'x86'
+
+    return os.path.join(plat, mach)
+
+
+# Load _pytransform library
+def _load_library(path=None, is_runtime=0, platid=None, suffix='', advanced=0):
+    path = os.path.dirname(__file__) if path is None \
+        else os.path.normpath(path)
+
+    plat = platform.system().lower()
+    name = '_pytransform' + suffix
+    if plat == 'linux':
+        filename = os.path.abspath(os.path.join(path, name + '.so'))
+    elif plat == 'darwin':
+        filename = os.path.join(path, name + '.dylib')
+    elif plat == 'windows':
+        filename = os.path.join(path, name + '.dll')
+    elif plat == 'freebsd':
+        filename = os.path.join(path, name + '.so')
+    else:
+        raise PytransformError('Platform %s not supported' % plat)
+
+    if platid is not None and os.path.isfile(platid):
+        filename = platid
+    elif platid is not None or not os.path.exists(filename) or not is_runtime:
+        libpath = platid if platid is not None and os.path.isabs(platid) else \
+            os.path.join(path, plat_path, format_platform(platid))
+        filename = os.path.join(libpath, os.path.basename(filename))
+
+    if not os.path.exists(filename):
+        raise PytransformError('Could not find "%s"' % filename)
+
+    try:
+        m = cdll.LoadLibrary(filename)
+    except Exception as e:
+        if sys.flags.debug:
+            print('Load %s failed:\n%s' % (filename, e))
+        raise
+
+    # Removed from v4.6.1
+    # if plat == 'linux':
+    #     m.set_option(-1, find_library('c').encode())
+
+    if not os.path.abspath('.') == os.path.abspath(path):
+        m.set_option(1, path.encode() if sys.version_info[0] == 3 else path)
+
+    # Required from Python3.6
+    m.set_option(2, sys.byteorder.encode())
+
+    if sys.flags.debug:
+        m.set_option(3, c_char_p(1))
+    m.set_option(4, c_char_p(not is_runtime))
+
+    # Disable advanced mode by default
+    m.set_option(5, c_char_p(not advanced))
+
+    # Set suffix for private package
+    if suffix:
+        m.set_option(6, suffix.encode())
+
+    return m
+
+
+def pyarmor_init(path=None, is_runtime=0, platid=None, suffix='', advanced=0):
+    global _pytransform
+    _pytransform = _load_library(path, is_runtime, platid, suffix, advanced)
+    return init_pytransform()
+
+
+def pyarmor_runtime(path=None, suffix='', advanced=0):
+    if _pytransform is not None:
+        return
+
+    try:
+        pyarmor_init(path, is_runtime=1, suffix=suffix, advanced=advanced)
+        init_runtime()
+    except Exception as e:
+        if sys.flags.debug or hasattr(sys, '_catch_pyarmor'):
+            raise
+        sys.stderr.write("%s\n" % str(e))
+        sys.exit(1)
+
+
+# ----------------------------------------------------------
+# End of pytransform
+# ----------------------------------------------------------
+
+#
+# Not available from v5.6
+#
+
+
+def generate_capsule(licfile):
+    prikey, pubkey, prolic = _generate_project_capsule()
+    capkey, newkey = _generate_pytransform_key(licfile, pubkey)
+    return prikey, pubkey, capkey, newkey, prolic
+
+
+@dllmethod
+def _generate_project_capsule():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('generate_project_capsule', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def _generate_pytransform_key(licfile, pubkey):
+    prototype = PYFUNCTYPE(py_object, c_char_p, py_object)
+    dlfunc = prototype(('generate_pytransform_key', _pytransform))
+    return dlfunc(licfile.encode() if sys.version_info[0] == 3 else licfile,
+                  pubkey)
+
+
+#
+# Deprecated functions from v5.1
+#
+@dllmethod
+def encrypt_project_files(proname, filelist, mode=0):
+    prototype = PYFUNCTYPE(c_int, c_char_p, py_object, c_int)
+    dlfunc = prototype(('encrypt_project_files', _pytransform))
+    return dlfunc(proname.encode(), filelist, mode)
+
+
+def generate_project_capsule(licfile):
+    prikey, pubkey, prolic = _generate_project_capsule()
+    capkey = _encode_capsule_key_file(licfile)
+    return prikey, pubkey, capkey, prolic
+
+
+@dllmethod
+def _encode_capsule_key_file(licfile):
+    prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p)
+    dlfunc = prototype(('encode_capsule_key_file', _pytransform))
+    return dlfunc(licfile.encode(), None)
+
+
+@dllmethod
+def encrypt_files(key, filelist, mode=0):
+    t_key = c_char * 32
+    prototype = PYFUNCTYPE(c_int, t_key, py_object, c_int)
+    dlfunc = prototype(('encrypt_files', _pytransform))
+    return dlfunc(t_key(*key), filelist, mode)
+
+
+@dllmethod
+def generate_module_key(pubname, key):
+    t_key = c_char * 32
+    prototype = PYFUNCTYPE(py_object, c_char_p, t_key, c_char_p)
+    dlfunc = prototype(('generate_module_key', _pytransform))
+    return dlfunc(pubname.encode(), t_key(*key), None)
+
+#
+# Compatible for PyArmor v3.0
+#
+@dllmethod
+def old_init_runtime(systrace=0, sysprofile=1, threadtrace=0, threadprofile=1):
+    '''Only for old version, before PyArmor 3'''
+    pyarmor_init(is_runtime=1)
+    prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int)
+    _init_runtime = prototype(('init_runtime', _pytransform))
+    return _init_runtime(systrace, sysprofile, threadtrace, threadprofile)
+
+
+@dllmethod
+def import_module(modname, filename):
+    '''Only for old version, before PyArmor 3'''
+    prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p)
+    _import_module = prototype(('import_module', _pytransform))
+    return _import_module(modname.encode(), filename.encode())
+
+
+@dllmethod
+def exec_file(filename):
+    '''Only for old version, before PyArmor 3'''
+    prototype = PYFUNCTYPE(c_int, c_char_p)
+    _exec_file = prototype(('exec_file', _pytransform))
+    return _exec_file(filename.encode())
diff --git a/cs101courseware/pytransform/__pycache__/__init__.cpython-36.pyc b/cs101courseware/pytransform/__pycache__/__init__.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..75bb2539be65062814dda6c5080880056d0f8c70
Binary files /dev/null and b/cs101courseware/pytransform/__pycache__/__init__.cpython-36.pyc differ
diff --git a/cs101courseware/pytransform/_pytransform.dll b/cs101courseware/pytransform/_pytransform.dll
new file mode 100644
index 0000000000000000000000000000000000000000..c121a4aa51ac71974b6ca0997006bfec3edb2673
Binary files /dev/null and b/cs101courseware/pytransform/_pytransform.dll differ
diff --git a/pytransform/__init__.py b/pytransform/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f656a22d3028cd47aedc8a79e8c9607a847957ac
--- /dev/null
+++ b/pytransform/__init__.py
@@ -0,0 +1,454 @@
+# These module alos are used by protection code, so that protection
+# code needn't import anything
+import os
+import platform
+import sys
+import struct
+
+# Because ctypes is new from Python 2.5, so pytransform doesn't work
+# before Python 2.5
+#
+from ctypes import cdll, c_char, c_char_p, c_int, c_void_p, \
+    pythonapi, py_object, PYFUNCTYPE, CFUNCTYPE
+from fnmatch import fnmatch
+
+#
+# Support Platforms
+#
+plat_path = 'platforms'
+
+plat_table = (
+    ('windows', ('windows', 'cygwin-*')),
+    ('darwin', ('darwin', 'ios')),
+    ('linux', ('linux*',)),
+    ('freebsd', ('freebsd*', 'openbsd*')),
+    ('poky', ('poky',)),
+)
+
+arch_table = (
+    ('x86', ('i?86', )),
+    ('x86_64', ('x64', 'x86_64', 'amd64', 'intel')),
+    ('arm', ('armv5',)),
+    ('armv6', ('armv6l',)),
+    ('armv7', ('armv7l',)),
+    ('ppc64', ('ppc64le',)),
+    ('mips32', ('mips',)),
+    ('aarch32', ('aarch32',)),
+    ('aarch64', ('aarch64', 'arm64'))
+)
+
+#
+# Hardware type
+#
+HT_HARDDISK, HT_IFMAC, HT_IPV4, HT_IPV6, HT_DOMAIN = range(5)
+
+#
+# Global
+#
+_pytransform = None
+
+
+class PytransformError(Exception):
+    pass
+
+
+def dllmethod(func):
+    def wrap(*args, **kwargs):
+        return func(*args, **kwargs)
+    return wrap
+
+
+@dllmethod
+def version_info():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('version_info', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def init_pytransform():
+    major, minor = sys.version_info[0:2]
+    # Python2.5 no sys.maxsize but sys.maxint
+    # bitness = 64 if sys.maxsize > 2**32 else 32
+    prototype = PYFUNCTYPE(c_int, c_int, c_int, c_void_p)
+    init_module = prototype(('init_module', _pytransform))
+    ret = init_module(major, minor, pythonapi._handle)
+    if (ret & 0xF000) == 0x1000:
+        raise PytransformError('Initialize python wrapper failed (%d)'
+                               % (ret & 0xFFF))
+    return ret
+
+
+@dllmethod
+def init_runtime():
+    prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int)
+    _init_runtime = prototype(('init_runtime', _pytransform))
+    return _init_runtime(0, 0, 0, 0)
+
+
+@dllmethod
+def encrypt_code_object(pubkey, co, flags, suffix=''):
+    _pytransform.set_option(6, suffix.encode())
+    prototype = PYFUNCTYPE(py_object, py_object, py_object, c_int)
+    dlfunc = prototype(('encrypt_code_object', _pytransform))
+    return dlfunc(pubkey, co, flags)
+
+
+@dllmethod
+def generate_license_file(filename, priname, rcode, start=-1, count=1):
+    prototype = PYFUNCTYPE(c_int, c_char_p, c_char_p, c_char_p, c_int, c_int)
+    dlfunc = prototype(('generate_project_license_files', _pytransform))
+    return dlfunc(filename.encode(), priname.encode(), rcode.encode(),
+                  start, count) if sys.version_info[0] == 3 \
+        else dlfunc(filename, priname, rcode, start, count)
+
+
+@dllmethod
+def generate_license_key(prikey, keysize, rcode):
+    prototype = PYFUNCTYPE(py_object, c_char_p, c_int, c_char_p)
+    dlfunc = prototype(('generate_license_key', _pytransform))
+    return dlfunc(prikey, keysize, rcode) if sys.version_info[0] == 2 \
+        else dlfunc(prikey, keysize, rcode.encode())
+
+
+@dllmethod
+def get_registration_code():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('get_registration_code', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def get_expired_days():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('get_expired_days', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def clean_obj(obj, kind):
+    prototype = PYFUNCTYPE(c_int, py_object, c_int)
+    dlfunc = prototype(('clean_obj', _pytransform))
+    return dlfunc(obj, kind)
+
+
+def clean_str(*args):
+    tdict = {
+        'str': 0,
+        'bytearray': 1,
+        'unicode': 2
+    }
+    for obj in args:
+        k = tdict.get(type(obj).__name__)
+        if k is None:
+            raise RuntimeError('Can not clean object: %s' % obj)
+        clean_obj(obj, k)
+
+
+def get_hd_info(hdtype, name=None):
+    if hdtype not in range(HT_DOMAIN + 1):
+        raise RuntimeError('Invalid parameter hdtype: %s' % hdtype)
+    size = 256
+    t_buf = c_char * size
+    buf = t_buf()
+    cname = c_char_p(0 if name is None
+                     else name.encode('utf-8') if hasattr('name', 'encode')
+                     else name)
+    if (_pytransform.get_hd_info(hdtype, buf, size, cname) == -1):
+        raise PytransformError('Get hardware information failed')
+    return buf.value.decode()
+
+
+def show_hd_info():
+    return _pytransform.show_hd_info()
+
+
+def assert_armored(*names):
+    prototype = PYFUNCTYPE(py_object, py_object)
+    dlfunc = prototype(('assert_armored', _pytransform))
+
+    def wrapper(func):
+        def wrap_execute(*args, **kwargs):
+            dlfunc(names)
+            return func(*args, **kwargs)
+        return wrap_execute
+    return wrapper
+
+
+def get_license_info():
+    info = {
+        'ISSUER': None,
+        'EXPIRED': None,
+        'HARDDISK': None,
+        'IFMAC': None,
+        'IFIPV4': None,
+        'DOMAIN': None,
+        'DATA': None,
+        'CODE': None,
+    }
+    rcode = get_registration_code().decode()
+    if rcode.startswith('*VERSION:'):
+        index = rcode.find('\n')
+        info['ISSUER'] = rcode[9:index].split('.')[0].replace('-sn-1.txt', '')
+        rcode = rcode[index+1:]
+
+    index = 0
+    if rcode.startswith('*TIME:'):
+        from time import ctime
+        index = rcode.find('\n')
+        info['EXPIRED'] = ctime(float(rcode[6:index]))
+        index += 1
+
+    if rcode[index:].startswith('*FLAGS:'):
+        index += len('*FLAGS:') + 1
+        info['FLAGS'] = ord(rcode[index - 1])
+
+    prev = None
+    start = index
+    for k in ['HARDDISK', 'IFMAC', 'IFIPV4', 'DOMAIN', 'FIXKEY', 'CODE']:
+        index = rcode.find('*%s:' % k)
+        if index > -1:
+            if prev is not None:
+                info[prev] = rcode[start:index]
+            prev = k
+            start = index + len(k) + 2
+    info['CODE'] = rcode[start:]
+    i = info['CODE'].find(';')
+    if i > 0:
+        info['DATA'] = info['CODE'][i+1:]
+        info['CODE'] = info['CODE'][:i]
+    return info
+
+
+def get_license_code():
+    return get_license_info()['CODE']
+
+
+def get_user_data():
+    return get_license_info()['DATA']
+
+
+def _match_features(patterns, s):
+    for pat in patterns:
+        if fnmatch(s, pat):
+            return True
+
+
+def _gnu_get_libc_version():
+    try:
+        prototype = CFUNCTYPE(c_char_p)
+        ver = prototype(('gnu_get_libc_version', cdll.LoadLibrary('')))()
+        return ver.decode().split('.')
+    except Exception:
+        pass
+
+
+def format_platform(platid=None):
+    if platid:
+        return os.path.normpath(platid)
+
+    plat = platform.system().lower()
+    mach = platform.machine().lower()
+
+    for alias, platlist in plat_table:
+        if _match_features(platlist, plat):
+            plat = alias
+            break
+
+    if plat == 'linux':
+        cname, cver = platform.libc_ver()
+        if cname == 'musl':
+            plat = 'musl'
+        elif cname == 'libc':
+            plat = 'android'
+        elif cname == 'glibc':
+            v = _gnu_get_libc_version()
+            if v and len(v) >= 2 and (int(v[0]) * 100 + int(v[1])) < 214:
+                plat = 'centos6'
+
+    for alias, archlist in arch_table:
+        if _match_features(archlist, mach):
+            mach = alias
+            break
+
+    if plat == 'windows' and mach == 'x86_64':
+        bitness = struct.calcsize('P'.encode()) * 8
+        if bitness == 32:
+            mach = 'x86'
+
+    return os.path.join(plat, mach)
+
+
+# Load _pytransform library
+def _load_library(path=None, is_runtime=0, platid=None, suffix='', advanced=0):
+    path = os.path.dirname(__file__) if path is None \
+        else os.path.normpath(path)
+
+    plat = platform.system().lower()
+    name = '_pytransform' + suffix
+    if plat == 'linux':
+        filename = os.path.abspath(os.path.join(path, name + '.so'))
+    elif plat == 'darwin':
+        filename = os.path.join(path, name + '.dylib')
+    elif plat == 'windows':
+        filename = os.path.join(path, name + '.dll')
+    elif plat == 'freebsd':
+        filename = os.path.join(path, name + '.so')
+    else:
+        raise PytransformError('Platform %s not supported' % plat)
+
+    if platid is not None and os.path.isfile(platid):
+        filename = platid
+    elif platid is not None or not os.path.exists(filename) or not is_runtime:
+        libpath = platid if platid is not None and os.path.isabs(platid) else \
+            os.path.join(path, plat_path, format_platform(platid))
+        filename = os.path.join(libpath, os.path.basename(filename))
+
+    if not os.path.exists(filename):
+        raise PytransformError('Could not find "%s"' % filename)
+
+    try:
+        m = cdll.LoadLibrary(filename)
+    except Exception as e:
+        if sys.flags.debug:
+            print('Load %s failed:\n%s' % (filename, e))
+        raise
+
+    # Removed from v4.6.1
+    # if plat == 'linux':
+    #     m.set_option(-1, find_library('c').encode())
+
+    if not os.path.abspath('.') == os.path.abspath(path):
+        m.set_option(1, path.encode() if sys.version_info[0] == 3 else path)
+
+    # Required from Python3.6
+    m.set_option(2, sys.byteorder.encode())
+
+    if sys.flags.debug:
+        m.set_option(3, c_char_p(1))
+    m.set_option(4, c_char_p(not is_runtime))
+
+    # Disable advanced mode by default
+    m.set_option(5, c_char_p(not advanced))
+
+    # Set suffix for private package
+    if suffix:
+        m.set_option(6, suffix.encode())
+
+    return m
+
+
+def pyarmor_init(path=None, is_runtime=0, platid=None, suffix='', advanced=0):
+    global _pytransform
+    _pytransform = _load_library(path, is_runtime, platid, suffix, advanced)
+    return init_pytransform()
+
+
+def pyarmor_runtime(path=None, suffix='', advanced=0):
+    if _pytransform is not None:
+        return
+
+    try:
+        pyarmor_init(path, is_runtime=1, suffix=suffix, advanced=advanced)
+        init_runtime()
+    except Exception as e:
+        if sys.flags.debug or hasattr(sys, '_catch_pyarmor'):
+            raise
+        sys.stderr.write("%s\n" % str(e))
+        sys.exit(1)
+
+
+# ----------------------------------------------------------
+# End of pytransform
+# ----------------------------------------------------------
+
+#
+# Not available from v5.6
+#
+
+
+def generate_capsule(licfile):
+    prikey, pubkey, prolic = _generate_project_capsule()
+    capkey, newkey = _generate_pytransform_key(licfile, pubkey)
+    return prikey, pubkey, capkey, newkey, prolic
+
+
+@dllmethod
+def _generate_project_capsule():
+    prototype = PYFUNCTYPE(py_object)
+    dlfunc = prototype(('generate_project_capsule', _pytransform))
+    return dlfunc()
+
+
+@dllmethod
+def _generate_pytransform_key(licfile, pubkey):
+    prototype = PYFUNCTYPE(py_object, c_char_p, py_object)
+    dlfunc = prototype(('generate_pytransform_key', _pytransform))
+    return dlfunc(licfile.encode() if sys.version_info[0] == 3 else licfile,
+                  pubkey)
+
+
+#
+# Deprecated functions from v5.1
+#
+@dllmethod
+def encrypt_project_files(proname, filelist, mode=0):
+    prototype = PYFUNCTYPE(c_int, c_char_p, py_object, c_int)
+    dlfunc = prototype(('encrypt_project_files', _pytransform))
+    return dlfunc(proname.encode(), filelist, mode)
+
+
+def generate_project_capsule(licfile):
+    prikey, pubkey, prolic = _generate_project_capsule()
+    capkey = _encode_capsule_key_file(licfile)
+    return prikey, pubkey, capkey, prolic
+
+
+@dllmethod
+def _encode_capsule_key_file(licfile):
+    prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p)
+    dlfunc = prototype(('encode_capsule_key_file', _pytransform))
+    return dlfunc(licfile.encode(), None)
+
+
+@dllmethod
+def encrypt_files(key, filelist, mode=0):
+    t_key = c_char * 32
+    prototype = PYFUNCTYPE(c_int, t_key, py_object, c_int)
+    dlfunc = prototype(('encrypt_files', _pytransform))
+    return dlfunc(t_key(*key), filelist, mode)
+
+
+@dllmethod
+def generate_module_key(pubname, key):
+    t_key = c_char * 32
+    prototype = PYFUNCTYPE(py_object, c_char_p, t_key, c_char_p)
+    dlfunc = prototype(('generate_module_key', _pytransform))
+    return dlfunc(pubname.encode(), t_key(*key), None)
+
+#
+# Compatible for PyArmor v3.0
+#
+@dllmethod
+def old_init_runtime(systrace=0, sysprofile=1, threadtrace=0, threadprofile=1):
+    '''Only for old version, before PyArmor 3'''
+    pyarmor_init(is_runtime=1)
+    prototype = PYFUNCTYPE(c_int, c_int, c_int, c_int, c_int)
+    _init_runtime = prototype(('init_runtime', _pytransform))
+    return _init_runtime(systrace, sysprofile, threadtrace, threadprofile)
+
+
+@dllmethod
+def import_module(modname, filename):
+    '''Only for old version, before PyArmor 3'''
+    prototype = PYFUNCTYPE(py_object, c_char_p, c_char_p)
+    _import_module = prototype(('import_module', _pytransform))
+    return _import_module(modname.encode(), filename.encode())
+
+
+@dllmethod
+def exec_file(filename):
+    '''Only for old version, before PyArmor 3'''
+    prototype = PYFUNCTYPE(c_int, c_char_p)
+    _exec_file = prototype(('exec_file', _pytransform))
+    return _exec_file(filename.encode())
diff --git a/pytransform/__pycache__/__init__.cpython-36.pyc b/pytransform/__pycache__/__init__.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..75bb2539be65062814dda6c5080880056d0f8c70
Binary files /dev/null and b/pytransform/__pycache__/__init__.cpython-36.pyc differ
diff --git a/pytransform/_pytransform.dll b/pytransform/_pytransform.dll
new file mode 100644
index 0000000000000000000000000000000000000000..b1af3263115ebab5c213656cf1daf87b3510116b
Binary files /dev/null and b/pytransform/_pytransform.dll differ
diff --git a/tutorial/FruitReport_resources_do_not_hand_in.dat b/tutorial/FruitReport_resources_do_not_hand_in.dat
new file mode 100644
index 0000000000000000000000000000000000000000..892b881307df3758dcc4db25f6cf0343a234a721
Binary files /dev/null and b/tutorial/FruitReport_resources_do_not_hand_in.dat differ
diff --git a/tutorial/VERSION b/tutorial/VERSION
new file mode 100644
index 0000000000000000000000000000000000000000..6af849e5dafeb2afad013892d13e1e5eccb7156e
--- /dev/null
+++ b/tutorial/VERSION
@@ -0,0 +1 @@
+v1.002
diff --git a/tutorial/__pycache__/addition.cpython-36.pyc b/tutorial/__pycache__/addition.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..32acf71145f4e5425fd4852c6079b18c5c6243a1
Binary files /dev/null and b/tutorial/__pycache__/addition.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/buyLotsOfFruit.cpython-36.pyc b/tutorial/__pycache__/buyLotsOfFruit.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f2c869210e333cd4ddfd077781f8c0ee44b02495
Binary files /dev/null and b/tutorial/__pycache__/buyLotsOfFruit.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/fruit_project.cpython-36.pyc b/tutorial/__pycache__/fruit_project.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..de630b05a908efd4b636bb042df5b7f733a4d635
Binary files /dev/null and b/tutorial/__pycache__/fruit_project.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/fruit_project_grade.cpython-36.pyc b/tutorial/__pycache__/fruit_project_grade.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7b9d759e978d3f49c894b14c87d51d748b75ac1b
Binary files /dev/null and b/tutorial/__pycache__/fruit_project_grade.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/grading.cpython-36.pyc b/tutorial/__pycache__/grading.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ab7122c7d868411c3a47d92b5e2cd9a881672135
Binary files /dev/null and b/tutorial/__pycache__/grading.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/projectParams.cpython-36.pyc b/tutorial/__pycache__/projectParams.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..84ec121081d18e05d64b51de0d64cf4eb2cf7c25
Binary files /dev/null and b/tutorial/__pycache__/projectParams.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/shop.cpython-36.pyc b/tutorial/__pycache__/shop.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..24f4a625f61b6d3f1d2ca6b23db63ed98716bdf2
Binary files /dev/null and b/tutorial/__pycache__/shop.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/shopAroundTown.cpython-36.pyc b/tutorial/__pycache__/shopAroundTown.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b6748bc9a4ddfc7faa6ea466e06a45124bd9ed8a
Binary files /dev/null and b/tutorial/__pycache__/shopAroundTown.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/shopSmart.cpython-36.pyc b/tutorial/__pycache__/shopSmart.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7c14ba34925d8dba50bcc4195cd01aacd75c33fd
Binary files /dev/null and b/tutorial/__pycache__/shopSmart.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/testClasses.cpython-36.pyc b/tutorial/__pycache__/testClasses.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a31cbd47a4375b82158f06bd1628d7b533647024
Binary files /dev/null and b/tutorial/__pycache__/testClasses.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/testParser.cpython-36.pyc b/tutorial/__pycache__/testParser.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0ce3ffd502b30cf1edd7cb1d50ecb316d2e9ba7f
Binary files /dev/null and b/tutorial/__pycache__/testParser.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/textDisplay.cpython-36.pyc b/tutorial/__pycache__/textDisplay.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..02c57770bfa5d8e5cb757f861e7f7d6abe2a32e7
Binary files /dev/null and b/tutorial/__pycache__/textDisplay.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/town.cpython-36.pyc b/tutorial/__pycache__/town.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..dfd4a94382d01bde5a14cc7b4eb444046f8c49e2
Binary files /dev/null and b/tutorial/__pycache__/town.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/tutorialTestClasses.cpython-36.pyc b/tutorial/__pycache__/tutorialTestClasses.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..36840c873bb96d59f3c28416dadb61c4013527db
Binary files /dev/null and b/tutorial/__pycache__/tutorialTestClasses.cpython-36.pyc differ
diff --git a/tutorial/__pycache__/util.cpython-36.pyc b/tutorial/__pycache__/util.cpython-36.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b4246739203d4fd2c21885657deaf16883e55d90
Binary files /dev/null and b/tutorial/__pycache__/util.cpython-36.pyc differ
diff --git a/tutorial/addition.py b/tutorial/addition.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d593d2643590a749d2c93b993b6491370c56542
--- /dev/null
+++ b/tutorial/addition.py
@@ -0,0 +1,23 @@
+# addition.py
+# -----------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+"""
+Run python autograder.py
+"""
+
+
+def add(a, b):
+    "Return the sum of a and b"
+    "*** YOUR CODE HERE ***"
+    return a + b
diff --git a/tutorial/autograder.py b/tutorial/autograder.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f339a8982dbfd2f8b420b921df864eccfb1a567
--- /dev/null
+++ b/tutorial/autograder.py
@@ -0,0 +1,359 @@
+# autograder.py
+# -------------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+# imports from python standard library
+from __future__ import print_function
+import grading
+import imp
+import optparse
+import os
+import re
+import sys
+import projectParams
+import random
+random.seed(0)
+try: 
+    from pacman import GameState
+except:
+    pass
+
+# register arguments and set default values
+def readCommand(argv):
+    parser = optparse.OptionParser(description = 'Run public tests on student code')
+    parser.set_defaults(generateSolutions=False, edxOutput=False, gsOutput=False, muteOutput=False, printTestCase=False, noGraphics=False)
+    parser.add_option('--test-directory',
+                      dest = 'testRoot',
+                      default = 'test_cases',
+                      help = 'Root test directory which contains subdirectories corresponding to each question')
+    parser.add_option('--student-code',
+                      dest = 'studentCode',
+                      default = projectParams.STUDENT_CODE_DEFAULT,
+                      help = 'comma separated list of student code files')
+    parser.add_option('--code-directory',
+                    dest = 'codeRoot',
+                    default = "",
+                    help = 'Root directory containing the student and testClass code')
+    parser.add_option('--test-case-code',
+                      dest = 'testCaseCode',
+                      default = projectParams.PROJECT_TEST_CLASSES,
+                      help = 'class containing testClass classes for this project')
+    parser.add_option('--generate-solutions',
+                      dest = 'generateSolutions',
+                      action = 'store_true',
+                      help = 'Write solutions generated to .solution file')
+    parser.add_option('--edx-output',
+                    dest = 'edxOutput',
+                    action = 'store_true',
+                    help = 'Generate edX output files')
+    parser.add_option('--gradescope-output',
+                    dest = 'gsOutput',
+                    action = 'store_true',
+                    help = 'Generate GradeScope output files')
+    parser.add_option('--mute',
+                    dest = 'muteOutput',
+                    action = 'store_true',
+                    help = 'Mute output from executing tests')
+    parser.add_option('--print-tests', '-p',
+                    dest = 'printTestCase',
+                    action = 'store_true',
+                    help = 'Print each test case before running them.')
+    parser.add_option('--test', '-t',
+                      dest = 'runTest',
+                      default = None,
+                      help = 'Run one particular test.  Relative to test root.')
+    parser.add_option('--question', '-q',
+                    dest = 'gradeQuestion',
+                    default = None,
+                    help = 'Grade one particular question.')
+    parser.add_option('--no-graphics',
+                    dest = 'noGraphics',
+                    action = 'store_true',
+                    help = 'No graphics display for pacman games.')
+    (options, args) = parser.parse_args(argv)
+    return options
+
+
+# confirm we should author solution files
+def confirmGenerate():
+    print('WARNING: this action will overwrite any solution files.')
+    print('Are you sure you want to proceed? (yes/no)')
+    while True:
+        ans = sys.stdin.readline().strip()
+        if ans == 'yes':
+            break
+        elif ans == 'no':
+            sys.exit(0)
+        else:
+            print('please answer either "yes" or "no"')
+
+
+# TODO: Fix this so that it tracebacks work correctly
+# Looking at source of the traceback module, presuming it works
+# the same as the intepreters, it uses co_filename.  This is,
+# however, a readonly attribute.
+def setModuleName(module, filename):
+    functionType = type(confirmGenerate)
+    classType = type(optparse.Option)
+
+    for i in dir(module):
+        o = getattr(module, i)
+        if hasattr(o, '__file__'): continue
+
+        if type(o) == functionType:
+            setattr(o, '__file__', filename)
+        elif type(o) == classType:
+            setattr(o, '__file__', filename)
+            # TODO: assign member __file__'s?
+        #print i, type(o)
+
+
+#from cStringIO import StringIO
+
+def loadModuleString(moduleSource):
+    # Below broken, imp doesn't believe its being passed a file:
+    #    ValueError: load_module arg#2 should be a file or None
+    #
+    #f = StringIO(moduleCodeDict[k])
+    #tmp = imp.load_module(k, f, k, (".py", "r", imp.PY_SOURCE))
+    tmp = imp.new_module(k)
+    #exec moduleCodeDict[k] in tmp.__dict__
+    setModuleName(tmp, k)
+    return tmp
+
+import py_compile
+
+def loadModuleFile(moduleName, filePath):
+    with open(filePath, 'r') as f:
+        return imp.load_module(moduleName, f, "%s.py" % moduleName, (".py", "r", imp.PY_SOURCE))
+
+
+def readFile(path, root=""):
+    "Read file from disk at specified path and return as string"
+    with open(os.path.join(root, path), 'r') as handle:
+        return handle.read()
+
+
+#######################################################################
+# Error Hint Map
+#######################################################################
+
+# TODO: use these
+ERROR_HINT_MAP = {
+  'q1': {
+    "<type 'exceptions.IndexError'>": """
+      We noticed that your project threw an IndexError on q1.
+      While many things may cause this, it may have been from
+      assuming a certain number of successors from a state space
+      or assuming a certain number of actions available from a given
+      state. Try making your code more general (no hardcoded indices)
+      and submit again!
+    """
+  },
+  'q3': {
+      "<type 'exceptions.AttributeError'>": """
+        We noticed that your project threw an AttributeError on q3.
+        While many things may cause this, it may have been from assuming
+        a certain size or structure to the state space. For example, if you have
+        a line of code assuming that the state is (x, y) and we run your code
+        on a state space with (x, y, z), this error could be thrown. Try
+        making your code more general and submit again!
+
+    """
+  }
+}
+
+import pprint
+
+def splitStrings(d):
+    d2 = dict(d)
+    for k in d:
+        if k[0:2] == "__":
+            del d2[k]
+            continue
+        if d2[k].find("\n") >= 0:
+            d2[k] = d2[k].split("\n")
+    return d2
+
+
+def printTest(testDict, solutionDict):
+    pp = pprint.PrettyPrinter(indent=4)
+    print("Test case:")
+    for line in testDict["__raw_lines__"]:
+        print("   |", line)
+    print("Solution:")
+    for line in solutionDict["__raw_lines__"]:
+        print("   |", line)
+
+
+def runTest(testName, moduleDict, printTestCase=False, display=None):
+    import testParser
+    import testClasses
+    for module in moduleDict:
+        setattr(sys.modules[__name__], module, moduleDict[module])
+
+    testDict = testParser.TestParser(testName + ".test").parse()
+    solutionDict = testParser.TestParser(testName + ".solution").parse()
+    test_out_file = os.path.join('%s.test_output' % testName)
+    testDict['test_out_file'] = test_out_file
+    testClass = getattr(projectTestClasses, testDict['class'])
+
+    questionClass = getattr(testClasses, 'Question')
+    question = questionClass({'max_points': 0}, display)
+    testCase = testClass(question, testDict)
+
+    if printTestCase:
+        printTest(testDict, solutionDict)
+
+    # This is a fragile hack to create a stub grades object
+    grades = grading.Grades(projectParams.PROJECT_NAME, [(None,0)])
+    testCase.execute(grades, moduleDict, solutionDict)
+
+
+# returns all the tests you need to run in order to run question
+def getDepends(testParser, testRoot, question):
+    allDeps = [question]
+    questionDict = testParser.TestParser(os.path.join(testRoot, question, 'CONFIG')).parse()
+    if 'depends' in questionDict:
+        depends = questionDict['depends'].split()
+        for d in depends:
+            # run dependencies first
+            allDeps = getDepends(testParser, testRoot, d) + allDeps
+    return allDeps
+
+# get list of questions to grade
+def getTestSubdirs(testParser, testRoot, questionToGrade):
+    problemDict = testParser.TestParser(os.path.join(testRoot, 'CONFIG')).parse()
+    if questionToGrade != None:
+        questions = getDepends(testParser, testRoot, questionToGrade)
+        if len(questions) > 1:
+            print('Note: due to dependencies, the following tests will be run: %s' % ' '.join(questions))
+        return questions
+    if 'order' in problemDict:
+        return problemDict['order'].split()
+    return sorted(os.listdir(testRoot))
+
+
+# evaluate student code
+def evaluate(generateSolutions, testRoot, moduleDict, exceptionMap=ERROR_HINT_MAP,
+             edxOutput=False, muteOutput=False, gsOutput=False,
+            printTestCase=False, questionToGrade=None, display=None):
+    # imports of testbench code.  note that the testClasses import must follow
+    # the import of student code due to dependencies
+    import testParser
+    import testClasses
+    for module in moduleDict:
+        setattr(sys.modules[__name__], module, moduleDict[module])
+
+    questions = []
+    questionDicts = {}
+    test_subdirs = getTestSubdirs(testParser, testRoot, questionToGrade)
+    for q in test_subdirs:
+        subdir_path = os.path.join(testRoot, q)
+        if not os.path.isdir(subdir_path) or q[0] == '.':
+            continue
+
+        # create a question object
+        questionDict = testParser.TestParser(os.path.join(subdir_path, 'CONFIG')).parse()
+        questionClass = getattr(testClasses, questionDict['class'])
+        question = questionClass(questionDict, display)
+        questionDicts[q] = questionDict
+
+        # load test cases into question
+        tests = filter(lambda t: re.match('[^#~.].*\.test\Z', t), os.listdir(subdir_path))
+        tests = map(lambda t: re.match('(.*)\.test\Z', t).group(1), tests)
+        for t in sorted(tests):
+            test_file = os.path.join(subdir_path, '%s.test' % t)
+            solution_file = os.path.join(subdir_path, '%s.solution' % t)
+            test_out_file = os.path.join(subdir_path, '%s.test_output' % t)
+            testDict = testParser.TestParser(test_file).parse()
+            if testDict.get("disabled", "false").lower() == "true":
+                continue
+            testDict['test_out_file'] = test_out_file
+            testClass = getattr(projectTestClasses, testDict['class'])
+            testCase = testClass(question, testDict)
+            def makefun(testCase, solution_file):
+                if generateSolutions:
+                    # write solution file to disk
+                    return lambda grades: testCase.writeSolution(moduleDict, solution_file)
+                else:
+                    # read in solution dictionary and pass as an argument
+                    testDict = testParser.TestParser(test_file).parse()
+                    solutionDict = testParser.TestParser(solution_file).parse()
+                    if printTestCase:
+                        return lambda grades: printTest(testDict, solutionDict) or testCase.execute(grades, moduleDict, solutionDict)
+                    else:
+                        return lambda grades: testCase.execute(grades, moduleDict, solutionDict)
+            question.addTestCase(testCase, makefun(testCase, solution_file))
+
+        # Note extra function is necessary for scoping reasons
+        def makefun(question):
+            return lambda grades: question.execute(grades)
+        setattr(sys.modules[__name__], q, makefun(question))
+        questions.append((q, question.getMaxPoints()))
+
+    grades = grading.Grades(projectParams.PROJECT_NAME, questions,
+                            gsOutput=gsOutput, edxOutput=edxOutput, muteOutput=muteOutput)
+    if questionToGrade == None:
+        for q in questionDicts:
+            for prereq in questionDicts[q].get('depends', '').split():
+                grades.addPrereq(q, prereq)
+
+    grades.grade(sys.modules[__name__], bonusPic = projectParams.BONUS_PIC)
+    return grades.points
+
+
+
+def getDisplay(graphicsByDefault, options=None):
+    graphics = graphicsByDefault
+    if options is not None and options.noGraphics:
+        graphics = False
+    if graphics:
+        try:
+            import graphicsDisplay
+            return graphicsDisplay.PacmanGraphics(1, frameTime=.05)
+        except ImportError:
+            pass
+    import textDisplay
+    return textDisplay.NullGraphics()
+
+
+
+
+if __name__ == '__main__':
+    options = readCommand(sys.argv)
+    if options.generateSolutions:
+        confirmGenerate()
+    codePaths = options.studentCode.split(',')
+    # moduleCodeDict = {}
+    # for cp in codePaths:
+    #     moduleName = re.match('.*?([^/]*)\.py', cp).group(1)
+    #     moduleCodeDict[moduleName] = readFile(cp, root=options.codeRoot)
+    # moduleCodeDict['projectTestClasses'] = readFile(options.testCaseCode, root=options.codeRoot)
+    # moduleDict = loadModuleDict(moduleCodeDict)
+
+    moduleDict = {}
+    for cp in codePaths:
+        moduleName = re.match('.*?([^/]*)\.py', cp).group(1)
+        moduleDict[moduleName] = loadModuleFile(moduleName, os.path.join(options.codeRoot, cp))
+    moduleName = re.match('.*?([^/]*)\.py', options.testCaseCode).group(1)
+    moduleDict['projectTestClasses'] = loadModuleFile(moduleName, os.path.join(options.codeRoot, options.testCaseCode))
+
+
+    if options.runTest != None:
+        runTest(options.runTest, moduleDict, printTestCase=options.printTestCase, display=getDisplay(True, options))
+    else:
+        evaluate(options.generateSolutions, options.testRoot, moduleDict,
+            gsOutput=options.gsOutput,
+            edxOutput=options.edxOutput, muteOutput=options.muteOutput, printTestCase=options.printTestCase,
+            questionToGrade=options.gradeQuestion, display=getDisplay(options.gradeQuestion!=None, options))
diff --git a/tutorial/buyLotsOfFruit.py b/tutorial/buyLotsOfFruit.py
new file mode 100644
index 0000000000000000000000000000000000000000..997fc9f489395e54b44cf998526df6cf88bb3206
--- /dev/null
+++ b/tutorial/buyLotsOfFruit.py
@@ -0,0 +1,48 @@
+# buyLotsOfFruit.py
+# -----------------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+"""
+To run this script, type
+
+  python buyLotsOfFruit.py
+
+Once you have correctly implemented the buyLotsOfFruit function,
+the script should produce the output:
+
+Cost of [('apples', 2.0), ('pears', 3.0), ('limes', 4.0)] is 12.25
+"""
+from __future__ import print_function
+
+fruitPrices = {'apples': 2.00, 'oranges': 1.50, 'pears': 1.75,
+               'limes': 0.75, 'strawberries': 1.00}
+
+
+def buyLotsOfFruit(orderList):
+    """
+        orderList: List of (fruit, numPounds) tuples
+
+    Returns cost of order
+    """
+    totalCost = 0.0
+    for f, c in orderList:
+        totalCost += fruitPrices[f] * c
+    "*** YOUR CODE HERE ***"
+    return totalCost
+
+
+# Main Method
+if __name__ == '__main__':
+    "This code runs when you invoke the script from the command line"
+    orderList = [('apples', 2.0), ('pears', 3.0), ('limes', 4.0)]
+    print('Cost of', orderList, 'is', buyLotsOfFruit(orderList))
diff --git a/tutorial/fruit_project.py b/tutorial/fruit_project.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9ef46f82d75b893280ae2aa4153b2fee48d40ef
--- /dev/null
+++ b/tutorial/fruit_project.py
@@ -0,0 +1,69 @@
+from unitgrade.unitgrade import QuestionGroup, Report, QPrintItem, Hidden
+from tutorial import shop
+
+class AdditionQuestion(QuestionGroup):
+    title = "Addition Test"
+    class AdditionItem1(QPrintItem):
+        a, b = 2,2
+        def compute_answer_print(self):
+            from tutorial.addition import add
+            v = add(self.a, self.b)
+            print(add(self.a, self.b))
+
+    class AdditionItem2(AdditionItem1):
+        a,b = -4, 2
+
+    class AdditionItem3(AdditionItem1):
+        a,b = -10, 2
+
+class FruitListQuestion(QuestionGroup):
+    title = "buy lots of fruit.py"
+    class FruitListItem1(QPrintItem):
+        fruit_list = [ ('apples', 2.0), ('pears',3.0), ('limes',4.0) ]
+        def compute_answer_print(self):
+            from tutorial.buyLotsOfFruit import buyLotsOfFruit
+            print(buyLotsOfFruit(self.fruit_list))
+
+    class FruitListItem2(FruitListItem1):
+        fruit_list =  [('apples', 4.0), ('pears', 3.0), ('limes', 2.0)]
+
+    class FruitListItem3(FruitListItem1):
+        fruit_list = [('apples', 1.25), ('pears', 1.50), ('limes', 1.75)]
+
+
+class FruitShops(QuestionGroup):
+    title = "Find best shop for fruit shopping (shopSmart.py)"
+
+    class FruitShopItem1(QPrintItem):
+        shops = [shop.FruitShop('shop1',  {'apples': 2.0, 'oranges': 1.0}),
+                 shop.FruitShop('shop2', {'apples': 1.0, 'oranges': 5.0})]
+        order = [('apples', 1.0), ('oranges', 3.0)]
+
+        def compute_answer_print(self):
+            from tutorial.shopSmart import shopSmart
+            print(shopSmart(self.order, self.shops))
+
+    class FruitShopItem2(FruitShopItem1):
+        order = [('apples', 3.0)]
+
+    class FruitShopItem3(FruitShopItem1):
+        shops = [shop.FruitShop('shop1', {'apples': 2.0, 'oranges': 1.0}),
+                 shop.FruitShop('shop2', {'apples': 1.0, 'oranges': 5.0}),
+                 shop.FruitShop('shop3',  {'apples': 1.5, 'oranges': 2.0})]
+        order = [('apples',10.0), ('oranges',3.0)]
+
+
+class FruitReport(Report):
+    title = "Fruit and addition report"
+    questions = [(AdditionQuestion, 1), (FruitListQuestion, 3), (FruitShops, 2) ]
+    pack_imports = [] # Include this file in .token file
+
+if __name__ == "__main__":
+    # from unitgrade_private.hidden_create_files import setup_answers, setup_grade_file_report
+    from unitgrade.unitgrade_helpers import evaluate_report_student
+    from unitgrade_private.hidden_create_files import setup_grade_file_report
+    # setup_grade_file_report(FruitReport, minify=True, bzip=False, obfuscate=True)
+    # evaluate_report_student(Report2())
+    evaluate_report_student(FruitReport())
+
+
diff --git a/tutorial/fruit_project_grade.py b/tutorial/fruit_project_grade.py
new file mode 100644
index 0000000000000000000000000000000000000000..50c6a0bd6b6704ceb174631957f85564b860cfa3
--- /dev/null
+++ b/tutorial/fruit_project_grade.py
@@ -0,0 +1,403 @@
+'''WARNING: Modifying, decompiling or otherwise tampering with this script, it's data or the resulting .token file will be investigated as a cheating attempt.'''
+__version__="0.0.4"
+mkUjQcFvadVsEefODhlo=object
+mkUjQcFvadVsEefODhli=True
+mkUjQcFvadVsEefODhlR=print
+mkUjQcFvadVsEefODhlz=open
+mkUjQcFvadVsEefODhlG=None
+mkUjQcFvadVsEefODhlt=sum
+mkUjQcFvadVsEefODhlK=list
+mkUjQcFvadVsEefODhlq=super
+mkUjQcFvadVsEefODhlM=NotImplementedError
+mkUjQcFvadVsEefODhlu=False
+mkUjQcFvadVsEefODhlp=Exception
+mkUjQcFvadVsEefODhlJ=type
+mkUjQcFvadVsEefODhlN=classmethod
+mkUjQcFvadVsEefODhlw=getattr
+mkUjQcFvadVsEefODhlI=issubclass
+mkUjQcFvadVsEefODhlC=float
+mkUjQcFvadVsEefODhlW=int
+mkUjQcFvadVsEefODhlP=all
+mkUjQcFvadVsEefODhlS=str
+mkUjQcFvadVsEefODhHn=bool
+mkUjQcFvadVsEefODhHL=len
+mkUjQcFvadVsEefODhHb=enumerate
+mkUjQcFvadVsEefODhHl=eval
+mkUjQcFvadVsEefODhHg=globals
+import os
+mkUjQcFvadVsEefODhbq=os.getcwd
+mkUjQcFvadVsEefODhbK=os.mkdir
+mkUjQcFvadVsEefODhbt=os.path
+import compress_pickle
+mkUjQcFvadVsEefODhbu=compress_pickle.load
+mkUjQcFvadVsEefODhbM=compress_pickle.dump
+def mkUjQcFvadVsEefODhLu(mkUjQcFvadVsEefODhlo,file_name,verbose=mkUjQcFvadVsEefODhli):
+ dn=mkUjQcFvadVsEefODhbt.dirname(file_name)
+ if not mkUjQcFvadVsEefODhbt.exists(dn):
+  mkUjQcFvadVsEefODhbK(dn)
+ if verbose:mkUjQcFvadVsEefODhlR("Writing cache...",file_name)
+ with mkUjQcFvadVsEefODhlz(file_name,'wb',)as f:
+  mkUjQcFvadVsEefODhbM(mkUjQcFvadVsEefODhlo,f,compression="lzma")
+ if verbose:mkUjQcFvadVsEefODhlR("Done!")
+def mkUjQcFvadVsEefODhLp(file_name):
+ return mkUjQcFvadVsEefODhbt.exists(file_name)
+def mkUjQcFvadVsEefODhLJ(file_name):
+ if mkUjQcFvadVsEefODhbt.exists(file_name):
+  with mkUjQcFvadVsEefODhlz(file_name,'rb')as f:
+   return mkUjQcFvadVsEefODhbu(f,compression="lzma")
+ else:
+  return mkUjQcFvadVsEefODhlG
+"""
+git add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade
+"""
+import unittest
+mkUjQcFvadVsEefODhbp=unittest.TestCase
+import numpy as np
+mkUjQcFvadVsEefODhbW=np.mkUjQcFvadVsEefODhlt
+mkUjQcFvadVsEefODhbC=np.max
+mkUjQcFvadVsEefODhbI=np.asarray
+mkUjQcFvadVsEefODhbw=np.abs
+mkUjQcFvadVsEefODhbN=np.floor
+mkUjQcFvadVsEefODhbJ=np.round
+import os
+mkUjQcFvadVsEefODhbq=os.getcwd
+mkUjQcFvadVsEefODhbK=os.mkdir
+mkUjQcFvadVsEefODhbt=os.path
+from io import StringIO
+import sys
+mkUjQcFvadVsEefODhln=sys.sterr
+mkUjQcFvadVsEefODhbS=sys.stderr
+mkUjQcFvadVsEefODhbP=sys.stdout
+import collections
+mkUjQcFvadVsEefODhlL=collections.OrderedDict
+import inspect
+mkUjQcFvadVsEefODhlr=inspect.currentframe
+mkUjQcFvadVsEefODhlg=inspect.getouterframes
+mkUjQcFvadVsEefODhlH=inspect.getfile
+mkUjQcFvadVsEefODhlb=inspect.isclass
+mkUjQcFvadVsEefODhnb=lambda x:mkUjQcFvadVsEefODhbJ(x) 
+mkUjQcFvadVsEefODhnl=lambda x:mkUjQcFvadVsEefODhlt(x)
+mkUjQcFvadVsEefODhnH=lambda x:mkUjQcFvadVsEefODhbN(x)
+def mkUjQcFvadVsEefODhLN(C,base_dir):
+ mkUjQcFvadVsEefODhng=C.__class__.__name__
+ return base_dir,mkUjQcFvadVsEefODhng
+class mkUjQcFvadVsEefODhbT:
+ def mkUjQcFvadVsEefODhLw(mkUjQcFvadVsEefODhnr):
+  return mkUjQcFvadVsEefODhli
+class mkUjQcFvadVsEefODhbA(mkUjQcFvadVsEefODhlK):
+ def __enter__(mkUjQcFvadVsEefODhnr,capture_errors=mkUjQcFvadVsEefODhli):
+  mkUjQcFvadVsEefODhnr._stdout=mkUjQcFvadVsEefODhbP
+  mkUjQcFvadVsEefODhbP=mkUjQcFvadVsEefODhnr._stringio=mkUjQcFvadVsEefODhnB()
+  if capture_errors:
+   mkUjQcFvadVsEefODhnr._sterr=mkUjQcFvadVsEefODhbS
+   mkUjQcFvadVsEefODhln=mkUjQcFvadVsEefODhnB()
+  mkUjQcFvadVsEefODhnr.capture_errors=capture_errors
+  return mkUjQcFvadVsEefODhnr
+ def __exit__(mkUjQcFvadVsEefODhnr,*mkUjQcFvadVsEefODhLn):
+  mkUjQcFvadVsEefODhnr.extend(mkUjQcFvadVsEefODhnr._stringio.getvalue().splitlines())
+  del mkUjQcFvadVsEefODhnr._stringio 
+  mkUjQcFvadVsEefODhbP=mkUjQcFvadVsEefODhnr._stdout
+  if mkUjQcFvadVsEefODhnr.capture_errors:
+   mkUjQcFvadVsEefODhln=mkUjQcFvadVsEefODhnr._sterr
+class mkUjQcFvadVsEefODhbo(mkUjQcFvadVsEefODhbp):
+ mkUjQcFvadVsEefODhny=mkUjQcFvadVsEefODhlG
+ mkUjQcFvadVsEefODhnY=mkUjQcFvadVsEefODhbp.assertEqual
+ mkUjQcFvadVsEefODhnX=0
+ def __init__(mkUjQcFvadVsEefODhnr,working_directory=mkUjQcFvadVsEefODhlG,correct_answer_payload=mkUjQcFvadVsEefODhlG,*mkUjQcFvadVsEefODhLn,**kwargs):
+  mkUjQcFvadVsEefODhnr.name=mkUjQcFvadVsEefODhnr.__class__.__name__
+  mkUjQcFvadVsEefODhnr._correct_answer_payload=mkUjQcFvadVsEefODhnT
+  mkUjQcFvadVsEefODhlq().__init__(*mkUjQcFvadVsEefODhLn,**kwargs)
+  if mkUjQcFvadVsEefODhnr.title is mkUjQcFvadVsEefODhlG:
+   mkUjQcFvadVsEefODhnr.title=mkUjQcFvadVsEefODhnr.name
+ def mkUjQcFvadVsEefODhLI(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni,tol=mkUjQcFvadVsEefODhlG):
+  if tol==mkUjQcFvadVsEefODhlG:
+   tol=mkUjQcFvadVsEefODhnr.tol
+  mkUjQcFvadVsEefODhnA=mkUjQcFvadVsEefODhbw((mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhno)-mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhni)))
+  if mkUjQcFvadVsEefODhbC(mkUjQcFvadVsEefODhnA)>tol:
+   mkUjQcFvadVsEefODhlR("Not equal within tolerance {tol}")
+   mkUjQcFvadVsEefODhlR(f"Element-wise differences {diff.tolist()}")
+   mkUjQcFvadVsEefODhnr.assertEqual(mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni,msg=f"Not equal within tolerance {tol}")
+ def mkUjQcFvadVsEefODhLC(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni,tol=mkUjQcFvadVsEefODhlG):
+  if tol==mkUjQcFvadVsEefODhlG:
+   tol=mkUjQcFvadVsEefODhnr.tol
+  mkUjQcFvadVsEefODhnA=mkUjQcFvadVsEefODhbw((mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhno)-mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhni)))
+  mkUjQcFvadVsEefODhnA=mkUjQcFvadVsEefODhnA/(1e-8+mkUjQcFvadVsEefODhbw((mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhno)+mkUjQcFvadVsEefODhbI(mkUjQcFvadVsEefODhni))))
+  if mkUjQcFvadVsEefODhbW(mkUjQcFvadVsEefODhnA>tol)>0:
+   mkUjQcFvadVsEefODhlR(f"Not equal within tolerance {tol}")
+   mkUjQcFvadVsEefODhlR(f"Element-wise differences {diff.tolist()}")
+   mkUjQcFvadVsEefODhnr.assertEqual(mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni,msg=f"Not equal within tolerance {tol}")
+ def mkUjQcFvadVsEefODhLW(mkUjQcFvadVsEefODhnr):
+  raise mkUjQcFvadVsEefODhlM("test code here")
+ def mkUjQcFvadVsEefODhLP(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni):
+  mkUjQcFvadVsEefODhnr.testfun(mkUjQcFvadVsEefODhno,mkUjQcFvadVsEefODhni)
+ def mkUjQcFvadVsEefODhLS(mkUjQcFvadVsEefODhnr,verbose=mkUjQcFvadVsEefODhlu):
+  mkUjQcFvadVsEefODhnz=1
+  try:
+   mkUjQcFvadVsEefODhno=mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhLW()
+  except mkUjQcFvadVsEefODhlp as e:
+   mkUjQcFvadVsEefODhlR("\n=================================================================================")
+   mkUjQcFvadVsEefODhlR(f"When trying to run test class '{self.name}' your code threw an error:")
+   mkUjQcFvadVsEefODhlR(e)
+   mkUjQcFvadVsEefODhlR("=================================================================================")
+   import traceback
+   traceback.print_exc()
+   return(0,mkUjQcFvadVsEefODhnz)
+  mkUjQcFvadVsEefODhnG=mkUjQcFvadVsEefODhnr._correct_answer_payload
+  try:
+   mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhLP(computed=mkUjQcFvadVsEefODhno,expected=mkUjQcFvadVsEefODhnG)
+  except mkUjQcFvadVsEefODhlp as e:
+   mkUjQcFvadVsEefODhlR("=================================================================================")
+   mkUjQcFvadVsEefODhlR(f"Test output from test class '{self.name}' does not match expected result. Test error:")
+   mkUjQcFvadVsEefODhlR(e)
+   mkUjQcFvadVsEefODhlR("-------------------------Your output:--------------------------------------------")
+   mkUjQcFvadVsEefODhlR(mkUjQcFvadVsEefODhno)
+   mkUjQcFvadVsEefODhlR("-------------------------Expected output:----------------------------------------")
+   mkUjQcFvadVsEefODhlR(mkUjQcFvadVsEefODhnG)
+   mkUjQcFvadVsEefODhlR("=================================================================================")
+   return(0,mkUjQcFvadVsEefODhnz)
+  return(1,mkUjQcFvadVsEefODhnz)
+ def mkUjQcFvadVsEefODhbn(mkUjQcFvadVsEefODhnr):
+  try:
+   mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhLP()
+  except mkUjQcFvadVsEefODhlp as e:
+   return 0
+  return 1
+class mkUjQcFvadVsEefODhbi(mkUjQcFvadVsEefODhbo):
+ def mkUjQcFvadVsEefODhbL(mkUjQcFvadVsEefODhnr):
+  raise mkUjQcFvadVsEefODhlp("Generate output here")
+ def mkUjQcFvadVsEefODhbl(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhnt,txt,mkUjQcFvadVsEefODhnK):
+  return txt
+ def mkUjQcFvadVsEefODhLW(mkUjQcFvadVsEefODhnr):
+  with mkUjQcFvadVsEefODhbA()as output:
+   mkUjQcFvadVsEefODhnt=mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhbL()
+  s="\n".join(output)
+  mkUjQcFvadVsEefODhnK=mkUjQcFvadVsEefODhbg(s)
+  return mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhbl(mkUjQcFvadVsEefODhnt,s,mkUjQcFvadVsEefODhnK)
+class mkUjQcFvadVsEefODhbR(mkUjQcFvadVsEefODhlJ):
+ @mkUjQcFvadVsEefODhlN
+ def __prepare__(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhng,bases):
+  return mkUjQcFvadVsEefODhlL()
+ def __new__(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhng,bases,mkUjQcFvadVsEefODhnq):
+  mkUjQcFvadVsEefODhnq['__ordered__']=[key for key in mkUjQcFvadVsEefODhnq.keys()if key not in('__module__','__qualname__')]
+  return mkUjQcFvadVsEefODhlJ.__new__(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhng,bases,mkUjQcFvadVsEefODhnq)
+class mkUjQcFvadVsEefODhbz(metaclass=mkUjQcFvadVsEefODhbR):
+ mkUjQcFvadVsEefODhny="Graph search"
+ mkUjQcFvadVsEefODhnM=mkUjQcFvadVsEefODhlG
+ mkUjQcFvadVsEefODhnu=mkUjQcFvadVsEefODhlu
+ def __init__(mkUjQcFvadVsEefODhnr,*mkUjQcFvadVsEefODhLn,**kwargs):
+  mkUjQcFvadVsEefODhnr.name=mkUjQcFvadVsEefODhnr.__class__.__name__
+  if mkUjQcFvadVsEefODhnr.items is mkUjQcFvadVsEefODhlG:
+   mkUjQcFvadVsEefODhnr.items=[]
+   mkUjQcFvadVsEefODhnp=[gt for gt in[mkUjQcFvadVsEefODhlw(mkUjQcFvadVsEefODhnr,gt)for gt in mkUjQcFvadVsEefODhnr.__ordered__]if mkUjQcFvadVsEefODhlb(gt)and mkUjQcFvadVsEefODhlI(gt,mkUjQcFvadVsEefODhbo)]
+   for gt in mkUjQcFvadVsEefODhnp:
+    mkUjQcFvadVsEefODhnr.items.append((gt,1))
+  mkUjQcFvadVsEefODhnr.items=[(I(),w)for I,w in mkUjQcFvadVsEefODhnr.items]
+class mkUjQcFvadVsEefODhbG():
+ mkUjQcFvadVsEefODhny="report title"
+ mkUjQcFvadVsEefODhnJ=[]
+ mkUjQcFvadVsEefODhnN=[]
+ def __init__(mkUjQcFvadVsEefODhnr):
+  mkUjQcFvadVsEefODhnw=mkUjQcFvadVsEefODhbt.abspath(mkUjQcFvadVsEefODhbt.dirname(mkUjQcFvadVsEefODhlH(mkUjQcFvadVsEefODhlJ(mkUjQcFvadVsEefODhnr))))
+  mkUjQcFvadVsEefODhnr.wdir,mkUjQcFvadVsEefODhnr.name=mkUjQcFvadVsEefODhLN(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhnw)
+  mkUjQcFvadVsEefODhnr.computed_answers_file=mkUjQcFvadVsEefODhbt.join(mkUjQcFvadVsEefODhnr.wdir,mkUjQcFvadVsEefODhnr.name+"_resources_do_not_hand_in.dat")
+  mkUjQcFvadVsEefODhnr.questions=[(Q(working_directory=mkUjQcFvadVsEefODhnr.wdir),w)for Q,w in mkUjQcFvadVsEefODhnr.questions]
+  if mkUjQcFvadVsEefODhbt.isfile(mkUjQcFvadVsEefODhnr.computed_answers_file):
+   mkUjQcFvadVsEefODhnr.mkUjQcFvadVsEefODhbH(mkUjQcFvadVsEefODhLJ(mkUjQcFvadVsEefODhnr.computed_answers_file))
+ def mkUjQcFvadVsEefODhbH(mkUjQcFvadVsEefODhnr,mkUjQcFvadVsEefODhnC):
+  for q,_ in mkUjQcFvadVsEefODhnr.questions:
+   for mkUjQcFvadVsEefODhnI,_ in q.items:
+    if q.name not in mkUjQcFvadVsEefODhnC or mkUjQcFvadVsEefODhnI.name not in mkUjQcFvadVsEefODhnC[q.name]:
+     continue
+    mkUjQcFvadVsEefODhnI._correct_answer_payload=mkUjQcFvadVsEefODhnC[q.name][mkUjQcFvadVsEefODhnI.name]['payload']
+def mkUjQcFvadVsEefODhbg(txt):
+ import re
+ mkUjQcFvadVsEefODhlx=re.VERBOSE
+ mkUjQcFvadVsEefODhlB=re.compile
+ mkUjQcFvadVsEefODhnW='[-+]? (?: (?: \d* \. \d+ ) | (?: \d+ \.? ) )(?: [Ee] [+-]? \d+ ) ?'
+ rx=mkUjQcFvadVsEefODhlB(mkUjQcFvadVsEefODhnW,mkUjQcFvadVsEefODhlx)
+ mkUjQcFvadVsEefODhlP=rx.findall(txt)
+ mkUjQcFvadVsEefODhlP=[mkUjQcFvadVsEefODhlC(a)if '.' in a else mkUjQcFvadVsEefODhlW(a)for a in mkUjQcFvadVsEefODhlP]
+ return mkUjQcFvadVsEefODhlP
+import numpy as np
+mkUjQcFvadVsEefODhbW=np.mkUjQcFvadVsEefODhlt
+mkUjQcFvadVsEefODhbC=np.max
+mkUjQcFvadVsEefODhbI=np.asarray
+mkUjQcFvadVsEefODhbw=np.abs
+mkUjQcFvadVsEefODhbN=np.floor
+mkUjQcFvadVsEefODhbJ=np.round
+from tabulate import tabulate
+from datetime import datetime
+mkUjQcFvadVsEefODhly=datetime.now
+import pickle
+mkUjQcFvadVsEefODhlY=pickle.loads
+import pyfiglet
+mkUjQcFvadVsEefODhlX=pyfiglet.figlet_format
+import inspect
+mkUjQcFvadVsEefODhlr=inspect.currentframe
+mkUjQcFvadVsEefODhlg=inspect.getouterframes
+mkUjQcFvadVsEefODhlH=inspect.getfile
+mkUjQcFvadVsEefODhlb=inspect.isclass
+mkUjQcFvadVsEefODhbt=os.path
+import os
+mkUjQcFvadVsEefODhbq=os.getcwd
+mkUjQcFvadVsEefODhbK=os.mkdir
+import argparse
+mkUjQcFvadVsEefODhlT=argparse.ArgumentParser
+mkUjQcFvadVsEefODhnP=mkUjQcFvadVsEefODhlT(description='Evaluate your report.',epilog="""Example: 
+To run all tests in a report: 
+> python report1.py
+To run only question 2 or question 2.1
+> python report1.py -q 2
+> python report1.py -q 2.1
+Note this scripts does not grade your report. To grade your report, use:
+> python report1_grade.py
+Finally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.
+As an example, suppose the report file is Documents/course_package/report1.py, and `course_package` is a python package. Change directory to 'Documents/` and execute:
+> python -m course_package.report1
+see https://docs.python.org/3.9/using/cmdline.html
+""")
+mkUjQcFvadVsEefODhnP.add_argument('-q',nargs='?',mkUjQcFvadVsEefODhlJ=mkUjQcFvadVsEefODhlS,default=mkUjQcFvadVsEefODhlG,help='Only evaluate this question (example: -q 2)')
+mkUjQcFvadVsEefODhnP.add_argument('--verbose',nargs='?',mkUjQcFvadVsEefODhlJ=mkUjQcFvadVsEefODhHn,default=mkUjQcFvadVsEefODhlu,help='Show expected output')
+def mkUjQcFvadVsEefODhbr(mkUjQcFvadVsEefODhLi,question=mkUjQcFvadVsEefODhlG,qitem=mkUjQcFvadVsEefODhlG):
+ mkUjQcFvadVsEefODhLn=mkUjQcFvadVsEefODhnP.parse_args()
+ if question is mkUjQcFvadVsEefODhlG and mkUjQcFvadVsEefODhLn.q is not mkUjQcFvadVsEefODhlG:
+  question=mkUjQcFvadVsEefODhLn.q
+  if "." in question:
+   question,qitem=[mkUjQcFvadVsEefODhlW(v)for v in question.split(".")]
+  else:
+   question=mkUjQcFvadVsEefODhlW(question)
+ mkUjQcFvadVsEefODhLH,mkUjQcFvadVsEefODhLg=mkUjQcFvadVsEefODhbx(mkUjQcFvadVsEefODhLi,question=question,qitem=qitem,verbose=mkUjQcFvadVsEefODhLn.verbose)
+ if question is mkUjQcFvadVsEefODhlG:
+  mkUjQcFvadVsEefODhlR("Provisional evaluation")
+  tabulate(mkUjQcFvadVsEefODhLg)
+  mkUjQcFvadVsEefODhLr=mkUjQcFvadVsEefODhLg
+  mkUjQcFvadVsEefODhlR(tabulate(mkUjQcFvadVsEefODhLr))
+  mkUjQcFvadVsEefODhlR(" ")
+ mkUjQcFvadVsEefODhlR("Note your results have not yet been registered. \nTo register your results, please run the file:")
+ fr=mkUjQcFvadVsEefODhlg(mkUjQcFvadVsEefODhlr())[1].filename
+ mkUjQcFvadVsEefODhlR(">>>",mkUjQcFvadVsEefODhbt.basename(fr)[:-3]+"_grade.py")
+ mkUjQcFvadVsEefODhlR("In the same manner as you ran this file.")
+ return mkUjQcFvadVsEefODhLH
+def mkUjQcFvadVsEefODhbB(q):
+ h=[(i['w'],i['possible'],i['obtained'])for i in q.values()]
+ h=mkUjQcFvadVsEefODhbI(h)
+ return h[:,0],h[:,1],h[:,2],
+def mkUjQcFvadVsEefODhbx(mkUjQcFvadVsEefODhLi,question=mkUjQcFvadVsEefODhlG,qitem=mkUjQcFvadVsEefODhlG,verbose=mkUjQcFvadVsEefODhlu):
+ mkUjQcFvadVsEefODhLB=mkUjQcFvadVsEefODhly()
+ mkUjQcFvadVsEefODhLx=mkUjQcFvadVsEefODhlX("UnitGrade",font="doom")
+ b="\n".join([l for l in mkUjQcFvadVsEefODhLx.splitlines()if mkUjQcFvadVsEefODhHL(l.strip())>0])
+ mkUjQcFvadVsEefODhlR(b+" v"+__version__)
+ mkUjQcFvadVsEefODhLy=mkUjQcFvadVsEefODhLB.strftime("%d/%m/%Y %H:%M:%S")
+ mkUjQcFvadVsEefODhlR("Started: "+mkUjQcFvadVsEefODhLy)
+ mkUjQcFvadVsEefODhlR("Evaluating "+mkUjQcFvadVsEefODhLi.title,"(use --help for options)")
+ mkUjQcFvadVsEefODhlR("")
+ mkUjQcFvadVsEefODhLg=[]
+ nL=80
+ mkUjQcFvadVsEefODhbn={}
+ for n,(q,w)in mkUjQcFvadVsEefODhHb(mkUjQcFvadVsEefODhLi.questions):
+  mkUjQcFvadVsEefODhLY=mkUjQcFvadVsEefODhlI(q.__class__,mkUjQcFvadVsEefODhbT)
+  if question is not mkUjQcFvadVsEefODhlG and n+1!=question:
+   continue
+  mkUjQcFvadVsEefODhlR(f"Question {n+1}: {q.title}")
+  mkUjQcFvadVsEefODhlR("="*nL)
+  q.possible=0
+  q.obtained=0
+  q_={}
+  for j,(mkUjQcFvadVsEefODhnI,iw)in mkUjQcFvadVsEefODhHb(q.items):
+   if qitem is not mkUjQcFvadVsEefODhlG and question is not mkUjQcFvadVsEefODhlG and mkUjQcFvadVsEefODhnI is not mkUjQcFvadVsEefODhlG and j+1!=qitem:
+    continue
+   ss=f"*** q{n+1}.{j+1}) {item.title}"
+   el=nL-4
+   if mkUjQcFvadVsEefODhHL(ss)<el:
+    ss+='.'*(el-mkUjQcFvadVsEefODhHL(ss))
+   mkUjQcFvadVsEefODhLX=mkUjQcFvadVsEefODhlI(mkUjQcFvadVsEefODhnI.__class__,mkUjQcFvadVsEefODhbT)
+   if not mkUjQcFvadVsEefODhLX:
+    mkUjQcFvadVsEefODhlR(ss,end="")
+   (mkUjQcFvadVsEefODhLA,mkUjQcFvadVsEefODhnz)=mkUjQcFvadVsEefODhnI.mkUjQcFvadVsEefODhLS()
+   q_[j]={'w':iw,'possible':mkUjQcFvadVsEefODhnz,'obtained':mkUjQcFvadVsEefODhLA,'hidden':mkUjQcFvadVsEefODhLX}
+   if not mkUjQcFvadVsEefODhLX:
+    if mkUjQcFvadVsEefODhLA==mkUjQcFvadVsEefODhnz:
+     mkUjQcFvadVsEefODhlR(f"PASS")
+    else:
+     mkUjQcFvadVsEefODhlR(f"*** FAILED")
+  ws,mkUjQcFvadVsEefODhnz,mkUjQcFvadVsEefODhLo=mkUjQcFvadVsEefODhbB(q_)
+  mkUjQcFvadVsEefODhnz=mkUjQcFvadVsEefODhlW(ws@mkUjQcFvadVsEefODhnz)
+  mkUjQcFvadVsEefODhLo=mkUjQcFvadVsEefODhlW(ws@mkUjQcFvadVsEefODhLo)
+  mkUjQcFvadVsEefODhLo=mkUjQcFvadVsEefODhnb(mkUjQcFvadVsEefODhlW((w*mkUjQcFvadVsEefODhLo)/mkUjQcFvadVsEefODhnz))if mkUjQcFvadVsEefODhnz>0 else 0
+  mkUjQcFvadVsEefODhbn[n]={'w':w,'possible':w,'obtained':mkUjQcFvadVsEefODhLo,'ítems':q_,'hidden':mkUjQcFvadVsEefODhLY}
+  q.obtained=mkUjQcFvadVsEefODhLo
+  q.possible=mkUjQcFvadVsEefODhnz
+  s1=f"*** Question q{n+1}"
+  s2=f" {q.obtained}/{w}"
+  mkUjQcFvadVsEefODhlR(s1+("."*(nL-mkUjQcFvadVsEefODhHL(s1)-mkUjQcFvadVsEefODhHL(s2)))+s2)
+  mkUjQcFvadVsEefODhlR(" ")
+  mkUjQcFvadVsEefODhLg.append([f"Question q{n+1}",f"{q.obtained}/{w}"])
+ ws,mkUjQcFvadVsEefODhnz,mkUjQcFvadVsEefODhLo=mkUjQcFvadVsEefODhbB(mkUjQcFvadVsEefODhbn)
+ mkUjQcFvadVsEefODhnz=mkUjQcFvadVsEefODhlW(mkUjQcFvadVsEefODhnl(mkUjQcFvadVsEefODhnz))
+ mkUjQcFvadVsEefODhLo=mkUjQcFvadVsEefODhlW(mkUjQcFvadVsEefODhnl(mkUjQcFvadVsEefODhLo))
+ mkUjQcFvadVsEefODhLi.possible=mkUjQcFvadVsEefODhnz
+ mkUjQcFvadVsEefODhLi.obtained=mkUjQcFvadVsEefODhLo
+ mkUjQcFvadVsEefODhLB=mkUjQcFvadVsEefODhly()
+ mkUjQcFvadVsEefODhLy=mkUjQcFvadVsEefODhLB.strftime("%H:%M:%S")
+ mkUjQcFvadVsEefODhlR(f"Completed: "+mkUjQcFvadVsEefODhLy)
+ mkUjQcFvadVsEefODhLg.append(["Total",""+mkUjQcFvadVsEefODhlS(mkUjQcFvadVsEefODhLi.obtained)+"/"+mkUjQcFvadVsEefODhlS(mkUjQcFvadVsEefODhLi.possible)])
+ mkUjQcFvadVsEefODhLH={'total':(mkUjQcFvadVsEefODhLo,mkUjQcFvadVsEefODhnz),'details':mkUjQcFvadVsEefODhbn}
+ return mkUjQcFvadVsEefODhLH,mkUjQcFvadVsEefODhLg
+from tabulate import tabulate
+from datetime import datetime
+mkUjQcFvadVsEefODhly=datetime.now
+mkUjQcFvadVsEefODhlb=inspect.isclass
+import inspect
+mkUjQcFvadVsEefODhlr=inspect.currentframe
+mkUjQcFvadVsEefODhlg=inspect.getouterframes
+mkUjQcFvadVsEefODhlH=inspect.getfile
+import json
+mkUjQcFvadVsEefODhlA=json.dumps
+mkUjQcFvadVsEefODhbt=os.path
+mkUjQcFvadVsEefODhbK=os.mkdir
+import os
+mkUjQcFvadVsEefODhbq=os.getcwd
+import bz2
+import pickle
+mkUjQcFvadVsEefODhlY=pickle.loads
+def mkUjQcFvadVsEefODhby(mkUjQcFvadVsEefODhLR,mkUjQcFvadVsEefODhLK):
+ with mkUjQcFvadVsEefODhlw(bz2,'open')(mkUjQcFvadVsEefODhLK,"wt")as f:
+  f.write(mkUjQcFvadVsEefODhLR)
+def mkUjQcFvadVsEefODhbY(mkUjQcFvadVsEefODhLi,output_dir=mkUjQcFvadVsEefODhlG):
+ n=80
+ mkUjQcFvadVsEefODhLH,mkUjQcFvadVsEefODhLg=mkUjQcFvadVsEefODhbx(mkUjQcFvadVsEefODhLi)
+ mkUjQcFvadVsEefODhlR(" ")
+ mkUjQcFvadVsEefODhlR("="*n)
+ mkUjQcFvadVsEefODhlR("Final evaluation")
+ mkUjQcFvadVsEefODhlR(tabulate(mkUjQcFvadVsEefODhLg))
+ mkUjQcFvadVsEefODhLH['sources']={}
+ mkUjQcFvadVsEefODhlR("Gathering files...")
+ for m in mkUjQcFvadVsEefODhLi.pack_imports:
+  with mkUjQcFvadVsEefODhlz(m.__file__,'r')as f:
+   mkUjQcFvadVsEefODhLH['sources'][m.__name__]=f.read()
+  mkUjQcFvadVsEefODhlR(f"*** {m.__name__}")
+ mkUjQcFvadVsEefODhLH['sources']={}
+ mkUjQcFvadVsEefODhLR=mkUjQcFvadVsEefODhlA(mkUjQcFvadVsEefODhLH,indent=4)
+ if output_dir is mkUjQcFvadVsEefODhlG:
+  output_dir=mkUjQcFvadVsEefODhbq()
+ mkUjQcFvadVsEefODhLG=mkUjQcFvadVsEefODhLi.__class__.__name__+"_handin"
+ mkUjQcFvadVsEefODhLt,mkUjQcFvadVsEefODhnz=mkUjQcFvadVsEefODhLH['total']
+ mkUjQcFvadVsEefODhLK="%s_%i_of_%i.token"%(mkUjQcFvadVsEefODhLG,mkUjQcFvadVsEefODhLt,mkUjQcFvadVsEefODhnz)
+ mkUjQcFvadVsEefODhLK=mkUjQcFvadVsEefODhbt.join(output_dir,mkUjQcFvadVsEefODhLK)
+ mkUjQcFvadVsEefODhby(mkUjQcFvadVsEefODhLR,mkUjQcFvadVsEefODhLK)
+ mkUjQcFvadVsEefODhlR(" ")
+ mkUjQcFvadVsEefODhlR("To get credit for your results, please upload the single file: ")
+ mkUjQcFvadVsEefODhlR(">",mkUjQcFvadVsEefODhLK)
+ mkUjQcFvadVsEefODhlR("To campusnet without any modifications.")
+def mkUjQcFvadVsEefODhbX(mkUjQcFvadVsEefODhng,mkUjQcFvadVsEefODhLq,payload):
+ mkUjQcFvadVsEefODhHl("exec")(mkUjQcFvadVsEefODhLq,mkUjQcFvadVsEefODhHg())
+ mkUjQcFvadVsEefODhLi=mkUjQcFvadVsEefODhHl(mkUjQcFvadVsEefODhng)()
+ pl=mkUjQcFvadVsEefODhlY(payload)
+ mkUjQcFvadVsEefODhLi.mkUjQcFvadVsEefODhbH(pl)
+ return mkUjQcFvadVsEefODhLi
+mkUjQcFvadVsEefODhLq='__version__ = "0.0.4"\nimport os\nimport compress_pickle\n\ndef cache_write(object, file_name, verbose=True):\n    dn = os.path.dirname(file_name)\n    if not os.path.exists(dn):\n        os.mkdir(dn)\n    if verbose: print("Writing cache...", file_name)\n    with open(file_name, \'wb\', ) as f:\n        compress_pickle.dump(object, f, compression="lzma")\n    if verbose: print("Done!")\n\n\ndef cache_exists(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    return os.path.exists(file_name)\n\n\ndef cache_read(file_name):\n    # file_name = cn_(file_name) if cache_prefix else file_name\n    if os.path.exists(file_name):\n        with open(file_name, \'rb\') as f:\n            return compress_pickle.load(f, compression="lzma")\n            # return pickle.load(f)\n    else:\n        return None\n\n\n\n"""\ngit add . && git commit -m "Options" && git push &&  pip install git+ssh://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git --upgrade\n\n"""\nimport unittest\nimport numpy as np\nimport os\nfrom io import StringIO\nimport sys\nimport collections\nimport inspect\n\nmyround = lambda x: np.round(x)  # required.\nmsum = lambda x: sum(x)\nmfloor = lambda x: np.floor(x)\n\ndef setup_dir_by_class(C,base_dir):\n    name = C.__class__.__name__\n    # base_dir = os.path.join(base_dir, name)\n    # if not os.path.isdir(base_dir):\n    #     os.makedirs(base_dir)\n    return base_dir, name\n\nclass Hidden:\n    def hide(self):\n        return True\n\nclass Capturing(list):\n    def __enter__(self, capture_errors=True):\n        self._stdout = sys.stdout\n        sys.stdout = self._stringio = StringIO()\n        if capture_errors:\n            self._sterr = sys.stderr\n            sys.sterr = StringIO() # memory hole it\n        self.capture_errors = capture_errors\n        return self\n\n    def __exit__(self, *args):\n        self.extend(self._stringio.getvalue().splitlines())\n        del self._stringio    # free up some memory\n        sys.stdout = self._stdout\n        if self.capture_errors:\n            sys.sterr = self._sterr\n\n\nclass QItem(unittest.TestCase):\n    title = None\n    testfun = unittest.TestCase.assertEqual\n    tol = 0\n\n    def __init__(self, working_directory=None, correct_answer_payload=None, *args, **kwargs):\n        self.name = self.__class__.__name__\n        self._correct_answer_payload = correct_answer_payload\n        super().__init__(*args, **kwargs)\n        if self.title is None:\n            self.title = self.name\n\n    def assertL2(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        if np.max(diff) > tol:\n            print("Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def assertL2Relative(self, computed, expected, tol=None):\n        if tol == None:\n            tol = self.tol\n        diff = np.abs( (np.asarray(computed) - np.asarray(expected)) )\n        diff = diff / (1e-8 + np.abs( (np.asarray(computed) + np.asarray(expected)) ) )\n        if np.sum(diff > tol) > 0:\n            print(f"Not equal within tolerance {tol}")\n            print(f"Element-wise differences {diff.tolist()}")\n            self.assertEqual(computed, expected, msg=f"Not equal within tolerance {tol}")\n\n    def compute_answer(self):\n        raise NotImplementedError("test code here")\n\n    def test(self, computed, expected):\n        self.testfun(computed, expected)\n\n    def get_points(self, verbose=False):\n        possible = 1\n        try:\n            computed = self.compute_answer()\n        except Exception as e:\n            print("\\n=================================================================================")\n            print(f"When trying to run test class \'{self.name}\' your code threw an error:")\n            print(e)\n            print("=================================================================================")\n            import traceback\n            traceback.print_exc()\n            return (0, possible)\n        correct = self._correct_answer_payload\n        try:\n            # print(computed, correct)\n            self.test(computed=computed, expected=correct)\n        except Exception as e:\n            print("=================================================================================")\n            print(f"Test output from test class \'{self.name}\' does not match expected result. Test error:")\n            print(e)\n            print("-------------------------Your output:--------------------------------------------")\n            print(computed)\n            print("-------------------------Expected output:----------------------------------------")\n            print(correct)\n            print("=================================================================================")\n            return (0, possible)\n        return (1, possible)\n\n    def score(self):\n        try:\n            self.test()\n        except Exception as e:\n            return 0\n        return 1\n\nclass QPrintItem(QItem):\n    def compute_answer_print(self):\n        raise Exception("Generate output here")\n\n    def process_output(self, res, txt, numbers):\n        return txt\n\n    def compute_answer(self):\n        with Capturing() as output:\n            res = self.compute_answer_print()\n        s = "\\n".join(output)\n        numbers = extract_numbers(s)\n        return self.process_output(res, s, numbers)\n\nclass OrderedClassMembers(type):\n    @classmethod\n    def __prepare__(self, name, bases):\n        return collections.OrderedDict()\n    def __new__(self, name, bases, classdict):\n        classdict[\'__ordered__\'] = [key for key in classdict.keys() if key not in (\'__module__\', \'__qualname__\')]\n        return type.__new__(self, name, bases, classdict)\n\nclass QuestionGroup(metaclass=OrderedClassMembers):\n    title = "Graph search"\n    items = None\n    partially_scored = False\n\n    def __init__(self, *args, **kwargs):\n        self.name = self.__class__.__name__\n        if self.items is None:\n            self.items = []\n            members = [gt for gt in [getattr(self, gt) for gt in self.__ordered__] if inspect.isclass(gt) and issubclass(gt, QItem)]\n            for gt in members:\n                self.items.append( (gt, 1) )\n        self.items = [(I(), w) for I, w in self.items]\n\nclass Report():\n    title = "report title"\n    questions = []\n    pack_imports = []\n\n    def __init__(self):\n        working_directory = os.path.abspath(os.path.dirname(inspect.getfile(type(self))))\n        # working_directory = os.path.join(pathlib.Path(__file__).parent.absolute(), "payloads/")\n        self.wdir, self.name = setup_dir_by_class(self, working_directory)\n        self.computed_answers_file = os.path.join(self.wdir, self.name + "_resources_do_not_hand_in.dat")\n        # print(self.computed_answers_file)\n        self.questions = [(Q(working_directory=self.wdir),w) for Q,w in self.questions]\n        if os.path.isfile(self.computed_answers_file):\n            self.set_payload(cache_read(self.computed_answers_file))\n\n    def set_payload(self, payloads):\n        for q, _ in self.questions:\n            for item, _ in q.items:\n                if q.name not in payloads or item.name not in payloads[q.name]:\n                    continue\n                item._correct_answer_payload = payloads[q.name][item.name][\'payload\']\n\ndef extract_numbers(txt):\n    import re\n    numeric_const_pattern = \'[-+]? (?: (?: \\d* \\. \\d+ ) | (?: \\d+ \\.? ) )(?: [Ee] [+-]? \\d+ ) ?\'\n    rx = re.compile(numeric_const_pattern, re.VERBOSE)\n    all = rx.findall(txt)\n    all = [float(a) if \'.\' in a else int(a) for a in all]\n    return all\n\n\nimport numpy as np\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport pickle\nimport pyfiglet\n\n# from unitgrade.unitgrade import Hidden\n# import unitgrade as ug\n# import unitgrade.unitgrade as ug\nimport inspect\nimport os\nimport argparse\n\n\nparser = argparse.ArgumentParser(description=\'Evaluate your report.\', epilog="""Example: \nTo run all tests in a report: \n\n> python report1.py\n\nTo run only question 2 or question 2.1\n\n> python report1.py -q 2\n> python report1.py -q 2.1\n\nNote this scripts does not grade your report. To grade your report, use:\n\n> python report1_grade.py\n\nFinally, note that if your report is part of a module (package), and the report script requires part of that package, the -m option for python may be useful.\nAs an example, suppose the report file is Documents/course_package/report1.py, and `course_package` is a python package. Change directory to \'Documents/` and execute:\n\n> python -m course_package.report1\n\nsee https://docs.python.org/3.9/using/cmdline.html\n""")\nparser.add_argument(\'-q\', nargs=\'?\', type=str, default=None, help=\'Only evaluate this question (example: -q 2)\')\nparser.add_argument(\'--verbose\', nargs=\'?\', type=bool, default=False, help=\'Show expected output\')\n# parser.add_argument(\'integers\', metavar=\'N\', type=int, nargs=\'+\',\n#                     help=\'an integer for the accumulator\')\n# parser.add_argument(\'--sum\', dest=\'accumulate\', action=\'store_const\',\n#                     const=sum, default=max,\n#                     help=\'sum the integers (default: find the max)\')\n\n\n\ndef evaluate_report_student(report, question=None, qitem=None):\n    args = parser.parse_args()\n    if question is None and args.q is not None:\n        question = args.q\n        if "." in question:\n            question, qitem = [int(v) for v in question.split(".")]\n        else:\n            question = int(question)\n    results, table_data = evaluate_report(report, question=question, qitem=qitem, verbose=args.verbose)\n    if question is None:\n        print("Provisional evaluation")\n        tabulate(table_data)\n        table = table_data\n        print(tabulate(table))\n        print(" ")\n    print("Note your results have not yet been registered. \\nTo register your results, please run the file:")\n\n    fr = inspect.getouterframes(inspect.currentframe())[1].filename\n    print(">>>", os.path.basename(fr)[:-3] + "_grade.py")\n    print("In the same manner as you ran this file.")\n    return results\n\n\ndef upack(q):\n    # h = zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()])\n    h =[(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]\n    h = np.asarray(h)\n    return h[:,0], h[:,1], h[:,2],\n    #\n    # ws, possible, obtained = (np.asarray(x).squeeze() for x in zip([(i[\'w\'], i[\'possible\'], i[\'obtained\']) for i in q.values()]))\n    # return ws, possible, obtained\n\ndef evaluate_report(report, question=None, qitem=None, verbose=False):\n    now = datetime.now()\n    ascii_banner = pyfiglet.figlet_format("UnitGrade", font="doom")\n    b = "\\n".join( [l for l in ascii_banner.splitlines() if len(l.strip()) > 0] )\n    print(b + " v" + __version__)\n    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")\n    print("Started: " + dt_string)\n    print("Evaluating " + report.title, "(use --help for options)")\n    print("")\n    table_data = []\n    nL = 80\n\n    score = {}\n    for n, (q, w) in enumerate(report.questions):\n        q_hidden = issubclass(q.__class__, Hidden)\n        if question is not None and n+1 != question:\n            continue\n\n        print(f"Question {n+1}: {q.title}")\n        print("="*nL)\n        q.possible = 0\n        q.obtained = 0\n\n        q_ = {} # Gather score in this class.\n        for j, (item, iw) in enumerate(q.items):\n            if qitem is not None and question is not None and item is not None and j+1 != qitem:\n                continue\n\n            ss = f"*** q{n+1}.{j+1}) {item.title}"\n            el = nL-4\n            if len(ss) < el:\n                ss += \'.\'*(el-len(ss))\n            hidden = issubclass(item.__class__, Hidden)\n            if not hidden:\n                print(ss, end="")\n            (current, possible) = item.get_points()\n            q_[j] = {\'w\': iw, \'possible\': possible, \'obtained\': current, \'hidden\': hidden}\n            # q.possible += possible * iw\n            # q.obtained += current * iw\n            if not hidden:\n                if current == possible:\n                    print(f"PASS")\n                else:\n                    print(f"*** FAILED")\n\n        ws, possible, obtained = upack(q_)\n        possible = int(ws @ possible)\n        obtained = int(ws @ obtained)\n        obtained = myround(int((w * obtained) / possible )) if possible > 0 else 0\n        score[n] = {\'w\': w, \'possible\': w, \'obtained\': obtained, \'ítems\': q_, \'hidden\': q_hidden}\n\n        q.obtained = obtained\n        q.possible = possible\n\n        s1 = f"*** Question q{n+1}"\n        s2 = f" {q.obtained}/{w}"\n        print(s1 + ("."* (nL-len(s1)-len(s2) )) + s2 )\n        print(" ")\n        table_data.append([f"Question q{n+1}", f"{q.obtained}/{w}"])\n\n    ws, possible, obtained = upack(score)\n    possible = int( msum(possible) )\n    obtained = int( msum(obtained) ) # Cast to python int\n    report.possible = possible\n    report.obtained = obtained\n    now = datetime.now()\n    dt_string = now.strftime("%H:%M:%S")\n    print(f"Completed: "+ dt_string)\n    table_data.append(["Total", ""+str(report.obtained)+"/"+str(report.possible) ])\n    results = {\'total\': (obtained, possible), \'details\': score}\n    return results, table_data\n\n\nfrom tabulate import tabulate\nfrom datetime import datetime\nimport inspect\nimport json\nimport os\nimport bz2\nimport pickle\n\ndef bzwrite(json_str, token): # to get around obfuscation issues\n    with getattr(bz2, \'open\')(token, "wt") as f:\n        f.write(json_str)\n\ndef gather_upload_to_campusnet(report, output_dir=None):\n    n = 80\n    results, table_data = evaluate_report(report)\n    print(" ")\n    print("="*n)\n    print("Final evaluation")\n    print(tabulate(table_data))\n    # also load the source code of missing files...\n    results[\'sources\'] = {}\n    print("Gathering files...")\n    for m in report.pack_imports:\n        with open(m.__file__, \'r\') as f:\n            results[\'sources\'][m.__name__] = f.read()\n        print(f"*** {m.__name__}")\n\n    results[\'sources\'] = {}\n    json_str = json.dumps(results, indent=4)\n    # now = datetime.now()\n    # dname = os.path.dirname(inspect.getfile(report.__class__))\n    # dname = os.getcwd()\n    if output_dir is None:\n        output_dir = os.getcwd()\n\n    payload_out_base = report.__class__.__name__ + "_handin"\n\n    obtain, possible = results[\'total\']\n    token = "%s_%i_of_%i.token"%(payload_out_base, obtain, possible) # + str(obtained) +"_" +  ".token"\n    token = os.path.join(output_dir, token)\n    bzwrite(json_str, token)\n\n    print(" ")\n    print("To get credit for your results, please upload the single file: ")\n    print(">", token)\n    print("To campusnet without any modifications.")\n\ndef source_instantiate(name, report1_source, payload):\n    eval("exec")(report1_source, globals())\n    report = eval(name)()\n    pl = pickle.loads(payload)\n    report.set_payload(pl)\n    return report\n\n\n\nclass AdditionQuestion(QuestionGroup):\n    title = "Addition Test"\n    class AdditionItem1(QPrintItem):\n        a, b = 2,2\n        def compute_answer_print(self):\n            from tutorial.addition import add\n            v = add(self.a, self.b)\n            print(add(self.a, self.b))\n\n    class AdditionItem2(AdditionItem1):\n        a,b = -4, 2\n\n    class AdditionItem3(AdditionItem1):\n        a,b = -10, 2\n\nclass FruitListQuestion(QuestionGroup):\n    title = "buy lots of fruit.py"\n    class FruitListItem1(QPrintItem):\n        fruit_list = [ (\'apples\', 2.0), (\'pears\',3.0), (\'limes\',4.0) ]\n        def compute_answer_print(self):\n            from tutorial.buyLotsOfFruit import buyLotsOfFruit\n            print(buyLotsOfFruit(self.fruit_list))\n\n    class FruitListItem2(FruitListItem1):\n        fruit_list =  [(\'apples\', 4.0), (\'pears\', 3.0), (\'limes\', 2.0)]\n\n    class FruitListItem3(FruitListItem1):\n        fruit_list = [(\'apples\', 1.25), (\'pears\', 1.50), (\'limes\', 1.75)]\n\nclass FruitReport(Report):\n    title = "Fruit and addition report"\n    questions = [(AdditionQuestion, 1), (AdditionQuestion, 3) ]\n    pack_imports = [] # Include this file in .token file'
+mkUjQcFvadVsEefODhLM=b'\x80\x03}q\x00X\x10\x00\x00\x00AdditionQuestionq\x01}q\x02(X\r\x00\x00\x00AdditionItem1q\x03}q\x04X\x07\x00\x00\x00payloadq\x05X\x01\x00\x00\x004q\x06sX\r\x00\x00\x00AdditionItem2q\x07}q\x08h\x05X\x02\x00\x00\x00-2q\tsX\r\x00\x00\x00AdditionItem3q\n}q\x0bh\x05X\x02\x00\x00\x00-8q\x0csus.'
+mkUjQcFvadVsEefODhng="FruitReport"
+mkUjQcFvadVsEefODhLi=mkUjQcFvadVsEefODhbX(mkUjQcFvadVsEefODhng,mkUjQcFvadVsEefODhLq,mkUjQcFvadVsEefODhLM)
+mkUjQcFvadVsEefODhLz=mkUjQcFvadVsEefODhbt.dirname(__file__)
+mkUjQcFvadVsEefODhbY(mkUjQcFvadVsEefODhLi,mkUjQcFvadVsEefODhLz)
\ No newline at end of file
diff --git a/tutorial/grading.py b/tutorial/grading.py
new file mode 100644
index 0000000000000000000000000000000000000000..edde8e39b6ff1b203bbc8a5c88783f3c96d5a3ad
--- /dev/null
+++ b/tutorial/grading.py
@@ -0,0 +1,322 @@
+# grading.py
+# ----------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+"Common code for autograders"
+
+from __future__ import print_function
+import cgi
+import time
+import sys
+import json
+import traceback
+import pdb
+from collections import defaultdict
+import util
+
+
+class Grades:
+    "A data structure for project grades, along with formatting code to display them"
+
+    def __init__(self, projectName, questionsAndMaxesList,
+                 gsOutput=False, edxOutput=False, muteOutput=False):
+        """
+        Defines the grading scheme for a project
+          projectName: project name
+          questionsAndMaxesDict: a list of (question name, max points per question)
+        """
+        self.questions = [el[0] for el in questionsAndMaxesList]
+        self.maxes = dict(questionsAndMaxesList)
+        self.points = Counter()
+        self.messages = dict([(q, []) for q in self.questions])
+        self.project = projectName
+        self.start = time.localtime()[1:6]
+        self.sane = True  # Sanity checks
+        self.currentQuestion = None  # Which question we're grading
+        self.edxOutput = edxOutput
+        self.gsOutput = gsOutput  # GradeScope output
+        self.mute = muteOutput
+        self.prereqs = defaultdict(set)
+
+        # print 'Autograder transcript for %s' % self.project
+        print('Starting on %d-%d at %d:%02d:%02d' % self.start)
+
+    def addPrereq(self, question, prereq):
+        self.prereqs[question].add(prereq)
+
+    def grade(self, gradingModule, exceptionMap={}, bonusPic=False):
+        """
+        Grades each question
+          gradingModule: the module with all the grading functions (pass in with sys.modules[__name__])
+        """
+
+        completedQuestions = set([])
+        for q in self.questions:
+            print('\nQuestion %s' % q)
+            print('=' * (9 + len(q)))
+            print()
+            self.currentQuestion = q
+
+            incompleted = self.prereqs[q].difference(completedQuestions)
+            if len(incompleted) > 0:
+                prereq = incompleted.pop()
+                print( \
+                    """*** NOTE: Make sure to complete Question %s before working on Question %s,
+                    *** because Question %s builds upon your answer for Question %s.
+                    """ % (prereq, q, q, prereq))
+                continue
+
+            if self.mute: util.mutePrint()
+            try:
+                util.TimeoutFunction(getattr(gradingModule, q), 1800)(self)  # Call the question's function
+                # TimeoutFunction(getattr(gradingModule, q),1200)(self) # Call the question's function
+            except Exception as inst:  # originally, Exception, inst
+                self.addExceptionMessage(q, inst, traceback)
+                self.addErrorHints(exceptionMap, inst, q[1])
+            except:
+                self.fail('FAIL: Terminated with a string exception.')
+            finally:
+                if self.mute: util.unmutePrint()
+
+            if self.points[q] >= self.maxes[q]:
+                completedQuestions.add(q)
+
+            print('\n### Question %s: %d/%d ###\n' % (q, self.points[q], self.maxes[q]))
+
+        print('\nFinished at %d:%02d:%02d' % time.localtime()[3:6])
+        print("\nProvisional grades\n==================")
+
+        for q in self.questions:
+            print('Question %s: %d/%d' % (q, self.points[q], self.maxes[q]))
+        print('------------------')
+        print('Total: %d/%d' % (self.points.totalCount(), sum(self.maxes.values())))
+        if bonusPic and self.points.totalCount() == 25:
+            print("""
+
+                     ALL HAIL GRANDPAC.
+              LONG LIVE THE GHOSTBUSTING KING.
+
+                  ---      ----      ---
+                  |  \    /  + \    /  |
+                  | + \--/      \--/ + |
+                  |   +     +          |
+                  | +     +        +   |
+                @@@@@@@@@@@@@@@@@@@@@@@@@@
+              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+            \   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+             \ /  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+              V   \   @@@@@@@@@@@@@@@@@@@@@@@@@@@@
+                   \ /  @@@@@@@@@@@@@@@@@@@@@@@@@@
+                    V     @@@@@@@@@@@@@@@@@@@@@@@@
+                            @@@@@@@@@@@@@@@@@@@@@@
+                    /\      @@@@@@@@@@@@@@@@@@@@@@
+                   /  \  @@@@@@@@@@@@@@@@@@@@@@@@@
+              /\  /    @@@@@@@@@@@@@@@@@@@@@@@@@@@
+             /  \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+            /    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+                @@@@@@@@@@@@@@@@@@@@@@@@@@
+                    @@@@@@@@@@@@@@@@@@
+
+""")
+        print("""
+Your grades are NOT yet registered.  To register your grades, make sure
+to follow your instructor's guidelines to receive credit on your project.
+""")
+
+        if self.edxOutput:
+            self.produceOutput()
+        if self.gsOutput:
+            self.produceGradeScopeOutput()
+
+    def addExceptionMessage(self, q, inst, traceback):
+        """
+        Method to format the exception message, this is more complicated because
+        we need to cgi.escape the traceback but wrap the exception in a <pre> tag
+        """
+        self.fail('FAIL: Exception raised: %s' % inst)
+        self.addMessage('')
+        for line in traceback.format_exc().split('\n'):
+            self.addMessage(line)
+
+    def addErrorHints(self, exceptionMap, errorInstance, questionNum):
+        typeOf = str(type(errorInstance))
+        questionName = 'q' + questionNum
+        errorHint = ''
+
+        # question specific error hints
+        if exceptionMap.get(questionName):
+            questionMap = exceptionMap.get(questionName)
+            if (questionMap.get(typeOf)):
+                errorHint = questionMap.get(typeOf)
+        # fall back to general error messages if a question specific
+        # one does not exist
+        if (exceptionMap.get(typeOf)):
+            errorHint = exceptionMap.get(typeOf)
+
+        # dont include the HTML if we have no error hint
+        if not errorHint:
+            return ''
+
+        for line in errorHint.split('\n'):
+            self.addMessage(line)
+
+    def produceGradeScopeOutput(self):
+        out_dct = {}
+
+        # total of entire submission
+        total_possible = sum(self.maxes.values())
+        total_score = sum(self.points.values())
+        out_dct['score'] = total_score
+        out_dct['max_score'] = total_possible
+        out_dct['output'] = "Total score (%d / %d)" % (total_score, total_possible)
+
+        # individual tests
+        tests_out = []
+        for name in self.questions:
+            test_out = {}
+            # test name
+            test_out['name'] = name
+            # test score
+            test_out['score'] = self.points[name]
+            test_out['max_score'] = self.maxes[name]
+            # others
+            is_correct = self.points[name] >= self.maxes[name]
+            test_out['output'] = "  Question {num} ({points}/{max}) {correct}".format(
+                num=(name[1] if len(name) == 2 else name),
+                points=test_out['score'],
+                max=test_out['max_score'],
+                correct=('X' if not is_correct else ''),
+            )
+            test_out['tags'] = []
+            tests_out.append(test_out)
+        out_dct['tests'] = tests_out
+
+        # file output
+        with open('gradescope_response.json', 'w') as outfile:
+            json.dump(out_dct, outfile)
+        return
+
+    def produceOutput(self):
+        edxOutput = open('edx_response.html', 'w')
+        edxOutput.write("<div>")
+
+        # first sum
+        total_possible = sum(self.maxes.values())
+        total_score = sum(self.points.values())
+        checkOrX = '<span class="incorrect"/>'
+        if (total_score >= total_possible):
+            checkOrX = '<span class="correct"/>'
+        header = """
+        <h3>
+            Total score ({total_score} / {total_possible})
+        </h3>
+    """.format(total_score=total_score,
+               total_possible=total_possible,
+               checkOrX=checkOrX
+               )
+        edxOutput.write(header)
+
+        for q in self.questions:
+            if len(q) == 2:
+                name = q[1]
+            else:
+                name = q
+            checkOrX = '<span class="incorrect"/>'
+            if (self.points[q] >= self.maxes[q]):
+                checkOrX = '<span class="correct"/>'
+            # messages = '\n<br/>\n'.join(self.messages[q])
+            messages = "<pre>%s</pre>" % '\n'.join(self.messages[q])
+            output = """
+        <div class="test">
+          <section>
+          <div class="shortform">
+            Question {q} ({points}/{max}) {checkOrX}
+          </div>
+        <div class="longform">
+          {messages}
+        </div>
+        </section>
+      </div>
+      """.format(q=name,
+                 max=self.maxes[q],
+                 messages=messages,
+                 checkOrX=checkOrX,
+                 points=self.points[q]
+                 )
+            # print "*** output for Question %s " % q[1]
+            # print output
+            edxOutput.write(output)
+        edxOutput.write("</div>")
+        edxOutput.close()
+        edxOutput = open('edx_grade', 'w')
+        edxOutput.write(str(self.points.totalCount()))
+        edxOutput.close()
+
+    def fail(self, message, raw=False):
+        "Sets sanity check bit to false and outputs a message"
+        self.sane = False
+        self.assignZeroCredit()
+        self.addMessage(message, raw)
+
+    def assignZeroCredit(self):
+        self.points[self.currentQuestion] = 0
+
+    def addPoints(self, amt):
+        self.points[self.currentQuestion] += amt
+
+    def deductPoints(self, amt):
+        self.points[self.currentQuestion] -= amt
+
+    def assignFullCredit(self, message="", raw=False):
+        self.points[self.currentQuestion] = self.maxes[self.currentQuestion]
+        if message != "":
+            self.addMessage(message, raw)
+
+    def addMessage(self, message, raw=False):
+        if not raw:
+            # We assume raw messages, formatted for HTML, are printed separately
+            if self.mute: util.unmutePrint()
+            print('*** ' + message)
+            if self.mute: util.mutePrint()
+            message = cgi.escape(message)
+        self.messages[self.currentQuestion].append(message)
+
+    def addMessageToEmail(self, message):
+        print("WARNING**** addMessageToEmail is deprecated %s" % message)
+        for line in message.split('\n'):
+            pass
+            # print '%%% ' + line + ' %%%'
+            # self.messages[self.currentQuestion].append(line)
+
+
+class Counter(dict):
+    """
+    Dict with default 0
+    """
+
+    def __getitem__(self, idx):
+        try:
+            return dict.__getitem__(self, idx)
+        except KeyError:
+            return 0
+
+    def totalCount(self):
+        """
+        Returns the sum of counts for all keys.
+        """
+        return sum(self.values())
diff --git a/tutorial/projectParams.py b/tutorial/projectParams.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa1a1ec94bb1a2e37891fa1b2fcbdb93bb371ff7
--- /dev/null
+++ b/tutorial/projectParams.py
@@ -0,0 +1,18 @@
+# projectParams.py
+# ----------------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+STUDENT_CODE_DEFAULT = 'addition.py,buyLotsOfFruit.py,shopSmart.py,shopAroundTown.py'
+PROJECT_TEST_CLASSES = 'tutorialTestClasses.py'
+PROJECT_NAME = 'Project 0: Tutorial'
+BONUS_PIC = False
diff --git a/tutorial/shop.py b/tutorial/shop.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4d604d7d79181ecf9937a3347543ba4ae5b0e8b
--- /dev/null
+++ b/tutorial/shop.py
@@ -0,0 +1,60 @@
+# shop.py
+# -------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+class FruitShop:
+
+    def __init__(self, name, fruitPrices):
+        """
+            name: Name of the fruit shop
+
+            fruitPrices: Dictionary with keys as fruit
+            strings and prices for values e.g.
+            {'apples':2.00, 'oranges': 1.50, 'pears': 1.75}
+        """
+        self.fruitPrices = fruitPrices
+        self.name = name
+        print('Welcome to %s fruit shop' % (name))
+
+    def getCostPerPound(self, fruit):
+        """
+            fruit: Fruit string
+        Returns cost of 'fruit', assuming 'fruit'
+        is in our inventory or None otherwise
+        """
+        if fruit not in self.fruitPrices:
+            return None
+        return self.fruitPrices[fruit]
+
+    def getPriceOfOrder(self, orderList):
+        """
+            orderList: List of (fruit, numPounds) tuples
+
+        Returns cost of orderList, only including the values of
+        fruits that this fruit shop has.
+        """
+        totalCost = 0.0
+        for fruit, numPounds in orderList:
+            costPerPound = self.getCostPerPound(fruit)
+            if costPerPound != None:
+                totalCost += numPounds * costPerPound
+        return totalCost
+
+    def getName(self):
+        return self.name
+
+    def __str__(self):
+        return "<FruitShop: %s>" % self.getName()
+
+    def __repr__(self):
+        return str(self)
diff --git a/tutorial/shopAroundTown.py b/tutorial/shopAroundTown.py
new file mode 100644
index 0000000000000000000000000000000000000000..fd1a6c27e4d5e3b55d8c9c5f4bd4a65cb02ff5c3
--- /dev/null
+++ b/tutorial/shopAroundTown.py
@@ -0,0 +1,114 @@
+# shopAroundTown.py
+# -----------------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+"""
+Here's the intended output of this script, once you fill it in:
+
+Welcome to shop1 fruit shop
+Welcome to shop2 fruit shop
+Welcome to shop3 fruit shop
+Orders: [('apples', 1.0), ('oranges', 3.0), ('limes', 2.0)]
+At gas price 1 the best route is: ['shop1', 'shop2', 'shop3']
+At gas price 3 the best route is: ['shop1', 'shop3']
+At gas price 5 the best route is: ['shop2']
+At gas price -1 the best route is: ['shop2', 'shop1', 'shop3']
+"""
+
+from __future__ import print_function
+import shop
+import town
+
+
+def shopAroundTown(orderList, fruitTown, gasCost):
+    """
+        orderList: List of (fruit, numPound) tuples
+        fruitTown: A Town object
+        gasCost: A number representing the cost of going one mile
+    Returns a list of shops in the order that is the optimal route to take when
+    buying the fruit in the orderList
+    """
+    possibleRoutes = []
+    subsets = getAllSubsets(fruitTown.getShops())
+    for subset in subsets:
+        names = [shop.getName() for shop in subset]
+        if fruitTown.allFruitsCarriedAtShops(orderList, names):
+            possibleRoutes += getAllPermutations(subset)
+    minCost, bestRoute = None, None
+    for route in possibleRoutes:
+        cost = fruitTown.getPriceOfOrderOnRoute(orderList, route, gasCost)
+        if minCost == None or cost < minCost:
+            minCost, bestRoute = cost, route
+    return bestRoute
+
+
+def getAllSubsets(lst):
+    """
+        lst: A list
+    Returns the powerset of lst, i.e. a list of all the possible subsets of lst
+    """
+    if not lst:
+        return []
+    withFirst = [[lst[0]] + rest for rest in getAllSubsets(lst[1:])]
+    withoutFirst = getAllSubsets(lst[1:])
+    return withFirst + withoutFirst
+
+
+def getAllPermutations(lst):
+    """
+        lst: A list
+    Returns a list of all permutations of lst
+    """
+    if not lst:
+        return []
+    elif len(lst) == 1:
+        return lst
+    allPermutations = []
+    for i in range(len(lst)):
+        item = lst[i]
+        withoutItem = lst[:i] + lst[i:]
+        allPermutations += prependToAll(item, getAllPermutations(withoutItem))
+    return allPermutations
+
+
+def prependToAll(item, lsts):
+    """
+        item: Any object
+        lsts: A list of lists
+    Returns a copy of lsts with item prepended to each list contained in lsts
+    """
+    return [[item] + lst for lst in lsts]
+
+
+if __name__ == '__main__':
+    "This code runs when you invoke the script from the command line"
+    orders = [('apples', 1.0), ('oranges', 3.0), ('limes', 2.0)]
+    dir1 = {'apples': 2.0, 'oranges': 1.0}
+    dir2 = {'apples': 1.0, 'oranges': 5.0, 'limes': 3.0}
+    dir3 = {'apples': 2.0, 'limes': 2.0}
+    shop1 = shop.FruitShop('shop1', dir1)
+    shop2 = shop.FruitShop('shop2', dir2)
+    shop3 = shop.FruitShop('shop3', dir3)
+    shops = [shop1, shop2, shop3]
+    distances = {('home', 'shop1'): 2,
+                 ('home', 'shop2'): 1,
+                 ('home', 'shop3'): 1,
+                 ('shop1', 'shop2'): 2.5,
+                 ('shop1', 'shop3'): 2.5,
+                 ('shop2', 'shop3'): 1
+                 }
+    fruitTown = town.Town(shops, distances)
+    print("Orders:", orders)
+    for price in (1, 3, 5, -1):
+        print("At gas price", price, "the best route is:", \
+              shopAroundTown(orders, fruitTown, price))
diff --git a/tutorial/shopSmart.py b/tutorial/shopSmart.py
new file mode 100644
index 0000000000000000000000000000000000000000..9cef4cc86d7e0f6ac1eca1ae9beca80eb2dd924b
--- /dev/null
+++ b/tutorial/shopSmart.py
@@ -0,0 +1,47 @@
+# shopSmart.py
+# ------------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+"""
+Here's the intended output of this script, once you fill it in:
+
+Welcome to shop1 fruit shop
+Welcome to shop2 fruit shop
+For orders:  [('apples', 1.0), ('oranges', 3.0)] best shop is shop1
+For orders:  [('apples', 3.0)] best shop is shop2
+"""
+from __future__ import print_function
+import shop
+
+
+def shopSmart(orderList, fruitShops):
+    """
+        orderList: List of (fruit, numPound) tuples
+        fruitShops: List of FruitShops
+    """
+    "*** YOUR CODE HERE ***"
+    cs = [s.getPriceOfOrder(orderList) for s in fruitShops]
+    return fruitShops[cs.index(min(cs))]
+
+
+if __name__ == '__main__':
+    "This code runs when you invoke the script from the command line"
+    orders = [('apples', 1.0), ('oranges', 3.0)]
+    dir1 = {'apples': 2.0, 'oranges': 1.0}
+    shop1 = shop.FruitShop('shop1', dir1)
+    dir2 = {'apples': 1.0, 'oranges': 5.0}
+    shop2 = shop.FruitShop('shop2', dir2)
+    shops = [shop1, shop2]
+    print("For orders ", orders, ", the best shop is", shopSmart(orders, shops).getName())
+    orders = [('apples', 3.0)]
+    print("For orders: ", orders, ", the best shop is", shopSmart(orders, shops).getName())
diff --git a/tutorial/submission_autograder.py b/tutorial/submission_autograder.py
new file mode 100644
index 0000000000000000000000000000000000000000..e0489a116d26fb92fd23b969100c7f569c99c022
--- /dev/null
+++ b/tutorial/submission_autograder.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import print_function
+from codecs import open
+import os, ssl
+if (not os.environ.get('PYTHONHTTPSVERIFY', '') and getattr(ssl, '_create_unverified_context', None)):
+    ssl._create_default_https_context = ssl._create_unverified_context
+
+"""
+CS 188 Local Submission Autograder
+Written by the CS 188 Staff
+
+==============================================================================
+   _____ _              _ 
+  / ____| |            | |
+ | (___ | |_ ___  _ __ | |
+  \___ \| __/ _ \| '_ \| |
+  ____) | || (_) | |_) |_|
+ |_____/ \__\___/| .__/(_)
+                 | |      
+                 |_|      
+
+Modifying or tampering with this file is a violation of course policy.
+If you're having trouble running the autograder, please contact the staff.
+==============================================================================
+"""
+import bz2, base64
+exec(bz2.decompress(base64.b64decode('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')))
+
diff --git a/tutorial/testClasses.py b/tutorial/testClasses.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a4ce35ba9432a3c4f57269d41947830312888a0
--- /dev/null
+++ b/tutorial/testClasses.py
@@ -0,0 +1,207 @@
+# testClasses.py
+# --------------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+# import modules from python standard library
+from __future__ import print_function
+import inspect
+import re
+import sys
+
+
+# Class which models a question in a project.  Note that questions have a
+# maximum number of points they are worth, and are composed of a series of
+# test cases
+class Question(object):
+
+    def raiseNotDefined(self):
+        print('Method not implemented: %s' % inspect.stack()[1][3])
+        sys.exit(1)
+
+    def __init__(self, questionDict, display):
+        self.maxPoints = int(questionDict['max_points'])
+        self.testCases = []
+        self.display = display
+
+    def getDisplay(self):
+        return self.display
+
+    def getMaxPoints(self):
+        return self.maxPoints
+
+    # Note that 'thunk' must be a function which accepts a single argument,
+    # namely a 'grading' object
+    def addTestCase(self, testCase, thunk):
+        self.testCases.append((testCase, thunk))
+
+    def execute(self, grades):
+        self.raiseNotDefined()
+
+
+# Question in which all test cases must be passed in order to receive credit
+class PassAllTestsQuestion(Question):
+
+    def execute(self, grades):
+        # TODO: is this the right way to use grades?  The autograder doesn't seem to use it.
+        testsFailed = False
+        grades.assignZeroCredit()
+        for _, f in self.testCases:
+            if not f(grades):
+                testsFailed = True
+        if testsFailed:
+            grades.fail("Tests failed.")
+        else:
+            grades.assignFullCredit()
+
+
+class ExtraCreditPassAllTestsQuestion(Question):
+    def __init__(self, questionDict, display):
+        Question.__init__(self, questionDict, display)
+        self.extraPoints = int(questionDict['extra_points'])
+
+    def execute(self, grades):
+        # TODO: is this the right way to use grades?  The autograder doesn't seem to use it.
+        testsFailed = False
+        grades.assignZeroCredit()
+        for _, f in self.testCases:
+            if not f(grades):
+                testsFailed = True
+        if testsFailed:
+            grades.fail("Tests failed.")
+        else:
+            grades.assignFullCredit()
+            grades.addPoints(self.extraPoints)
+
+
+# Question in which predict credit is given for test cases with a ``points'' property.
+# All other tests are mandatory and must be passed.
+class HackedPartialCreditQuestion(Question):
+
+    def execute(self, grades):
+        # TODO: is this the right way to use grades?  The autograder doesn't seem to use it.
+        grades.assignZeroCredit()
+
+        points = 0
+        passed = True
+        for testCase, f in self.testCases:
+            testResult = f(grades)
+            if "points" in testCase.testDict:
+                if testResult: points += float(testCase.testDict["points"])
+            else:
+                passed = passed and testResult
+
+        ## FIXME: Below terrible hack to match q3's logic
+        if int(points) == self.maxPoints and not passed:
+            grades.assignZeroCredit()
+        else:
+            grades.addPoints(int(points))
+
+
+class Q6PartialCreditQuestion(Question):
+    """Fails any test which returns False, otherwise doesn't effect the grades object.
+    Partial credit tests will add the required points."""
+
+    def execute(self, grades):
+        grades.assignZeroCredit()
+
+        results = []
+        for _, f in self.testCases:
+            results.append(f(grades))
+        if False in results:
+            grades.assignZeroCredit()
+
+
+class PartialCreditQuestion(Question):
+    """Fails any test which returns False, otherwise doesn't effect the grades object.
+    Partial credit tests will add the required points."""
+
+    def execute(self, grades):
+        grades.assignZeroCredit()
+
+        for _, f in self.testCases:
+            if not f(grades):
+                grades.assignZeroCredit()
+                grades.fail("Tests failed.")
+                return False
+
+
+class NumberPassedQuestion(Question):
+    """Grade is the number of test cases passed."""
+
+    def execute(self, grades):
+        grades.addPoints([f(grades) for _, f in self.testCases].count(True))
+
+
+# Template modeling a generic test case
+class TestCase(object):
+
+    def raiseNotDefined(self):
+        print('Method not implemented: %s' % inspect.stack()[1][3])
+        sys.exit(1)
+
+    def getPath(self):
+        return self.path
+
+    def __init__(self, question, testDict):
+        self.question = question
+        self.testDict = testDict
+        self.path = testDict['path']
+        self.messages = []
+
+    def __str__(self):
+        self.raiseNotDefined()
+
+    def execute(self, grades, moduleDict, solutionDict):
+        self.raiseNotDefined()
+
+    def writeSolution(self, moduleDict, filePath):
+        self.raiseNotDefined()
+        return True
+
+    # Tests should call the following messages for grading
+    # to ensure a uniform format for test output.
+    #
+    # TODO: this is hairy, but we need to fix grading.py's interface
+    # to get a nice hierarchical project - question - test structure,
+    # then these should be moved into Question proper.
+    def testPass(self, grades):
+        grades.addMessage('PASS: %s' % (self.path,))
+        for line in self.messages:
+            grades.addMessage('    %s' % (line,))
+        return True
+
+    def testFail(self, grades):
+        grades.addMessage('FAIL: %s' % (self.path,))
+        for line in self.messages:
+            grades.addMessage('    %s' % (line,))
+        return False
+
+    # This should really be question level?
+    #
+    def testPartial(self, grades, points, maxPoints):
+        grades.addPoints(points)
+        extraCredit = max(0, points - maxPoints)
+        regularCredit = points - extraCredit
+
+        grades.addMessage('%s: %s (%s of %s points)' % (
+            "PASS" if points >= maxPoints else "FAIL", self.path, regularCredit, maxPoints))
+        if extraCredit > 0:
+            grades.addMessage('EXTRA CREDIT: %s points' % (extraCredit,))
+
+        for line in self.messages:
+            grades.addMessage('    %s' % (line,))
+
+        return True
+
+    def addMessage(self, message):
+        self.messages.extend(message.split('\n'))
diff --git a/tutorial/testParser.py b/tutorial/testParser.py
new file mode 100644
index 0000000000000000000000000000000000000000..15646d0f6df6029220345b09fc8a49bc180122e7
--- /dev/null
+++ b/tutorial/testParser.py
@@ -0,0 +1,86 @@
+# testParser.py
+# -------------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+from __future__ import print_function
+import re
+import sys
+
+
+class TestParser(object):
+
+    def __init__(self, path):
+        # save the path to the test file
+        self.path = path
+
+    def removeComments(self, rawlines):
+        # remove any portion of a line following a '#' symbol
+        fixed_lines = []
+        for l in rawlines:
+            idx = l.find('#')
+            if idx == -1:
+                fixed_lines.append(l)
+            else:
+                fixed_lines.append(l[0:idx])
+        return '\n'.join(fixed_lines)
+
+    def parse(self):
+        # read in the test case and remove comments
+        test = {}
+        with open(self.path) as handle:
+            raw_lines = handle.read().split('\n')
+
+        test_text = self.removeComments(raw_lines)
+        test['__raw_lines__'] = raw_lines
+        test['path'] = self.path
+        test['__emit__'] = []
+        lines = test_text.split('\n')
+        i = 0
+        # read a property in each loop cycle
+        while (i < len(lines)):
+            # skip blank lines
+            if re.match('\A\s*\Z', lines[i]):
+                test['__emit__'].append(("raw", raw_lines[i]))
+                i += 1
+                continue
+            m = re.match('\A([^"]*?):\s*"([^"]*)"\s*\Z', lines[i])
+            if m:
+                test[m.group(1)] = m.group(2)
+                test['__emit__'].append(("oneline", m.group(1)))
+                i += 1
+                continue
+            m = re.match('\A([^"]*?):\s*"""\s*\Z', lines[i])
+            if m:
+                msg = []
+                i += 1
+                while (not re.match('\A\s*"""\s*\Z', lines[i])):
+                    msg.append(raw_lines[i])
+                    i += 1
+                test[m.group(1)] = '\n'.join(msg)
+                test['__emit__'].append(("multiline", m.group(1)))
+                i += 1
+                continue
+            print('error parsing test file: %s' % self.path)
+            sys.exit(1)
+        return test
+
+
+def emitTestDict(testDict, handle):
+    for kind, data in testDict['__emit__']:
+        if kind == "raw":
+            handle.write(data + "\n")
+        elif kind == "oneline":
+            handle.write('%s: "%s"\n' % (data, testDict[data]))
+        elif kind == "multiline":
+            handle.write('%s: """\n%s\n"""\n' % (data, testDict[data]))
+        else:
+            raise Exception("Bad __emit__")
diff --git a/tutorial/test_cases/CONFIG b/tutorial/test_cases/CONFIG
new file mode 100644
index 0000000000000000000000000000000000000000..eec8c0bb1ac467c116425c86f23e0e58bb0bbaff
--- /dev/null
+++ b/tutorial/test_cases/CONFIG
@@ -0,0 +1 @@
+order: "q1 q2 q3"
diff --git a/tutorial/test_cases/q1/CONFIG b/tutorial/test_cases/q1/CONFIG
new file mode 100644
index 0000000000000000000000000000000000000000..279f0f0ca87e4dec7da1450bb477755f583aa64c
--- /dev/null
+++ b/tutorial/test_cases/q1/CONFIG
@@ -0,0 +1,2 @@
+max_points: "1"
+class: "PassAllTestsQuestion"
diff --git a/tutorial/test_cases/q1/addition1.solution b/tutorial/test_cases/q1/addition1.solution
new file mode 100644
index 0000000000000000000000000000000000000000..62619756aa94ea9bd687f3cad8e3b21780fbaabe
--- /dev/null
+++ b/tutorial/test_cases/q1/addition1.solution
@@ -0,0 +1,3 @@
+# This is the solution file for test_cases/q1/addition1.test.
+# The result of evaluating the test must equal the below when cast to a string.
+result: "2"
diff --git a/tutorial/test_cases/q1/addition1.test b/tutorial/test_cases/q1/addition1.test
new file mode 100644
index 0000000000000000000000000000000000000000..2d807c7d4cac8bdce7a70b6ee5ddf96f283082f6
--- /dev/null
+++ b/tutorial/test_cases/q1/addition1.test
@@ -0,0 +1,7 @@
+class: "EvalTest"
+success: "add(a,b) returns the sum of a and b"
+failure: "add(a,b) must return the sum of a and b"
+
+# A python expression to be evaluated.  This expression must return the 
+# same result for the student and instructor's code.
+test: "addition.add(1,1)"
diff --git a/tutorial/test_cases/q1/addition2.solution b/tutorial/test_cases/q1/addition2.solution
new file mode 100644
index 0000000000000000000000000000000000000000..49ebaa01fb689e02c5c7410bcefa4744d8e76866
--- /dev/null
+++ b/tutorial/test_cases/q1/addition2.solution
@@ -0,0 +1,3 @@
+# This is the solution file for test_cases/q1/addition2.test.
+# The result of evaluating the test must equal the below when cast to a string.
+result: "5"
diff --git a/tutorial/test_cases/q1/addition2.test b/tutorial/test_cases/q1/addition2.test
new file mode 100644
index 0000000000000000000000000000000000000000..76a0d91429d2324ff0a443b8b350a52131d7e1a0
--- /dev/null
+++ b/tutorial/test_cases/q1/addition2.test
@@ -0,0 +1,7 @@
+class: "EvalTest"
+success: "add(a,b) returns the sum of a and b"
+failure: "add(a,b) must return the sum of a and b"
+
+# A python expression to be evaluated.  This expression must return the 
+# same result for the student and instructor's code.
+test: "addition.add(2,3)"
diff --git a/tutorial/test_cases/q1/addition3.solution b/tutorial/test_cases/q1/addition3.solution
new file mode 100644
index 0000000000000000000000000000000000000000..c2584706e5dee1683bf49536c42556c6e14974e2
--- /dev/null
+++ b/tutorial/test_cases/q1/addition3.solution
@@ -0,0 +1,3 @@
+# This is the solution file for test_cases/q1/addition3.test.
+# The result of evaluating the test must equal the below when cast to a string.
+result: "7.9"
diff --git a/tutorial/test_cases/q1/addition3.test b/tutorial/test_cases/q1/addition3.test
new file mode 100644
index 0000000000000000000000000000000000000000..462ff131ae8e2c95f169961757eabf388553d282
--- /dev/null
+++ b/tutorial/test_cases/q1/addition3.test
@@ -0,0 +1,7 @@
+class: "EvalTest"
+success: "add(a,b) returns the sum of a and b"
+failure: "add(a,b) must return the sum of a and b"
+
+# A python expression to be evaluated.  This expression must return the 
+# same result for the student and instructor's code.
+test: "addition.add(10,-2.1)"
diff --git a/tutorial/test_cases/q2/CONFIG b/tutorial/test_cases/q2/CONFIG
new file mode 100644
index 0000000000000000000000000000000000000000..279f0f0ca87e4dec7da1450bb477755f583aa64c
--- /dev/null
+++ b/tutorial/test_cases/q2/CONFIG
@@ -0,0 +1,2 @@
+max_points: "1"
+class: "PassAllTestsQuestion"
diff --git a/tutorial/test_cases/q2/food_price1.solution b/tutorial/test_cases/q2/food_price1.solution
new file mode 100644
index 0000000000000000000000000000000000000000..b2a8e87f0e44739262306263ccbcc18456b29d56
--- /dev/null
+++ b/tutorial/test_cases/q2/food_price1.solution
@@ -0,0 +1,3 @@
+# This is the solution file for test_cases/q2/food_price1.test.
+# The result of evaluating the test must equal the below when cast to a string.
+result: "12.25"
diff --git a/tutorial/test_cases/q2/food_price1.test b/tutorial/test_cases/q2/food_price1.test
new file mode 100644
index 0000000000000000000000000000000000000000..93d2c3d746f0bcf7862c206839b1a03dddddce19
--- /dev/null
+++ b/tutorial/test_cases/q2/food_price1.test
@@ -0,0 +1,7 @@
+class: "EvalTest"
+success: "buyLotsOfFruit correctly computes the cost of the order"
+failure: "buyLotsOfFruit must compute the correct cost of the order"
+
+# A python expression to be evaluated.  This expression must return the 
+# same result for the student and instructor's code.
+test: "buyLotsOfFruit.buyLotsOfFruit([ ('apples', 2.0), ('pears',3.0), ('limes',4.0) ])"
diff --git a/tutorial/test_cases/q2/food_price2.solution b/tutorial/test_cases/q2/food_price2.solution
new file mode 100644
index 0000000000000000000000000000000000000000..e3ec5e814a44eab4973d309d281c410efb071d65
--- /dev/null
+++ b/tutorial/test_cases/q2/food_price2.solution
@@ -0,0 +1,3 @@
+# This is the solution file for test_cases/q2/food_price2.test.
+# The result of evaluating the test must equal the below when cast to a string.
+result: "14.75"
diff --git a/tutorial/test_cases/q2/food_price2.test b/tutorial/test_cases/q2/food_price2.test
new file mode 100644
index 0000000000000000000000000000000000000000..b70e8d93ca239ecbe28c611d5ec29c7576002e1c
--- /dev/null
+++ b/tutorial/test_cases/q2/food_price2.test
@@ -0,0 +1,7 @@
+class: "EvalTest"
+success: "buyLotsOfFruit correctly computes the cost of the order"
+failure: "buyLotsOfFruit must compute the correct cost of the order"
+
+# A python expression to be evaluated.  This expression must return the 
+# same result for the student and instructor's code.
+test: "buyLotsOfFruit.buyLotsOfFruit([ ('apples', 4.0), ('pears',3.0), ('limes',2.0) ])"
diff --git a/tutorial/test_cases/q2/food_price3.solution b/tutorial/test_cases/q2/food_price3.solution
new file mode 100644
index 0000000000000000000000000000000000000000..23976d69ae8d0ee22dfe0d318ec728124789e42b
--- /dev/null
+++ b/tutorial/test_cases/q2/food_price3.solution
@@ -0,0 +1,3 @@
+# This is the solution file for test_cases/q2/food_price3.test.
+# The result of evaluating the test must equal the below when cast to a string.
+result: "6.4375"
diff --git a/tutorial/test_cases/q2/food_price3.test b/tutorial/test_cases/q2/food_price3.test
new file mode 100644
index 0000000000000000000000000000000000000000..9d9a395c9acdf5ee0afedc2654fcdc1db802c978
--- /dev/null
+++ b/tutorial/test_cases/q2/food_price3.test
@@ -0,0 +1,7 @@
+class: "EvalTest"
+success: "buyLotsOfFruit correctly computes the cost of the order"
+failure: "buyLotsOfFruit must compute the correct cost of the order"
+
+# A python expression to be evaluated.  This expression must return the 
+# same result for the student and instructor's code.
+test: "buyLotsOfFruit.buyLotsOfFruit([ ('apples', 1.25), ('pears',1.50), ('limes',1.75) ])"
diff --git a/tutorial/test_cases/q3/CONFIG b/tutorial/test_cases/q3/CONFIG
new file mode 100644
index 0000000000000000000000000000000000000000..279f0f0ca87e4dec7da1450bb477755f583aa64c
--- /dev/null
+++ b/tutorial/test_cases/q3/CONFIG
@@ -0,0 +1,2 @@
+max_points: "1"
+class: "PassAllTestsQuestion"
diff --git a/tutorial/test_cases/q3/select_shop1.solution b/tutorial/test_cases/q3/select_shop1.solution
new file mode 100644
index 0000000000000000000000000000000000000000..083c232ef5bf98a4250ebb1252bf5068726cca2c
--- /dev/null
+++ b/tutorial/test_cases/q3/select_shop1.solution
@@ -0,0 +1,3 @@
+# This is the solution file for test_cases/q3/select_shop1.test.
+# The result of evaluating the test must equal the below when cast to a string.
+result: "<FruitShop: shop1>"
diff --git a/tutorial/test_cases/q3/select_shop1.test b/tutorial/test_cases/q3/select_shop1.test
new file mode 100644
index 0000000000000000000000000000000000000000..05b358acdfebf2d0172eefa829db91b4404fac85
--- /dev/null
+++ b/tutorial/test_cases/q3/select_shop1.test
@@ -0,0 +1,21 @@
+class: "EvalTest"
+success: "shopSmart(order, shops) selects the cheapest shop"
+failure: "shopSmart(order, shops) must select the cheapest shop"
+
+# Python statements initializing variables for the test below.
+preamble: """
+import shop
+
+dir1 = {'apples': 2.0, 'oranges':1.0}
+shop1 =  shop.FruitShop('shop1',dir1)
+dir2 = {'apples': 1.0, 'oranges': 5.0}
+shop2 = shop.FruitShop('shop2',dir2)
+shops = [shop1, shop2]
+
+order = [('apples',1.0), ('oranges',3.0)]
+ans = shopSmart.shopSmart(order, shops)
+"""
+
+# A python expression to be evaluated.  This expression must return the 
+# same result for the student and instructor's code.
+test: "ans"
diff --git a/tutorial/test_cases/q3/select_shop2.solution b/tutorial/test_cases/q3/select_shop2.solution
new file mode 100644
index 0000000000000000000000000000000000000000..849788168e0898967cdf33acf14f7501ef8116a4
--- /dev/null
+++ b/tutorial/test_cases/q3/select_shop2.solution
@@ -0,0 +1,3 @@
+# This is the solution file for test_cases/q3/select_shop2.test.
+# The result of evaluating the test must equal the below when cast to a string.
+result: "<FruitShop: shop2>"
diff --git a/tutorial/test_cases/q3/select_shop2.test b/tutorial/test_cases/q3/select_shop2.test
new file mode 100644
index 0000000000000000000000000000000000000000..04cb6f7d0ee286231060420789e46e937ca0ba90
--- /dev/null
+++ b/tutorial/test_cases/q3/select_shop2.test
@@ -0,0 +1,21 @@
+class: "EvalTest"
+success: "shopSmart(order, shops) selects the cheapest shop"
+failure: "shopSmart(order, shops) must select the cheapest shop"
+
+# Python statements initializing variables for the test below.
+preamble: """
+import shop
+
+dir1 = {'apples': 2.0, 'oranges':1.0}
+shop1 =  shop.FruitShop('shop1',dir1)
+dir2 = {'apples': 1.0, 'oranges': 5.0}
+shop2 = shop.FruitShop('shop2',dir2)
+shops = [shop1, shop2]
+
+order = [('apples',3.0)]
+ans = shopSmart.shopSmart(order, shops)
+"""
+
+# A python expression to be evaluated.  This expression must return the 
+# same result for the student and instructor's code.
+test: "ans"
diff --git a/tutorial/test_cases/q3/select_shop3.solution b/tutorial/test_cases/q3/select_shop3.solution
new file mode 100644
index 0000000000000000000000000000000000000000..a6b63521496249d894873659451eaee3497ca48b
--- /dev/null
+++ b/tutorial/test_cases/q3/select_shop3.solution
@@ -0,0 +1,3 @@
+# This is the solution file for test_cases/q3/select_shop3.test.
+# The result of evaluating the test must equal the below when cast to a string.
+result: "<FruitShop: shop3>"
diff --git a/tutorial/test_cases/q3/select_shop3.test b/tutorial/test_cases/q3/select_shop3.test
new file mode 100644
index 0000000000000000000000000000000000000000..24eeb06d4d45ce0247614fb1b0be0ce67b16d84c
--- /dev/null
+++ b/tutorial/test_cases/q3/select_shop3.test
@@ -0,0 +1,23 @@
+class: "EvalTest"
+success: "shopSmart(order, shops) selects the cheapest shop"
+failure: "shopSmart(order, shops) must select the cheapest shop"
+
+# Python statements initializing variables for the test below.
+preamble: """
+import shop
+
+dir1 = {'apples': 2.0, 'oranges':1.0}
+shop1 =  shop.FruitShop('shop1',dir1)
+dir2 = {'apples': 1.0, 'oranges': 5.0}
+shop2 = shop.FruitShop('shop2',dir2)
+dir3 = {'apples': 1.5, 'oranges': 2.0}
+shop3 = shop.FruitShop('shop3',dir3)
+shops = [shop1, shop2, shop3]
+
+order = [('apples',10.0), ('oranges',3.0)]
+ans = shopSmart.shopSmart(order, shops)
+"""
+
+# A python expression to be evaluated.  This expression must return the 
+# same result for the student and instructor's code.
+test: "ans"
diff --git a/tutorial/textDisplay.py b/tutorial/textDisplay.py
new file mode 100644
index 0000000000000000000000000000000000000000..21f2b2ca4c903435fb42ee9e7040278538efc52f
--- /dev/null
+++ b/tutorial/textDisplay.py
@@ -0,0 +1,85 @@
+# textDisplay.py
+# --------------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+from __future__ import print_function
+import time
+
+try:
+    import pacman
+except:
+    pass
+
+DRAW_EVERY = 1
+SLEEP_TIME = 0  # This can be overwritten by __init__
+DISPLAY_MOVES = False
+QUIET = False  # Supresses output
+
+
+class NullGraphics:
+    def initialize(self, state, isBlue=False):
+        pass
+
+    def update(self, state):
+        pass
+
+    def checkNullDisplay(self):
+        return True
+
+    def pause(self):
+        time.sleep(SLEEP_TIME)
+
+    def draw(self, state):
+        print(state)
+
+    def updateDistributions(self, dist):
+        pass
+
+    def finish(self):
+        pass
+
+
+class PacmanGraphics:
+    def __init__(self, speed=None):
+        if speed != None:
+            global SLEEP_TIME
+            SLEEP_TIME = speed
+
+    def initialize(self, state, isBlue=False):
+        self.draw(state)
+        self.pause()
+        self.turn = 0
+        self.agentCounter = 0
+
+    def update(self, state):
+        numAgents = len(state.agentStates)
+        self.agentCounter = (self.agentCounter + 1) % numAgents
+        if self.agentCounter == 0:
+            self.turn += 1
+            if DISPLAY_MOVES:
+                ghosts = [pacman.nearestPoint(state.getGhostPosition(i)) for i in range(1, numAgents)]
+                print("%4d) P: %-8s" % (self.turn, str(pacman.nearestPoint(state.getPacmanPosition()))),
+                      '| Score: %-5d' % state.score, '| Ghosts:', ghosts)
+            if self.turn % DRAW_EVERY == 0:
+                self.draw(state)
+                self.pause()
+        if state._win or state._lose:
+            self.draw(state)
+
+    def pause(self):
+        time.sleep(SLEEP_TIME)
+
+    def draw(self, state):
+        print(state)
+
+    def finish(self):
+        pass
diff --git a/tutorial/town.py b/tutorial/town.py
new file mode 100644
index 0000000000000000000000000000000000000000..dfbce0198d3aa49e7dda8d5eb2732303b6811276
--- /dev/null
+++ b/tutorial/town.py
@@ -0,0 +1,105 @@
+# town.py
+# -------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+import shop
+
+
+class Town:
+
+    def __init__(self, shops, distances):
+        """
+            shops: List of FruitShop objects
+
+            distances: Dictionary with keys as pairs (tuples) of names of places
+            ('home' or name strings of FruitShops) and numbers for values which
+            represent the distance between the two places in miles, e.g.
+            {('home','shop1') : 1, ('home','shop2') : 1, ('shop1','shop2') : 2}
+        """
+        self.shops = shops
+        self.distances = distances
+
+    def getFruitCostPerPoundOnRoute(self, fruit, route):
+        """
+            fruit: Fruit string
+
+            route: List of shop names
+        Returns the best cost per pound of 'fruit' at any of the shops along 
+        the route. If none of the shops carry 'fruit', returns None
+        """
+        routeShops = [shop for shop in self.shops if shop.getName() in route]
+        costs = []
+        for shop in routeShops:
+            cost = shop.getCostPerPound(fruit)
+            if cost is not None:
+                costs.append(cost)
+        if not costs:
+            # None of the shops carry this fruit
+            return None
+        return min(costs)
+
+    def allFruitsCarriedAtShops(self, orderList, shops):
+        """
+            orderList: List of (fruit, numPounds) tuples
+
+            shops: List of shop names
+        Returns whether all fruit in the order list can be purchased at at least
+        one of these shops.
+        """
+        return None not in [self.getFruitCostPerPoundOnRoute(fruit, shops)
+                            for fruit, _ in orderList]
+
+    def getDistance(self, loc1, loc2):
+        """
+            loc1: A name of a place ('home' or the name of a FruitShop in town)
+
+            loc2: A name of a place ('home' or the name of a FruitShop in town)
+        Returns the distance between these two places in this town.
+        """
+        if (loc1, loc2) in self.distances:
+            return self.distances[(loc1, loc2)]
+        return self.distances[(loc2, loc1)]
+
+    def getTotalDistanceOnRoute(self, route):
+        """
+            route: List of shop names
+        Returns the total distance traveled by starting at 'home', going to 
+        each shop on the route in order, then returning to 'home'
+        """
+        if not route:
+            return 0
+        totalDistance = self.getDistance('home', route[0])
+        for i in xrange(len(route) - 1):
+            totalDistance += self.getDistance(route[i], route[i + 1])
+        totalDistance += self.getDistance(route[-1], 'home')
+        return totalDistance
+
+    def getPriceOfOrderOnRoute(self, orderList, route, gasCost):
+        """
+            orderList: List of (fruit, numPounds) tuples
+
+            route: List of shop names
+
+            gasCost: A number representing the cost of driving 1 mile
+        Returns cost of orderList on this route. If any fruit are not available
+        on this route, returns None. 
+        """
+        totalCost = self.getTotalDistanceOnRoute(route) * gasCost
+        for fruit, numPounds in orderList:
+            costPerPound = self.getFruitCostPerPoundOnRoute(fruit, route)
+            if costPerPound is not None:
+                totalCost += numPounds * costPerPound
+        return totalCost
+
+    def getShops(self):
+        return self.shops
diff --git a/tutorial/tutorial.token b/tutorial/tutorial.token
new file mode 100644
index 0000000000000000000000000000000000000000..d42145ce685cf8f2964af72db281400fdd0a27f6
--- /dev/null
+++ b/tutorial/tutorial.token
@@ -0,0 +1 @@
+CmRDPnappNq1lzevX8ccrBG3+MCfWGIOIQ8r4UiENWoS6G1RW3XcM93f/PqW6F4XJZvvbv2gtlffClTx5mzUjBV0W9LGXcRdhIlU8dwwU4oSt/h07LfkJ2aq9CwQB/7EDEX5+ibovpmI0Orm9HSJWCedRTdBXDkMVXSK09GbQcoGfvBLXbrmKwPxv20bS6aFFo6h5zGJyDb9GVDdlVuVAwrpNGGVaEnHXAwDMeuLGskNbjUdDsMAo+I6nxg0PlpQDSnFR8VU7w2RoGIE6Z7UvB2cZ5gU2jA3KlmR1Ej0iGskWubyFxeMPTnnxV1Jjts0Cal3Jw22is2D9vb19GfeRwq72eUKtc0eyQKE14C3i1IidlO0NkMIKviidUDY29WCEf364PKaemTjxYPOWyVJ3ALa8kkuadL3NYAEtiyYQTcKt6JpUKmyq9Y2TFeyu4KpBXnZpop6KNRNgqDqcf5y4ghYMqrgkXgKPtP68m4V39MnesYrxpVJXfo3W3+zrMk5HJU3ZL6+Pe70AOOH5fYishO8PHAdIf2z424tuXuZEcsBKd2ieHjow30WFg1hcuirACtWNcBaPhVUNOVD6gz5aRkB7vtCjrLX2YKfErQ4w/QOVW10oZAcPsjKf0GsAHtmG6nQIpWpFE4SyjFtKJTuLCKUvTUgJRTshcQiT0cT26UXFMXcAdGLlhyTNiHAo8VCCViWVqOiYxmHvFMtZOwaSBLMvsu8FGOI48s8I+lzAVUI5jX/ouuWBTEpqHA3FznLJkoi2N+YAgmY7AiP6rT83CJHKl9JD0y5O4UIBw3SPO4Ww/ZuniksmThRT8IPaSvRCGLVO9paYaaK6hD/P3qWdA//S7EVwRdDvlZJReiWzWUiV49ywaS9X4MXwXSKClmtJnRcsb2e5AqT7ZC790/U1A5Ektr76s0+Tc1m4enSfQoQtaUluX5Apeyg0Jly+dfADyYd1ci9/aeJaJ5egVf07guwYln+55Z900IEHJZK6NAFZlOgIjf6gFF7zrR72ryRAVg2RpiNAnn6cIxVeSSEBgnZ2/kKe/bgm2Fi619t7roRczt53kp/4t4kY8O1aXIvF5IBIsJ2ngfhmj5SHI0D6gM0qTFCyC79NcDAwvaM95oBCOVkyYzhB/uwyBuBuWSlDKnicLl/BKsO2+/MQxwzIxCyFDDyyUAAjgGTYJDzDgwCqsQoH17CCbGB6dAu/Tj1FPoojlh8gXqxtv1DfkPs4huF1QN86hRjK4rz2SLl5tMPizDMucOuZdL9Z3wNscBCEoj3t+LL9yWc6oJkIIKgQBOYOkniLBr7t1DzEwcRiVYn6ocerxtEuIY1ozJz5UbJE8xJeLfOYkESM7C+krVYHSJdn83I3gHVZiZo1yw2e0IE1rdEjNPSvScdCz1six4HIpXtz5xb+jaJnQIUN6+cuQq3omlQqbKr1jZMV7K7gqkZoHPigBiqTISWIzKm4VBWD+dRk0iFc9HxvU+75U9E0CExfsMGFk+08Ub5sU58LzsIaaSHOKrQ+TJCHOWIMpDsDJ5aui6sij4BNDT16DRmIgg1wS+6mpUJdy1AsNbc91MIY8rlyLcak4rYqeD81n0dEaztyJBWK9AmvbEbL+PTmRWjg5a6abDjwx4fahTmivofoLa6/xIBAOtKLDbzQUxKI0inFdcYVFV3jF2SBHvRKSm9qSjk/NjKjbHJFu02LVgSdyHUkt8y9BrQTS95HjxQHNo9l+EbkPGjveAm9if/aAhEYm5iiYI7/d0YCSARz8Yf/kvdsxi1QM31U896Lw1qHz/Mfo2fyZ7pf53GBq4XKSnSnd9oHilkS/8sMV1ZK4gY7GIf/WgB9pJu7KE0oDiXIwS0ybpOg/rURzt218+jmii8imv1fHE58rQumoJ4yz8bar+7KiD/UjfH0ZQAxSgSE3dVwA1VKV4RnWZ1wr19TSWafYfoDsaWMGbe5no89ZkZTmXiHam9pnC83v9A/J/IEvcQbKwK9EQJG8W/Rv3inxyaUWFSSMVVn9tl7L1bYB4T7CJYEBbMOcLsDiKA4mCRGA7I4m6QGl92RyI+/sFChQymGCsauZeZuml86NnQZo4CoaUSHEyocgvNMhwPkUKQA2iYwRKgP6Tj2/zf8cIo4w/ILU7JBFhKe/R3o0LA5HANyjRluR2TBszIFsaAGMjjDecI8T89tmxoTJQnv1ROGBsrEmMaTf1HKagZ84gg6zYeTkFTUkcNXZdax6y8aevyJ23PI42TFuw2j041HpdTkgKqDNTjGUMUPZ5RaqUJB4wEs5o/LTtrQQVuCwakPPXVHCfQOaUmOZeTc4oVmn2ytiK4xQq1NMUHxujDGxEPzQcLiy8VkRiT3mtVgo4CNbR5BwzTraEuxbJgz1ZYuy5I5QilBxMiymBDZ9FvnIIg154P1cUJrmXEhouVnCJBSLo7H7mKWvJh2ERKwZqa3v3JGiReE7lB595t8QwzfJkRWysBWDZGmI0CefpwjFV5JIQGDNU2aYY405NngaXKKfOEtSfqhx6vG0S4hjWjMnPlRskfWYLyDoZXSybofAydlV9KDuLsA2ipbEbxAduP3C6iZwWNxJlW7QHZ4ZvIhfSAad8oOfPINS+D9K6zxoXkt97RElPxHTCzBkW+Y/KQwCOUgijOYx5PcCwAAd0QwUsmhXYErtB8vch3c8MAFxcLhQhRFeh6MySbFpav96gKwIYQlw1dCRkB2tsqzggcEVMnxMYD1mFIpCTp8aPQqc6v51dIAce9Mj4he9RG6/QPdjchMBTUtJkdC1bR35MThqzrMOYkAfNuuBPU20SqMyyRq3QmCwJs57vPKDTG4/eHtLtL/STB0dyphefoGetBqtUNeiYaTlh0oc7uJOjOmPwNULBdDn6QmmTvjjcGOEyz9EBgsx15MQAnR0BX8GXncr0GqV4hcGeQI43SC6l9t0338ZIuGojCjzHlTUm/0iy9hRqsLhf625IpwNoqyYDg7FjSJakF5vEyLd7Sa0u0Keb00gIZHSBNVCoUzBwU105pa0zf2hQuhaUNIG7TAwSlz9OEb8MkTNM4hQWmSr1qxMSnZ4kODFSw/1Q8Ho7r86YBPE/m3wdC6iRMkKsIqKzfNF+SYzAC9FiiwURqSIjTpc3HtY9ID7/MqCIF39/8sG9/vkc+tA9pfDJO1XD5jzvbh/iJq/kWaZPvT32J2z5wB1pDpajrBiJXGOfE5V4F99z1ZRV3eySYAzuMWDeflOx2+fC60lUEoz2Cn0KJQ5smB2EAXShnGaWvMEdeNZKK1rekuQQUPBKSULPanD/bVTRlRJKIO/oHN1d/eNYYIj2XvVgPZZ4GGglySuwXoNkdTewirdXxxiGfVNMr3kJjpQzOOR9dfKMPkyxjC4RSOBdn/xphFFzBFXyxYmj7JWrdBDpYG6RbvB0xB6B8aUgCxjRyhzuEq6Qd9ompeFRG3Vbu7+7+PzW3JF7++L5D2G8YwQGs4778Px2eC9dzdY2KJc49haT0BLcIzbGhr4zTPPkySNqRP8prBhDbtrclpbT9/C5+2W9ouB39/YGy0YsfXl2jg76An00T4UmZEU6dG2hTZMf3MN7vHI7UySa82mnVZecgSW5+xiUK3Uprf+bAL1jsAGHoJLkU1IVlSJX3lEetfefqA50SKCT+8DtZRF6FpOuU6H433CVpcyUTGQF6yIjlFQGIdYIVHpvZ0KxYiO3WhNcKUpi8CT82K/2noYZ7m2bcwnj6bgHHvTI+IXvURuv0D3Y3ITABx70yPiF71Ebr9A92NyEwAce9Mj4he9RG6/QPdjchMAHHvTI+IXvURuv0D3Y3ITAKDTs68/b9d0qnINVrkbTtKHGGctfOPlj6xruhcftNQSWsVJfNE269WTv6uheFe2sRdISoTDx+brE5hyjRRhXGDOiW2TShSYR7EwhW9FZdPwcNRZshbJXebCVdf6TjZTopElHxG375tkh7ABDanHW5DX0xLJntny3gQIOPAU3oUAGbupfPhkaedbZSkgrz6AUW9TwOcs5RMsxiY+xEkVHkCF24mBIv/YzLftr8HicwoQ/j/5ZtyVokKbo4jOOkt7smtl49tKTWFKAtxo97HEFzIeMXjn90z7aa6VPftJYU9gMbhkp2WF9BGDN3SfUpOlwhBrp9NFUbDxq1KobAvGIPKdZ/MnU6mt14+UNIVSnJPQol6cr6jbNFFOSlG5kAftomHrMx0qQm7r1LNVNikDCTJh3hOZG5COffXwq7xo2BbQEmBmKMAvsLdZ6JyjZFl44FWsvSC7SsbzAdGXUaxNggBse5iV4J8uhc7w/GrHTwQwUFGHkxwAylo2UIdm/zwLoW5RsoMKXC/7E/y415lgrmJJ96lL+MtrhNAyZo4T51DwHHvTI+IXvURuv0D3Y3ITABx70yPiF71Ebr9A92NyEwAce9Mj4he9RG6/QPdjchMAHHvTI+IXvURuv0D3Y3ITABx70yPiF71Ebr9A92NyEwJeV92zbp/3mKgQ4q/UgZQCTPMzuM2g3m6AegJm5UET0GpVVnrQBj9O87rRqPvkPOIdDkJmdcnbUka8fqOzP/vhYHb7eomNPpHUuwDGpT6kgGG+FKjCmFg3RgZvTYq1ICC1S+eWA73qdQMh8/zRCk6xIqmg2I+l5yxO2E6p2ZtOgUg9Q9LiSTbOPxbI21f8L+GGp53Xr85D9alaTszXD+KRbZnA8KBUgw88+icMw4Swkmbzm368+/f6eq/pliGPc4D90gFzwG3jHzEyIebmZpVBe2TlCKd9lDLMlgZij3f/MFFg5YTvGF7hKrUjvHQxPxCRQLuy4+tyk0e3rgGSBLPgt3ebMuSeG2KFG11+d/P60ns3nugjij3kfZilBA+4dLKCCYMUOJ5czZSn9BXTohjhpuyRcT+DGrqCfW5YyT+78KnhiTwJsU4vZlHh/vB2h5DIMkz4w52e4e/thvxjg7LiYgA/sLtLyw+w0MFVyK8YoakBbiTs0mktxkuk19V4vXB8cVEYpq2pyh70tpE8tLnhdQyLNhME84yk9S7n7bUyIAodehuIObiCw6FFXpZZbJBE15FPDs+c7vPk0P7mpwTxC3+8ofvq5S6SzSk44FI6UOew62K/QRlM6/MobYJzv8GKxsdqHyvHvW4pzcJMCnjgqx9HFEYH2XBtEbyyeAk/sDhUhkZZe3qoVA5u6KSI9zB5nZwZRcpYOWENS3wDXcMCLuePPIeFrHxmINMQiJOpoOTiF3ADS2EfZAfsO1plj9CUqezlaCpBFuHz0uaO9NLBVDJwZ/38aDVUf5IAy6sbMC16GEo2Sv9M4a2mkIB5YoKk9idctZ3S+yA8vxebmHtSaLbM+dNGNPYyFfY6PZ22AOHTX7uJY2CC2awGLEnY9/EIs3Pf8hpNlJc99hWAbokChM2plEvfT4vNTOdmLefTUf8mcwgR/J5HXOYrDrWgv8I6Vj10gqz3WBuLgh49332wJ0xdBOgZJECCbqmlRatygSV+2iKMygKYzgHfZMXgkbJKp8E+bq9BNDe9ts88E45ibhTqM/wwKrXEDwgPL0H3QkXeINT5g0v2+Uk9VApFCmDyridXgRkNjWLTLtoXVxBydgnIoIA5k99CA3XRPbR/wRmuQ1jykj1flctSRdh1TTBvp8QiMAiQNCJ9HUKjSr8wLq+J7VrzJH5oRlY6UuA5smwxeVEzrerub4+0eWy+AoEtUQ12Yt/p3kbgov7NkrkQp09lgP1gMOpQMg1PXId58eN1KLs/o6il4p/BMoi0bzHMiF1GjoBXy6hDL/0nGD2CLdbcSpw22qK+VnOdNXAtko3J4tDXEN8Zy4YrbbXEAAF+ikWGEACRtv1zJb9akYYhWBIQ0ZeLoCKDnn8agq3IcbWLHZDPjg7kqe6UXgesN9Ej2n3oH+aIw7d9fwXk3maBN1YexT7oMvkVLkmPve70UDlZRAu3UPOhcHKAaQQ2FwEapQnTkRYQau5FlxOgah+SjU/34rda06vDLXS2twxlIJaBbSdyuxvlPTBmJKzPSbELezZ5TBKHBpfJtlRjQQYAqHaY2lSLcVqGMDYVaUNrYC5OTyXBrrW4tWchd58Aj6IhiTLxmEc0MixozWwXY/3RJdJLmN0CQCyH16Mwgi6KIWj4fW9owDhB904MUqdZMWF3DdO2jxmtnr/SdYKkdSXwvIFqtFjEi+R0krMSn/E6wZgJ1tJfwyo1rSvFLOXl7gFmCgSyWJu32T7+GFrwoguAM14fPJjsB6EgEQCZyNAe0k8EIhi7nQ7jmD4x2YwupKJV7mhuGSA8Qsn/syOJr3egcwSuEJ4aehW5f97wvEVaMExpxwRDF2IHxCuZS/XTxFGkxW6S/42b87mmkAS4GGMh7VxBdMwBUXtPe/ULqXV6sK7kAo5vJV8X/ikOZzHBGlHUuHsw1mVIk8c9RHqdO69QoyU2C2e/MPtxCfFTOUAxsUrj0jnVyNydwoJ4Smt71UIJ+1zx6JwnwPObUH4xDnnyCyqDC1iHIrVweGdMrPKEkZi+rJrWIFtSmut4ZbdGjnEwlm5IDn3NW7I/VPSpWbpQMvDfaBzfDf8fqGc3iMrUka0iske6U1khYE4N/WfmbUHpOClNk+m2K/P9pk4dXGHCApIx8XEW6LdO/DaGtKh98MFeueNmXDIhSLUFjQ8zMSBfgS5KlN3Sm7ZF3RV5AzeCKt+YgiSZP67yfcVRGpy0EdDmSroFtxb1w8+R1M+5PaDXLE4aptUGOW4cMU2RKBgAYs8UAJGHGK7SYK7uvOc2UWqN/cRjrE1hZlUHUEb9R/A3rS8IjkRGVayIgijrDJhAJFGoy1dBZlAgRBLrwF0bkB70HD7QhgwOGwoqwVd2zDD1LCd/IofT+bac7wnQmS7QxMCgUkUNe7dypdF/zsvJAFxpJmHFu84+H5e4Z1FtHhIIyu7GsS2xEuESHFZ84DTBF88d4pCs1fCnHu1sM77cQPjDavOOVrNCzNgPF0SFy3DDxemgEUsDACAPheRVTmcSIiYXUppU6lP/li9wvDhi8Ake7FtuSPq0nDkq/a1qwbGzk2GL0b/R9ggur8QlS8SSX7RH0LdQ6uynDnuAC20kgBInaxgI2sGq29QBP1ANugKXethmocw2OhGZ18ab6aRA2LgQcBOhnfwV1z3xTphjgTUfKFDAIi0rXTrLrtevJZEEJtIZGkKD3c7ItHAMEWGAluS3E+UxRvUajTZp8kd4AnNo0TlvaTd4NgzIDIxNoZCALjYYLoWIFTDY3Y7264WBq4Gs0ny9WtlUbBm3k8EL0FqgjgxuL4nXMEASj3ujUCELhgNK63iqmREEWcjl1Xvgc5KlUYzQfybUXYRE/a8FYcMcHlRd6YVNCpbEu4as3rC6pnp+gSJJaKwqtcgFFbqB9mcReFvh3bhleBCVsBknUQ5Ggp1qVybfaiZnARL/+LC+HO64YHsIVMeOWBirN+SiVg8xATYp+iZCkgnbNfwSUp08gEznFcZWRUwq4D9804AbyiLzHn4ZiOXTuUi2JznhGEWBCfYPUZB5lghtrCvDAO2aZeEl+Rkjca3WHV3U9BCVyRjXpEOha/nZ/DstS5YCavDsGccoVvGQAvRvrVujwfG+U8ETDCbToqVAyF+KtlGku+e62BIl6tay7F+Ro53CRLc1xXxC6XCXAnV8wCxagnZDJH488hLOjXkFgJYE1eFXh4+S1caBdMBrNT7/UK9gcji6qG+Krdytw1FTBUWn4hlwfGgB1M21IavV+Y3qvdFRlQ7BIdcGo+DZVfDlhHQA0TKDnIOKiGSL72IcP8iMEZ8qchGSym2y+UGVRKjP2TFB7gNBqcHDJ/QZHA5Q4lQQkA7s7/veV0nGOV/TG4+vQTGWzCGaFFPSZw5p8HjC6XHNlGZgEhHx3C8CsCZ/d7WgES+CZboNUrg3Cvc9ZEB5wSno58wHTk48r7C1hTk79xCGdBnH7eSc6C5w8MVY1ZMh7abi1L6A7g4y6MMIYyoh4l127r7zEl3N9Ig0o2007xC+kTxRSE6kH4ZdeS+cJb0R2Az0NduhAAbFwzAjiSSxEg+9S/w3GTLHRzSbMjvztOJTK9aHGAN0gHSz6/GWnolQckpIgpxC2mfFFWwOOJp3IR9agQKwqvatJl7S5n+MrjJe/MPtDTcBOZjxrsYoksrw4piXfH747ibgzBLxQTVAwnjSCxvVQeF0xrF6upb7X1Ag9XIIL3D98s9GZDkLdKlxZm7WD5anljQV8tu71qVx8oTUpFllQFdH1R6jbDsx0XKJmz2nT1dtiHKtKWeS7kzyJGOLyKpebzB8H6/BubrHAJLGL+GpSiY7IEx7GBwU9IIQ3NOG0Uaqw5xemYPhumpwXC84AVhQVWuczm6JENvdACMORhxIqsbY8xjPx8SdioD4RpJkwfbpIfkbW8LK0vXiY72o/kqdLlX2KHFrw9ASwZlG2a4fu0uZRUiwJga8c3IdUymsdaBxRLfNMZrzX1Mgmz7B5wgAhKQNISotGQbTsMKmdkru91gR5sGvcS4tnKGAqVDGxKQca56rLXoBNa8QQt7jWo588TzDJr9maC8+QZk323IFXtWAH2touPuQjMCEQ+axAN47ND3khOaM5twQOHTFRkwJTzuRt80tuoFNYOloJAB6SI5dh1BEQA+x1FAtLZ+oFJhjRCtrSeHQzEWiLQ+fMvEMB2SSDpN/8tIiMBo1XWikzxM9j0tqd7q9M1G/oNiLDQdbIfr5lvn9xcLilI/+QATyxFrMVvAMJpTm0iLlIqLC2UimAQRsa2GgWaIyB/b+MHIRBVCw7OIHAxpxXz33uC5sB5tj6EwNuAZr4UnbuSBdyNo8hr2gcIOyQjJ6HUhYxvGLufBPuxC/LeiiYLmYCHGX7YUfiHvXAeTZ8QMH2K4sxoU7qPGV36DntDHd2WsqXiYBNP0Mqli0lL5RDAMi0HSZLRUKBTpatHqHwb7lHt+akEF6FfOYMLViyFHCGMn/LCY+XcQlhZEOxs+A5Dfz3T28lo7QknlWWmZ2Qf2CEEOOPU/hL7qChvI8myBZWEsvE/frvDR3k0xcm/eAh0MxuU2iwvenaHbN/pFZ4CRAPEwioYoMAgjXZ+L2v7EM40a20d/HMzHgefhsiXlSKUIk17uk1tbezW8AZp+lYCPyt40k1d+hAgBCdyCJCUDmL29WtDSuR00jh6w16+MSRXtMje//yWeksrUMRPn1wJqvTNgQ59HGUq5MQF7HgNDEZx7PKkXHHuMdTjj5PQOSjse2Ovmf6PKX1wQ+U35tol2mp2tL70rmoOsMmtZk1sIzYtA33g8JhBlQrZM9gikhZQFa8Vk56Qd/o/Qz/Dv5wIz8otrkFab/hcrC5vgdAsEHvqtbPjzvPyRq2zD1a6uhZyLa6nHViID7h9PMyNjC4gtzx24PCfUQp+pQEcn8nDH84SzTdj0XtXjFFvj1bHlCdls40kJ+CpVNJYRz3kR3MD5YPZImIki9AerEp8Qhr8GrGZmpWjQwDDnDz9OAHFjQrWqJLnwcD81F2dvrsHMKAYS/zv/kNCWvfyXwTZRbyQDxUQ+CFmAmrfFu8pS8rd6wzTceuq6tB6RsfkrXn3ueET/eUc+4LlV3VunzlH8QH7AbVW7+cCIhA/SlyCHwBttBeCSAi1q+qwpe1x59SUICspkI/MNI7a1s8p86xWVj+LCCgvSjmNRQv9i8rV4OoxnAYBAgdDSucxYNSqHK1uL9oZbqFG91dLurillXvseJMIJwpwTRnpE7jOSsRlqeDx8iPLR4zXDhRgIUKxljn0y5wqPO46jaQZbLOpkdlBBAodIm8AAqyjO1QyRU/7MGrUxwsJzNeDrkZZ3WfQNqpFsT4XwlaijXUMZPrl9E++XSmIICi6rdJi8PgIY+4BGYBvMAOaK5WLQ3x33X8efxZCak0m4FJSadPnSc4vpBHtDb0LI89QDL0hzog3rcXsOp30Kgm8ylXdhSNS80GSKJKqJ8oJaW5FK7t9WKHFp2zYww0pHW6xrSbkekolRKkwhsKq7BlbICE/8fhGqfzbHr6smMgL2n9/bdFutFYYEUzVfoHGICixVOO2DKLbaBE7YY3kAg3xBJIvnVnsOm+2QkVqPhkjb5CSW5QHM2WimTzHMjOjIqAc34IWprIhD9QEQKVIpxDFhJIlu2Bl5cbRySE4uz0aj8wtjaVrp6k2IUfrD6OpDthwCCyio1z/1CnS1hq4exk+I8bBtnWJK+k6BE85U5geArEx3LgyohVLPhkI26IYCewSTptHJgf/QxfDjJ1hwB9WWuibYLcmSygbxWm3piwmTGun+vsOhkL0WqdjcV9VC9CODc84LTBGzF1KoApEPCAyLB6XlE74j3LGh8waSsoOtNFHYvKfEP+v5Be6OTV5Hw2ri3umcQ8o9kse+vEIiAJDjcbImLeUs9xGq/nxxkoBFS4eDazuQGe4qJmzNcZ+JWunpRXfluOzlYHdEeRvjBPw7vCaynbk4WnSWcf1v+EV2fSjYUvXR9y7KDNyoqlPHxyHVNeTRGl80SUKSo6ZwR/V1RsgpDHDlp8n7RmG8O0eEVxbTQAJx89Y9OERYobAJeOSXzueqGlCgahjLrartwiMCfJouI8tGv0xzrWY4p8ROlZpLWAvlMMVloVWBZJOFyapvM9yqyXQ6kTD0fOlXhWtm53I4yt3ggRLJr0rq+wKdI5UqUjKhcDIcgmncs8tFPw2r7e4eGvnLgGRydARzgCSHdpDXwnnqe6XM+AOwCsgJ0UjQolruwJlu+YEvZEnAtTPXsxpzl7epO04+o6KZgvlJ8rKg5BB6+rNzYLYH38DdyZLtfJ/RZPc3+yzyVQuFixaIjLm6sud6DzWD6hT2xNJAJcrcnJjFcI6nu1rWqwbJvmbV1CsXRrOqxgLbjDzF2rj1DyqHW8MOrQPvWtiSw9LXzqsmfTV3fJ6BSRuKXQSPxcqjM16wk6zlwumCyhOIcdXGvE6n9xeZd1Nlm4OBgAPgaBfHpaBi16yJznCjJUozDljpXCxO0DDO0yw4vYZBC1wkPXDXFs8OxyhOiP6PwJdJoKQRku3AS0KwwHd6l0FWByypqWOVG1wy4Y1QBHNGPVJOWqiK/GRHVj/jUDSDgaETsRJLSlA2J4oRiEmOe8cCP1K8pmMvS10arla0fcpDT/ywVbutiJ7vsAE5wsxPR3P2o53D9+7sNTcPcAuatcOnKIQ/ieLn6Whrbg7nJ5OEa1vi1b6EZOm9w7PgRNqTwN8F/jbUB0i7YnomJYxzNsRX8M1LeCBXZ1o2HjmhKWXIqoGa0MmTCb7/jUm0YBJVh8+xdOJB+mTJBquMqlbgN0DV/GUG0r+hyeYvzIafiyUFWH6FR8frbPDqrlpYNw0wg4QExxC5kk5Fh8IZTSsv4odNu1oOv27oDr+Y0T52fTAC5vHLA+/GdgVb8Q7zjUhGQ/9Nva0SQsHwSwhXM5RYOoa0kNkUHLlt9JJO6WvQjCfCfhujVKlAA1l+OqY/Bf4RBjb+vOXNGqmMkYDYmh7nGQN+eU/Ak0vb5bs2R26q3ZPDAsU7nhTfp9LHSPMbuybdAcotx2ZIfjlAncyF7R+JAUKqFPGXG4SF8B9G6a1vc67I4fH6i0qJOdm+8+aum8PPSIpBr5PpFXOII7NCedGMg4WLlfH/aqkpBCrvxxPctfyHX8ktW8Sn1RM5Lg8HpraUhu4h8/M7ovb8x7yD5/BGv4YDCc0AvJb5vswF/OTpjuDKHLvE1Z7dFqS32ZkWYJdfRpNuOBufEp6Km3VIV62Td0EWHd0h7YgSnno0xhQixivAki5Q2g05eMTG7b8ybhf6gsNBSOxxD9/qSfeVQ1XoRQIB9G2UfyRy9Jz9zR6/YRRCiRmmBjpHAdBBzVJO3+jlAdoJ/rEXw6G+NH2Ut4K5csXCIg8uyUnQtnAMw3INC0oBUY+EKJMu7topo32kRnedwvTaHBQMkZcLcvtnzLY9SMfHzBGd8baPu6YwUoHQv3mAlBENfHst/10tU1omsM+hiFsggoiGr86U/RP4vYYU9QnIX8Ul27kXG5DvXESQRIyCnzp3XuEJ8RbfywCE5U1XSRkrrFJEMOVz0zqnGijQ9waYB2m/gc7u49ePCJ4n5L/Cn7ztOyfTJPwAElwjH0kcRCXNhB3GyVpGO9KMWfvgkcXn4Mtfvx+B1BcGWvDOeXpI040/v+ierTD7heDDFb/WgaNBUanmOJVKYeVaFSXj4oo7kwhl+lzGUx0r2xPkx6HD+wJl8a1G0zP+///kKMy2iNcs9B3q6hkp5Qb0VfsnZ0e3J6VDcP8u2pY4IFGqRoFCU5Xco9xv7VnrPbLp0zaTAb/Ahdmq7PfbaZuCmfTDQMmhgcXmyHr9SBR6HgOOvQ2B9pwwYRhwVBnq81PtsOKfBgXjcN/SWmTug/3Eofey8wAJS2bYLMe7VwuaPV3tggKB39KX16AzWpBQrSMSpIwVhYR0oQ5MS2S9VXsnnhSThscZSxWxYB5/SmQWicWz/RJJzLOCMmdMUwCF6U8DFJq2Rl9QZD8dpRMA8jGsl4DjfgalrT+Ho/OExgKXe1Phsm+EtK/bpnlzypjE9Yek3HgAgWwgKl2DeZk8YIc36LEAA8NVI4IqW3mHJxf7zxaFCuwJm4qxacpVva+YBu9U8/lUBPgk5b+Z648K0kFTjWLzKEH/bKcuLu4K8fmtgZtltHEHfCE6bN+x3SkxKjuYtjywRlv1lJwtI417CcpS95RhMAa/Ct9J2Acgb71LibFdzU0HKLMeCN6kRbWLiaY16SMrhUmT4wVuSWme77MOo4EDvoa/yLIwLBPm46KtyJEhitkDF+smYyj4sKJXEoT2+a05RGQPMiYbrgdUvvE6JSa8x4CyMRDCzWPpFt/c10NPHAWE+oNLLz1BSWGagUVxX6ptwK2ITgXMnU0S+O4N2sXiYMArWu5wSJMKar4oi+HHs5HFdFf4KMqWKCFQ9QhLWSVLBF89dixmY/7zBSWzBmlVa8fageo4Dj4eq04DtEwSbw6JXomNFfMwfVQ6WP/IRJVMhLOTLjg7087o6CGAz9fLNYg4QXxukfQzMEW6TScCxXJD4srsfGHTQfDhTPuYkHx3RDJu34h0DqC4vAvmzwh52cGefqp70to+chQMJDxXM/SEILpAvWwmtQNANWYMGJq3hWJskH5L5ZltiYNizrb/TERTvMvkEcjnxzXjggLESudIgvIBzq7TJU8OPQVmZ6QfwhYrCJhyzExvyaANUZNomoXIOu7epTBE75qG0LwyxsoB502Ev0LOZDWveQDeBPOkByZu0xKblMTHSeDBhCKLSEDqnq9Dsr2uWIMRzZjCbj6CmIXugPtqyBsfPvcjdjqJgAfbjBwJo5C+6k/rpdGumUJDtunS5si/dN6Io3N2xGtGe7Thwgq3HNFM9RVy8kKSg/koj2aYnm4r4KlVQ+nPbEIXkdQza1JhRMfvi4V8LtQJeK8ITo+4JOnq4d6WDRCYwu8kEadp/bTs3ZxKoF0aIESUOKmr98nbVCyrL/A63W/JWTqKzS0+LYulohb+Y1kaR6yJy6u5A8QJws6ci2lWdUCHXVhm1HtMN97KNET4oZoFk2BbYpJmmIuK+7+4GGHdwezqtVDWsFZGUox6LTT53kLcuXHs9t3nzLo/x55Pab/GyNkPiIl3pNkJ3KP4LmghhAls2TqaR14khaJ8J1H1eoZvONooOXBv+ERJJ2PKT3hDJ0LX8uRpaNiTN1RFLEvhx/rHsAjMcNIp8aa71JpQl0U3LbPuSEXeq8ICHDZn1CgEB5lt6YSfrvp+PyatZhZtxu3wVoV+75zXblPOqo5xGQpxKwdlAQKRnG8vIzSN7j0KYj382tlKgHolxgQV849Eww9pxf0Vq155NGvycHcBeMlbqIb8USPVeHGMhOvxA0MFKHi3jWpD3kGoyWCKABiDAInwjUCAUj8n6tyob5vUTYiTudWl86EF2dtS6iF4sMICG+eT7qG/bcpL8IG+UVQNxgUfoaGAAr8mxLXGo7CmMsGgbvNgR1+OjwKsKMrl7OLCGc/Uv5aJNhzWCroTWTm1yEn1wyUGE6bT+TJFSsMOa4VedRBk0/B1Wa5Fexe9JIdGFjoSCcny0b9/+LbgjNIEAVJJwMg0qwaDg1v1wBUMNUXdqZrNH65tfAgiDVSKeVtGKqAhkYaFKnKFFNNBQEYOg7u+XEFGwPYkAUD/ivVywAOFFBCPgQ2Awv0HzC6Bi/FKfkvPZzHLVW5dPvPOYTYWwfB3imkfVgmrKLbf0xFRmYMuoZBQjAD7kyVz8s4+fFaFRkyPgjSRVDLCbEYp5YyZgB4HWOh3Zh3nwu88hJUH/cEY7IBlRF9Ac/jwQmiBzChHEIIvRVgees50lI74hU/bRnpX/hRzR8XW5lUek5BzBgG5/c97Qz/NGh6I5yVfZhbGACHiR8cRbElcU3hCvh0ux9YZWzsU+MTjKeGSg7rQGMaV/vHwAKpcncR7UBemh3lKennjwkp5oRZpHIkj/k7Vxdg2CDcLioWgacpy4RevT4C5h2zOBWNXfR1nI1rPpXJAttjD8DXTx5k2gfl97baIATyiH5xBFJXctHj+0EXSt4T+BnhCvU0dIgwhjZeSvyUANZi6SLOxGiuBcxOWTg34ByYhf9zcssNTcWzAsDUbSAdOtVSAZIY5YzPcreb54WZEXPHWdbHqzt1YfPWiTutsAnO28+EsQ6mxbDbqcIS3YwCVmNSxSefNRffGoOwbjoFIFs8Ys/x54lppvNkBhAwoiHtepQQFOo1+bi03mloYHwbjttoKgT+453AYWvBpohNBCAhG0OjUotXcHsfADesVhCL7T9oHa15RzRkZdXR6PsI+FqWExp/hf8ATf120JqzCgxXAbuobeCsbLemxjMhniCr8d8iECH5HhI/rcapJXwfu78S2TrxCCB+12awsN5CJqB+jiah5HPL8J6YbvZBdQcElTa9uCDx33iDkwuXGY4Ax3/NNPp4vpd8KpJkXo0CBLU2xuqSHaG9ahbMnvhALQ7kcB6s0pHkXxChuquymd8XFI2VrvsARV1R3MZEW0NHFgnNYUPvVTaFq8cEQHhjOQHKPv91wpl9wsz/JyYyk8oKn45t1nF5i8r6uA+hosP4DEVWiBJuw3HS6D2hIs5piicywgEMt0Au1bOzyI6qbvcfrH85X0XARseawPp5VvwqJSFdnP3Wveo5dGUTojPaHxiCk/Ph1FZ4dxECEqQLUpMX7ZvGqAZ4Q19t+20GAGaKJUt8zrCRj2ezn4reMvI9CgnzPpoqP5mGCwefFAs8fhoqQiXDXyLDsYu6YFza2WJHECE8lg7PMfd5IFjJLA+M3xvXhbp+40Fah0WQVBiWH0Yewbr/Zr3Id5gBdjJJLXVuJGnEacNquG4z5Y2KOZSQ1yOrIjl+95lZaPI+6ocbG9kZTH+OVn3FIFLtfj38pqH4GlDPjeWtQiTx2Hm5GtWA/QRTguPqySbI9uzb3zg5SpwKvZn2TkDb9AnkiQBcwXOAEIHcT5sOj6xAvJca4Sos8wYTCaPC6EDI95lOPT5Kvh8BPYrMDUuk/pfN7fJ+Ab05Ev55669yC+ySj0xR+4J9Aymxk4DXazmYmqadv4+bfisaoGl3WTgiqZDNPl891jnADGAaM1yQdlzxu1uNRR/GJwUfWQt/YxUU5Sr8kICVK9YMnMcikCe8rm5LE0IPiptAHSyk/uCcKSe/SK+u0N1IgBA+8NruKafXYXCp6XNJs5IQaPyaMEkd++YgrVOREARrJE/2TRrkxfEohtJPiKpR/QrGEcbg3k4X/TUGN9dKrjcGnPmrN5GsOBwyQgue3+bGAH8fnzW2mcaG1PWPOBIhZgLOR+KxO6rHgdkWRbxTYLIm3gE9GMkuamfDUoz/Ve1EDQxs2EYTVTx81fWnOprdMQ9LWagy+HMkaok9tld1w1gToNCIqNIDoCzGHl2Ox/oRD+W+iYCa0oNNfzer75Q1MQW6V8Txtn6j6A5L0K+2oCsZrEd1NqCvKHucyRry2lzxEvzqJB2uNOdCPzK7DwffCiYbDgQddXnvcaDefT51L2sgC2qJW129Orv6asshhKbCHs9L/KaBugLSzyKzUriaZQsBGdR8TQrvnGwwyFmM1ZQLTxxlcWXO9IriOGj+8Ob8AJA5IgUrTOFeYrxDO9S0jiPsHjCUeYR/3fXf1PvbwusZbToFNtvyXPUWq5ZTl+QNHKGSbeQfO/E41Ceks4qRhxP9+SFYiV95sUjqkzE1R4cCtOsangM+mQm1sdP1P/BaIRksowOExKgkzqXtJ2viaAKAj5UzR+1k6cSC8mEE7BUbLERIHfAlQ0ZAnXWalqFHE/7hdZ7ZoTOKesAxn9z5kQk7VIYI8/Da2OjSnPODQXoadILXY7l6zNgxBR/kfty8AAWvL0ytKKEaL6MBJpiAeB1lEmuMZcwuVK2V5S86hhMgJnNuJ3r11pzxpHKiIUT2IxJEGk6jrtzy9z1ObH38cg8F6XJnXYRo0gpm1FkzzMcmXUpH+60vLFUvmS83K1fkIoHhG+P9GC0GwNRUqKflaw3ap6YZpheyjbrBS2k5JOwCCs3eEmCs8iTJl+4AjuxbBuPwVDpn4TeS+IjcRSie9QQ6AuJKx4WOrT1V7on2PgEe5q1QtgbOdkUSU1GO9qctAtl6dhdDS90IBJcuStphYRxB03V8gI1M+CApazBF76cJIH7vCWj2JTwvocypPmqYGbrDwlF0jqV/hv9SVypFixTTkihSlv/WLpG3XNFBpWAlhygJxrsRAHfOLslVbIWqHl4TWEnhc+cmvXd4QKaxnB7Se1XRJR5hkMU+z4IEtx4coZJt5B878TjUJ6SzipGHE/35IViJX3mxSOqTMTVHhwK06xqeAz6ZCbWx0/U/8FohGSyjA4TEqCTOpe0na+JoAoCPlTNH7WTpxILyYQTsFRssREgd8CVDRkCddZqWoUcT/uF1ntmhM4p6wDGf3PmRCTtUhgjz8NrY6NKc84NBehp0gtdjuXrM2DEFH+R+3LwABa8vTK0ooRovowEmmIB4HWUSa4xlzC5UrZXlLzqGEyAmc24nevXWnPGkcqIhRPYjEkQaTqOu3PL3PU5sffxyDwXpcmddhGjSCmbUWTPMxyZdSkf7rS8sVS+ZLzcrV+QJWa0qVU7+FhOR+c4PxwdUEWLca9vY5RyjvLaHWGXITh/5tK5B87bZ2aGDpcukdd8GZudZ/TmEFNwROPW/FFttBlTqTD3up86O2iZxwF3VJSHOdPqhSKtGbCXsrEpfcT0kH6bUhjOUMaNXErZBcuMzIqulrvrXkD7BMdHgUBv41wCDptKIfCBqWcYXbB/WUQ8oUTbp9EW1GkseQSwEBoWPAPU7yJXozrkN95S/O5lYKQBsKqtb0Is0JlE+mHCL+64WmtcMnhMJ5l/WTbLuzgJoAfeYrKymkSOHiD0ilhrKSR34TKfHaeDktQxLMsbhfJoqMNRNcV31HWYZN26OmUu+DJ7puM2FV6xIXyXW4wUC2BKQ/+30JILbgkl1x4UUy0QFaybqOvQ6xPQjdsRGEbVTBBc8hIlQJn3P9R4Hyiwi5A9C1oF1qCQ0yXimQBJveZYmZ8xp4PQAtYNJBQxvalpuGlAYSz54pSRyBixSM5Ox0CF3txg8wOgCqtCUe1Sa9WwJhTHNLTD6FNTCRB77WBXqAyPtS+0ggzGaGpbRXoqbdg81ExEBAFFLCyarIsbKD8IDQ/zHZZ2E1CH3BbPGOLzQDbxhDH9SK4ebsCxrxJNDdBpHjCMzmcTk/rc3wJQkGqoUUwGXaoDjD4nLZB4aWAX9CU7XUaP2OiiqgJFUbN21YhMea5NpWfz7x2UBqeGyqNgNPYTFh9pY+AyZEvLjOJ0+H+CLalJkGUMg8nYWKqmaAgkrkeQp5tT494IplPP6jccceqJHG5w5WCeShoO1gKEnCOMTF2IFZoktdslvv3oLbiaqhUtFNKosU8R1IGsOXNoFNMSBlBtAhwmEvM1dG5dtHmiTMq+lhAQlEDdwoE0yMiMBL5Gwj63aimWUjRcygeAA7qszu+o2+O/LhFBeqbswG9dbrCUNIi21C5cb6jtnHhZ8j/ovKr6ewPLiorwNBjEH1Lp2/R2szMuGt/MhsbTBDVQ03luDtPdzgNayu46nmR/CFBFfKFF2kzOgElEKoFcNERF5Xx1XAHBSMmh/piF7I+cRz6pJoUXPAKkkJTs+hgvGgQE/sI6c76A7f2JSxj0dGXFYKPY9+NqO+PXJJTSrBPHhkJrPPsZPo1gnv2sdoA1gMqF0CzrZXWhJQ1/XiAYWujTPZn9iwmY9ofCVdUoUA0ZAj6ySsPCC5WtGBCF7pQmKEyUdJy4b9ruU2Y3g/3ghHpszAljKkDDzAuzJIuFbH6HssP9dKlcDshsbBykqVBipGrjuWTO0fiHvmRUKnVoIlnsjVYfETQv2TE4NSCJiC89pBLw7ajlt0UapIwSSTgEACsc0biPtIh1LZAEuvacfQuQvTym3F7cduGeDZX7kB8G2wZzOjGaiWwEw4ihZrg/r+ympYnQQ5vfaJS8e8agYL4XREPFWE7zExmLOz5KXFOYNL7j6jhr6F2+EM/PDehaWSvOFPjprqk906tBFshwMbrZXzvElkoG8xUjCN6w3EOW3ZJfi0tKCZJS9Kg+s0A4/CPK0MQJceGa2DnbpnKImGsV4pm4iC92iUj3fvmN1FOGx5l52ePtFXfgjYE0fQRqdFqLlp+lNlcgSTdjEickYsxY7dQa5uNK6qv5z0q11Ki7tysMXVxIgHY8TeRXd5CcNX4rh8vmCR6ykRSZKC+gfyf7c1CGIB39Hfj5dqKpcJefB27jQ/2Lu5cEWcPbEMAuAyWHtp2Kge0Utl29dZSkITr6SyLdF2dPYNsaVCx+8GWa3jijPpyCF1UHXYKxnHB2vxQ3OSLVS/ZeQYKpm+2MDHNOXHZNFaW7+dy/+MOfaDc+3VkwXTbJBIfblx/6JGRGoY6r2mZpv7qBkiNbBlScAiPM46hxh7htYcLXcO+5BFrftUM3x4kvR2pQTt4BIGyD8QviO/y3597CWTBar3D8BbpUb6v+d4WEKpfTqud2GGCxjYhGdDMsjQhF82cb7uASBAkfQEV2mM4SaUT3xCoUcvb1gWUq5Ch0d7D/tUz35Gmixk1kmKs1F5hHQvgOr3A4YgXWWeYI8j3ZakUjOfYwm/OOrMugpwrIzQRkpxG9ACmOYVfg9LB/DLvwJAt4QUhJ9yZRLxfBNeEY31AwJgT4gSuQMGe9qgUt3Uv0lAEb2Fb9RidPyKNqdK8vPCd2ZmQ3GPFxc+lSBuSlR9md1IT8VbfyaNXpnf3SbcHXKu44tFm3iQ4h2p7cUce9m54zlQRkP99xhcKkoRVLvnRial/UZFgrFJXDtKuNf7kF6OR6/KO8agY81oClFaHPKaAJBXBZEssjp8bm/ga0ub00FiYwbdHyck442fP8NCb1Fp+4cJB9D6vmbSNgG7F5y+1aCVQjOdwck6juDH8evUrfe1VUhueaqPUZqSGpJ+LW2CUldFvd7BkRkoe7rdk5gI8DcLRBLmUD8r/6ioC/W0Cz+dGEUtObh8ODYI2NYLzE2dWo0DhwCtwx/38H5XoLqpKHHNSOxCUcZ21y0QE2yKJwgHWIopDEb29xbxbOD34Adhi2qFv21FSB8R8Vc1nOlIp6SEwqA1E8VO88dH9ZrgSTQVrYiwuSVE7kXOpniVfut0MX4F+Ypg5hiutjVHtxZNE0OwAYL3tB0AoGe+q3vHGIgzS4I9R6E5rLHQeh0ToIEtnq9HsYnuI8hMnd7GbUB/6VEPCKBrpcvgSX2UtuRkISEzhoeyWVZmzoKUccq0gwxFCPCDCGEraFeAUkE3mosM8zokiGdaJHqZNYSFFWG48ON1EUOf/M830sMMYDPpGa4YBYeJFzZ5c/p84yjiN9pRG1/JhJvc37IHBWje3q8LrUkQwQK33vazWMoZA/FA2rstAvgG3ItIJ6q71LmFsbX33d2PwLGJLLeeovGZQupr7dJNNwPWL6DLEJxumJ/yqnZQTLdBDZCYqbfmBL/ZRm9rfcLHghUnDbOGdt+Z8MU3LDwzA8TL60m0ASftOWLbt5rzmXsCRdJFax0yC7XxBGNYRYNDBi4RVlpy3LJ1naPtEFFQ9ANpKLDhAP7L2uURQwtDZcHB433ulJEThmijcXjjl8KCxOUcioyJIzAGXe+J6QQvrYbdP2M1U+fJMLp3jAe+TXNKGr/8ShiGqk7q6DcKcygrQAMNW/XE7b9RS82SG+img0pgA8+PNk9CV6OFBnTaAilE5k1JSMR5JSBemRpbYFzrA+Stc4+1+GvEL1F1aRNsqomZy2RftL0cA8inEzKsDv7JuXU4SX/zcPWi2Ctr4juKyAvedZTxoubsMhZD/awEHglXgOEf43vpDw9Ld7UoFOaAsFrYYzr9pakflGB+KGdpAW3VPZnFKObPPJucGMswaoDVT5ES+Z5LU2l4tLSYT7JKU3ZJ68jcDyyUAq7B5cmtg9yGaalDWWV2GJGYgesW6MfDFOnfzVMhUPVgrhi+D0CB88KYkBGfd2HwtKc7m9RnRZUXDnKZJBrzG5jNIpJY/YLmkX8Mnvyu1dtZZx8pWGXIR7NKxJwjljphyMBUb7aFwKOLY/mqqzQ6dHlzkNQ7NIhZln0xBMvRTWtVO1HfrHODTCImcXSZEruaiJcL+S3oB+lC+auTuXLjUDqhT1qoGMpVm4kxdcpI8M7oPJYaPu0FTKiEZN8dDKTlUoQDFWvyA/YoT4UI32rTkF9RaPmW+4IkNPCeiahexhaHLo2pLDPFrtF8TZFbAwIVo/U1/nJ1BmYbEoTFqjg5DqAzYH9xRIBz7Yld8SkFo1aEZy8eApYKLJHl+xBApFcxkJG/jHaciTy48FGS+DdE6J+Sed4vP0bpZRVznL+TGkJXnyKqcv2BCVoDfYijb4AE9a/XogMLyIO5dHsLOys6fFuLDDc/VYbDRsGFYx9fdN5NW/kr4PZFPZOmD5uJii5spCNg5GQOAgTohoXxpYkK0pwthlp+dglInywEz3Z6Jpi0aAUPX/hHkHtZRCh7e4BJPmkaM9DpxOwVybpX5n0hzll64H/pIMUsui2mAykhi8tjg6eH0IjFZ65SFMLod6Tzd/yebyxqSTWad1z8iNGN7wOJ80iK9MNHW3VmArmT5Vb56cw0J5+BEBKwYZKC2u9t7DuAKp+3QH26H+aCM5gsaG2CRnINQgqAc6RzYwgJ+CrM1uWVwILI9tFB+Dfpju6/YLEfz3zCCElAlExYr+LwmTwfUotUd8bxau2j/08ag1YmEB/3LPiI5HyJxyrQvD6MUqTv0ZUMg7IJl4A9zmL7oNpgFSKWIMf9RK2r0IPsPcAqLt9B/vEKguWNE1dQRMdE1BZipukWgVTi3E/EAHwBPm2BKQFSC0iOV+AEUaAKTv5CJbJrBrCDe9/SGzHCjSq6Lz7huU3YCgOpy/ZP6BezzbX4dP4RpoZuieRMaoF+FesNUz8EnR3EWPQBXTXW9YQq0irMBJneiep2RL9/yUnXpQLdK8AfJYETmCh1gBHJJBvPg4P+UQoHqvuUnm1N1KkLHwDJH03QiAnWSW6PcdAcAHpmkzIfGUULzLtMkIx5sFoFdeZ/UbMG2ut7VjYjbNzPzmq8Hzrag5uliDuj9clNPxcH9Phvj8FvZgkiWG7KuatCCCKMOdpKT1Ur546e8EP6XxlnFFvLQ6kDkKEJsmNf6GYLgPemLsmtJECF/HB1ndqblbOP6GIAfl96f9nOAl/ahateswPzwZUlEiAdpYeg7E6FRTcTeENcdh1pjHd3SeK44RvbIGCEkRY3PAYfSYD4KeNtko5bihHa7yJufZBXZCMrAY7SGcbcUou9+jzTSZTrG8WdKPFKLIqG+uXRmcOy3LGS7kIvguCsMfbBn5Vg9+SmONpcC4Njfka3TCmyer0rMdUyBGXBX1bWRKoIIkxoq2jmZX0jB+c36tzfWkP19mRD2gmdDonZfl6ZrEad4fYMHg9o7dxCiepd9lN+2GgCQhYeTkcAgsiEFOdVyNgwY5SAaCqvNkWUAJGAptjWShPLonKwWIqAPn19Q/DE1opGEgvXVl2Zw5ag1z/wnsnHhEmEgFTbNoGJC8d5OFG13Wtf7DaKTnuEwJ35Rno0lLm2VEUDVXWLRkuT9ui7UlhCmVtU95Vi8Ui4L7+IZPJGcMJp30dVwEAGqXU/XfOIMyp6PDCov//Kg6JZo7ljjABg28gbZLekdEkqnZ5UNmePJ+6UhgWKvZkFDWEi9rMcKzXws2a6NcbJgFqBu2YJkd0pYnEONjNPyQpDpTA0iLc52Wtz7WMzPcBHCXW9y3CN3cZYEsA4I6v/yb7ZaRVab71ogE/H1bnpzUbyvGUBR6XyEV1WP6tBuksF+Q8pPo4iMRtEWQlSRJpTwvMaHaciqO0dKWevLDIg+wTxa1R8vz5WlkDlI2iiiLSIs/Y/KOox49OWeN+cNbbLwtWJZTa6M9+T1VUUEKCxzUNclXPkhzc9/eJ0uOyrtLUFyIr04yyi7mb1s35s+CDUSEYwzJCCeGS+6uu5uwZfMwehA+CWCtwKzcRwQkyc8KJAiZLyNlXnIqkJ9AwQFK13QYZ9P7I6E35ddKEHubVj4cmtNuQEW/mzhu0/jiU2NVJFIveT9BIoR55rN3ckDsk0yL7UmNlHNhUXxMkg8J/aXoH/DRg5JLYFdFi8KDy1vmgBayvjdGoJm42MOpDVSy8ZCSKCZptcZiAG7ArQV06uxodpKlePDlbkS5zdPcLkWDIHiI8UpBZK+9hfAu62nCEDiEY1fAh3/oFbtPA9nSM6zkJ2hZ4pITa9XVLIsggfSA/KOAjcDuUGGZA0XeaUK5fshQtnhyKl1NZe/J23WsciY4HnfE/DmAq/QzUIOq1dsr2E3b+tDpoY2CY3Tz/YhpH2hD2NeI6+9aXuZbhJK6TJ0EnfC9SspNJPgiaWrcVSKHTJ//oLn4hVzRwbPrxtgIrbBm4G9/IMwXVanvpigPsM7cYVVohnvyJiIcRmJy4JCITIC951lPGi5uwyFkP9rAQeCVeA4R/je+kPD0t3tSgU5oCwWthjOv2lqR+UYH4oZ2kD5tyIA1ulTz1Fju3TtdXLwiUmKeOHMuNaVMCqXYB1a8i2DLKdjUf876MU/qcXdjwF/sIAbAHuqPTOrSL9l8SSRTX9D1gCgIsdlbxAz+qHZELKFF/aiSqd/tifdH3gTYBAVdWgKvTVYpmJG/AgnBa2gs9zdTVJkmcWHAw0P6rkfQa5XjoStlK1G+IUMdrGgd2GmLkW08Zxe/uOWIMhIK7eh/BpU95X/SKg6FDPq0IjvgdQ2miUIEi3Gaz8XHDUUmUDlT6YttrrlAY5AWk/tniXgfH+zGrbw0gq79z5lVbYScC1YCeS7rNxVXfvhUNr0sYIGJAdAvaUBeisQcIQDvXBibIfov07jT/d8KtxU8XpGwQzdESvpO2n7rrGMrj1JErCqxhMCDJ/cuuU3qLxFDSxCoojUrHHGcV0QEPKvCfbf0OBKqNAPNP7neV+vXKqnvXINLACMsqy/Vb2LwFhtAG1RUfvUX6+6FR8RpeEV3IzHEA6iFa2PLt3AiXxAI6w64zHF6A128loH+Q0a4YMUMQngEUd0KtFrZ7IPS2gbew8AQngTZjDX8dYYkDn1vzgfQXCmqwEzTm9uaHK/XXwwlhlh1VXIViY2cKXQtHzyA3NgEU0yiubXYWCUHjnuCfbpRDFuHr2K2GDHga5pA9s5PbFA3Pt1ZMF02yQSH25cf+iRkRqGOq9pmab+6gZIjWwZUnAIjzOOocYe4bWHC13DvuQRa37VDN8eJL0dqUE7eASBsg/EL4jv8t+fewlkwWq9w/AW6VG+r/neFhCqX06rndhhgsY2IRnQzLI0IRfNnG+7gEgQJH0BFdpjOEmlE98QqFHL29YFlKuQodHew/7VM9+RposZNZJirNReYR0L4Dq9wOGIF1lnmCPI92WpFIzn2MJvzjqzLoKcKyM0EZKcRvQApjmFX4PSwfwy78CQLeEFISfcmUS8XwTXhGN9QMCYE+IErkDBnvaoFLd1L9JQBG9hW/UYnT8ijanSvLzwndmZkNxjxcXPpUgbkpUfZndSE/FW38mjV6Z390m3B1yruOLRZt4kOIdqe3FHHvZueM5UEZD/fcYXCpKEVS750Ympf1GRYKxSVw7SrjX+5BejkevyjvGoGPNaApRWhzymgCQVwWRLLI6fG5v4GtLm9NBYmMG3R8nJOONnz/DQm9RafuHCQfQ+r5m0jYBuxecvtWglUIzncHJOo7gx/Hr1K33tVVIbnmqj1GakhqSfi1tglJXRb3ewZEZKHu63ZOYCPA3C0QS5lA/K/+oqAv1tAs/nRhFLTm4fDg2CNjWC8xNnVqNA4cArcMf9/B+V6C6qShxzUjsQlHGdtctEBNsiicIB1iKKQxG9vcW8Wzg9+AHYYtqhb9tRUgfEfFXNZzpSKekhMKgNRPFTvPHR/Wa4Ek0Fa2IsLklRO5FzqZ4lX7rdDF+BfmKYOYYrrY1R7cWTRNDsAGC97QdAKBnvqt7xxiIM0uCPUehOayx0HodE6CBLZ6vR7GJ7iPITJ3exm1Af+lRDwiga6XL4El9lLbkZCEhM4aHsllWZs6ClHHKtIMMRQjwgwhhK2hXgFJBN5qLDPM6JIJyLh8jjBEt3QsiKDslivqG/6AIn+A+KD32LZ/95ctXyNT0EGQfE0iKKSvb2DGoVwAIPZQeULRK30gF96zgn3QEQ4+MNZB7FkROhnfBzFA9AFg49dgZkjlxK6b+NPuXDQX+ObsscJEzsl8d2eBMBNQAXIMs0HFA27g7vlUb19ZtwyJ4KqEfZKWbCjx8sq9nc4f3BUVRheIYvrrdcWfZo1RC8XpnZGNlCj0QL9Dr4iSlyHIJHRH1c8+Hr8huE5W1bkMYZ6cYYvcsfptIT3Bse/mIqa486lW7Hww3R7tQBpvQQv9IWVbLP5JUNkvB/wT/1cBywrS/EUinEKu+RHT2tCUIBa0qPBRRM9/G8me/ksqBibYtosXUAWXDmE51ZcSQlEjDFU411DtNnNqFXas+JBCHteMe/v8ilgk9HO2f9blERM9tCIVqS7VSjMPnftvQoAZsoDM0NjWLLbi+H8F2+agCLpFVCF4p1YN17jNnG2QCQQV/zxDNSlnhmTWo1T9/8AatcuuQaIWEgdmAgGbOBkUIp2qzE6mltZw83cab6HGcCS6ZKwCqlWrn54DhfYXaeELkLT0qpMCAAo8gHv9VMtuE2mqL8UXPpV7kZ6k2eBYiQKp0JTAskJmjxYzUZgV8ZgOHyiTnOJs8Ll2dgS0tlrbJy2SvStDOfJZcjgA9wmznQplPKQDmBkJTY2zZzUv+DAKQ87em+n0+bYm73jzEfdxAh2FaJexmNy4wSJKXax0Cie+yYnCZg23LN70Wo3L1rAGJoPKXdrSEwxOhW8hno1LB61JYsKBuSIzbEnCCBkbuRt963Jesx0N/ugQGea42XMLJEJyrE3Yof72Tia0leodKIqnsLfs61rgqsHRW0QDfCMp6tbhJcGgaAvly7DO9IMGyqjyzwMVRwQoSpAURz+sIWtK9QH19lOi0AFRh5ID1h95LsMjckOgO4hvzj1eNtond3QDBfp87eK8sJjhaD6kBOtpuyxlYsylVjpsmL7XNiTlXX8Wf2V7PTe3oRDtB4sWPyHRP6hL79vrHriAlq0hH2Px3VOW+qPh79e0oyyrXgplPKQDmBkJTY2zZzUv+DAaT5HnulJULFi4upaRrllNGTvVs03Cm66tq1OcaJY3NALJJazWIKQ2F030rabLHg8JWAxfTJWmdWA14/6U+RFAAtLydWO4+B6BlsGR9JN+PA3x1nCuXjrLvWtvRrdefR0ihLdOcynDCmOnZnZGFW3PGR/G7pF9XJtBEikUTxzB2SXykvjq+HLp0kiRcD7fplsIlJEzdRe4EhxbLVlIZCaGG9WPZaCQTUCvPYVnXeOEByPc5yvGTnCOOhkd5md2zqsj9XRoX89RV9N5AVWC5SXfGeTc9rtXkF1AuzuGJTtOCCCLWUVVVibotKqQwYD6efsfvqH+peVunB+7xtyHvqytAjc09Igoh2YPJKfiz5XkVAc4izjYRWwcMpPFFtu30v4EhjHVn87prSLyWu2TUCnYAi7P9lm0jHWc7N42o8NY4wKOHhaPWU/qtrCdlxpumDkRgLQSLrCi01gymoQc7YR/IYq0EP+/0UPApC1pVPXyKgbVjTQdLae9nNaylaH/SXYOOmrybeVBY6eKe9UEYwfrGwvOZRmZr+jASrvXhxzp3CJ8i5+Zpx+amqp6ME455IYTg4wNeiL/JygOU4crCIICHlW0G52uVH+mxRb/pCFlIQtdnVp/coWFXDXgHM7/nKEbhUKndqjS3/mzEko9FrqZGaGVe1RXo9V1IYmbLKSw6wYI2mJhVe+QEzVaRWdpSwcoQBCrr0NDFWqcAVF9iRmAIVN4u0E8OBx3QCCXEsMRdiOCshWFub1T8+jthJ6Uxm4hNEYNmI2tj+RhikFW8fTfBVJ8TTm+1WkdB6WxUdmbwCJ30pdw2YSUCdAC7y1sT10nB9j6o8FKmeFFS8oSs6vzBu8o6MXUey+ASx9twaEpAAOxOJIbrOwVd/XlZjZlTOETaaovxRc+lXuRnqTZ4FiJD49CTVnlBJ2H+B42ymhCPxY/BUngvJAWLCspFyK1PjsdVKum9svax3z+cV5vatILH76h/qXlbpwfu8bch76srRXGBDuYD3dwYBDjldFw8/gOwmXU+ganpTM2ALcO7SGwJGz0p0NzUhbKF8HjkAjPjQXxkYBMQSsXwWGSNfPj79gg9xnnWk0HL2Xc5ZRY0WU5A6evDvioeec7D7v7c7bu3xJMOv087lFr/DFItDiik2cUIgd2iZxMlpxBdwJRCmT5B9diHCJt/vZoBc1n0lbFUhz/vFwS1HCtvf89FbiR9CwJnCGq8oyCKywruDhZfw/KC6soOEuOQW41OfUXWPCJvxAP2OXJyvyjphjyw/J8D30c71NvfZ2cRtHzMv0HI1OFIGbscZQTuj7fX5V6VtzZVCZ9zAJf4UKTYFuW+YLkDmAYKec/1Puwxg0dDjnAZ1bwIl5jba9G3Zeq+BjDu1GAjRuIdbaltiprEUzk3P7kENcPsMrvC6ru24QRWH2vAeh4B8WNKO74sH4mDfuqAsMzSxSfLMZ1c5EmUTu+SKs/6moQcYb9UsOjl0N0tPXSPxZRKDbK4yxcc6CttGLTNKijGQpynBb0WhUAvxKHv3/csswOKDglzoKJTIL7ev9RROloFqkytblYUbSAtTqs2pQp8gZsHDjZWmex4+yBn3t0vPQArqs2PWYRQfn8Quo5A0aREzJoMnGuDteHOSeR3WTNLCRAdQ1n6fN+bNtxLXM4nPYUMvokci1+jSdGIn5QQIuaDRxFEGUgL+D9+Q6z03ZkYCRMz3ye3GJ2WphEV33FLUwY93w1MdjKWU3SaLqY2B5uAAugHdNgOh3E2XAfxQ7EZxa556+mPXSpYZ2+sfjDnB4LUCiJEDyBj6Yiq13EkyjYHO7p6ovrwre/0XVCdlphUySjVvKGZs3EQ9o59mvnPPUh9y3Bnx+g/ps599A7SbVpDJi80M2VwaSQrbmvwNoOCQ5yRncu2Ma41uRyK4HALZ4pecCUvxAZwxJyiM0y8h15DJi80M2VwaSQrbmvwNoOCSW6JUFtb1OrhTEH6PsvA/4cfBt0Q4oEg7uMjm5a3qa7KJzYC/gClrK3Qb6uC4eP8xb4Mvk9qVqcPVjx62/A9aQXi2G/8qqvg1MlL6LYe9O8FtZTUGhK5zo1f7YfuM6JNwplPKQDmBkJTY2zZzUv+DALLgyfI8B7GNRZl7xo4UE5BGi+ZlVKzE+X3c+qnmVfHyQD6lEGItwo0wPEKrcNoPkJwHuni6/ENcEEe5+UOuY4CDMlxiLSr1O3s+kKidY8BgtTgCnwB7vjtFSFko+7veEPmO/yjYr3z15oSZPpbNrKIKqpRgzhRSKbUftLS6hU8x3H8qqQWiFxEl4womXwJOsi1KhEqlc9muLOQ6FNqAFiAiLK21+0yH0bjCzizZBlwQXOq7sjo2b4C1uMt9akz5sTXPSNeWg7E8TlMPRyEvIiJn6uRE42NqOH7lAUlWcCdyOWUFyniaa1Dvjwv4cx1WYSTLg5o9WZSF3KEg5QaVNqJdJKzO6zGtAjQR67R9gy5CPknR07AAlPYASDGqUh52sEJe9go7bnwlYpq9K9f0OrDojngIoE47WenVFaUjOl1hgdYZGdqKM+3mgGmDlOQNYAXkvLtegFnIeaHJWPyrZ/DW2wgsO58Hi5B8kpRXozMwtYD/XhdevE7z9BhBInK70hKj86x5tccBguC3tMaYsCC/dpTMec06tSlvwMVaiv/gj7/YJdFPQ2wda8DKhjIJsIBKK0leXk7B1yVld6yjhUHrer6+3/1ELM6BCQiSXiSSakU1ckEVV43hVFt7WMzNEINY/BnJzp85761j0B8QrtEvl+nOMLNlW27vWEhqT7XBk+5nVjkAUo6TOjsFkHaLUHyyr+HQHg1saPAgvtSdfGA3wzWLiA4s3q++yB9UBjXhYCt4yspQcoowbrsORq3D4hJmxsBdH3Gm/ajRuga8MLHLFCG4IJCLxLui/pNxvc6CcX6iQlNwKkWaB9VjYEbQUcBXIH26L7p+Gsx3h8Bm9GFIZp6ubBJb7N9z+HCqEzPxXrAix4s6bXvE7aEz50CDQfj3d1Fo4iU67uCWk+0PqCG5AhZ+vf/ZVyWBy7PdwF7BG6Y3hxUYzsnXqKjAy6M2AHqx6Py3nDB508TDwQYQ1zAWv8+KPt+8zmPLGwR2ycZgb1/twAbrg0w7ZZpvoZKRYW18rt9N7hvla5tgltU3cuAcsK0vxFIpxCrvkR09rQlCFMVED6Gaqv32agqG5GENIO1eF03gAbMSUAfsqdEfLrJa5G8NTV0vsXZkZwcrYtig00B71s7zRdMpTOQYxMkzgaK/uymSMdklKhHZGCeHTeElCBBSotPCJ11tnI/nPNHR81qJH4CMUIJ2JsistHuIUNHEUQZSAv4P35DrPTdmRgJEzPfJ7cYnZamERXfcUtTBj3fDUx2MpZTdJoupjYHm4HlR0L1yZ2P8+kwv+0thqN
diff --git a/tutorial/tutorialTestClasses.py b/tutorial/tutorialTestClasses.py
new file mode 100644
index 0000000000000000000000000000000000000000..07ea4c348ad8374ea7bd74a1ec243d70b9c48386
--- /dev/null
+++ b/tutorial/tutorialTestClasses.py
@@ -0,0 +1,57 @@
+# tutorialTestClasses.py
+# ----------------------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+import testClasses
+
+
+# Simple test case which evals an arbitrary piece of python code.
+# The test is correct if the output of the code given the student's
+# solution matches that of the instructor's.
+class EvalTest(testClasses.TestCase):
+
+    def __init__(self, question, testDict):
+        super(EvalTest, self).__init__(question, testDict)
+        self.preamble = compile(testDict.get('preamble', ""), "%s.preamble" % self.getPath(), 'exec')
+        self.test = compile(testDict['test'], "%s.test" % self.getPath(), 'eval')
+        self.success = testDict['success']
+        self.failure = testDict['failure']
+
+    def evalCode(self, moduleDict):
+        bindings = dict(moduleDict)
+        # exec self.preamble in bindings
+        exec(self.preamble, bindings)
+        return str(eval(self.test, bindings))
+
+    def execute(self, grades, moduleDict, solutionDict):
+        result = self.evalCode(moduleDict)
+        if result == solutionDict['result']:
+            grades.addMessage('PASS: %s' % self.path)
+            grades.addMessage('\t%s' % self.success)
+            return True
+        else:
+            grades.addMessage('FAIL: %s' % self.path)
+            grades.addMessage('\t%s' % self.failure)
+            grades.addMessage('\tstudent result: "%s"' % result)
+            grades.addMessage('\tcorrect result: "%s"' % solutionDict['result'])
+
+        return False
+
+    def writeSolution(self, moduleDict, filePath):
+        handle = open(filePath, 'w')
+        handle.write('# This is the solution file for %s.\n' % self.path)
+        handle.write('# The result of evaluating the test must equal the below when cast to a string.\n')
+
+        handle.write('result: "%s"\n' % self.evalCode(moduleDict))
+        handle.close()
+        return True
diff --git a/tutorial/util.py b/tutorial/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..50cd74a58362ec449c3d1f3dc40eb6ea5f79ccb5
--- /dev/null
+++ b/tutorial/util.py
@@ -0,0 +1,696 @@
+# util.py
+# -------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+# util.py
+# -------
+# Licensing Information:  You are free to use or extend these projects for
+# educational purposes provided that (1) you do not distribute or publish
+# solutions, (2) you retain this notice, and (3) you provide clear
+# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
+# 
+# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
+# The core projects and autograders were primarily created by John DeNero
+# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
+# Student side autograding was added by Brad Miller, Nick Hay, and
+# Pieter Abbeel (pabbeel@cs.berkeley.edu).
+
+
+import sys
+import inspect
+import heapq, random
+
+
+# import cStringIO
+
+
+class FixedRandom:
+    def __init__(self):
+        fixedState = (3, (2147483648, 507801126, 683453281, 310439348, 2597246090,
+                          2209084787, 2267831527, 979920060, 3098657677, 37650879, 807947081, 3974896263,
+                          881243242, 3100634921, 1334775171, 3965168385, 746264660, 4074750168, 500078808,
+                          776561771, 702988163, 1636311725, 2559226045, 157578202, 2498342920, 2794591496,
+                          4130598723, 496985844, 2944563015, 3731321600, 3514814613, 3362575829, 3038768745,
+                          2206497038, 1108748846, 1317460727, 3134077628, 988312410, 1674063516, 746456451,
+                          3958482413, 1857117812, 708750586, 1583423339, 3466495450, 1536929345, 1137240525,
+                          3875025632, 2466137587, 1235845595, 4214575620, 3792516855, 657994358, 1241843248,
+                          1695651859, 3678946666, 1929922113, 2351044952, 2317810202, 2039319015, 460787996, 3654096216,
+                          4068721415, 1814163703, 2904112444, 1386111013, 574629867, 2654529343, 3833135042, 2725328455,
+                          552431551, 4006991378, 1331562057, 3710134542, 303171486, 1203231078, 2670768975, 54570816,
+                          2679609001, 578983064, 1271454725, 3230871056, 2496832891, 2944938195, 1608828728, 367886575,
+                          2544708204, 103775539, 1912402393, 1098482180, 2738577070, 3091646463, 1505274463, 2079416566,
+                          659100352, 839995305, 1696257633, 274389836, 3973303017, 671127655, 1061109122, 517486945,
+                          1379749962, 3421383928, 3116950429, 2165882425, 2346928266, 2892678711, 2936066049,
+                          1316407868, 2873411858, 4279682888, 2744351923, 3290373816, 1014377279, 955200944, 4220990860,
+                          2386098930, 1772997650, 3757346974, 1621616438, 2877097197, 442116595, 2010480266, 2867861469,
+                          2955352695, 605335967, 2222936009, 2067554933, 4129906358, 1519608541, 1195006590, 1942991038,
+                          2736562236, 279162408, 1415982909, 4099901426, 1732201505, 2934657937, 860563237, 2479235483,
+                          3081651097, 2244720867, 3112631622, 1636991639, 3860393305, 2312061927, 48780114, 1149090394,
+                          2643246550, 1764050647, 3836789087, 3474859076, 4237194338, 1735191073, 2150369208, 92164394,
+                          756974036, 2314453957, 323969533, 4267621035, 283649842, 810004843, 727855536, 1757827251,
+                          3334960421, 3261035106, 38417393, 2660980472, 1256633965, 2184045390, 811213141, 2857482069,
+                          2237770878, 3891003138, 2787806886, 2435192790, 2249324662, 3507764896, 995388363, 856944153,
+                          619213904, 3233967826, 3703465555, 3286531781, 3863193356, 2992340714, 413696855, 3865185632,
+                          1704163171, 3043634452, 2225424707, 2199018022, 3506117517, 3311559776, 3374443561,
+                          1207829628, 668793165, 1822020716, 2082656160, 1160606415, 3034757648, 741703672, 3094328738,
+                          459332691, 2702383376, 1610239915, 4162939394, 557861574, 3805706338, 3832520705, 1248934879,
+                          3250424034, 892335058, 74323433, 3209751608, 3213220797, 3444035873, 3743886725, 1783837251,
+                          610968664, 580745246, 4041979504, 201684874, 2673219253, 1377283008, 3497299167, 2344209394,
+                          2304982920, 3081403782, 2599256854, 3184475235, 3373055826, 695186388, 2423332338, 222864327,
+                          1258227992, 3627871647, 3487724980, 4027953808, 3053320360, 533627073, 3026232514, 2340271949,
+                          867277230, 868513116, 2158535651, 2487822909, 3428235761, 3067196046, 3435119657, 1908441839,
+                          788668797, 3367703138, 3317763187, 908264443, 2252100381, 764223334, 4127108988, 384641349,
+                          3377374722, 1263833251, 1958694944, 3847832657, 1253909612, 1096494446, 555725445, 2277045895,
+                          3340096504, 1383318686, 4234428127, 1072582179, 94169494, 1064509968, 2681151917, 2681864920,
+                          734708852, 1338914021, 1270409500, 1789469116, 4191988204, 1716329784, 2213764829, 3712538840,
+                          919910444, 1318414447, 3383806712, 3054941722, 3378649942, 1205735655, 1268136494, 2214009444,
+                          2532395133, 3232230447, 230294038, 342599089, 772808141, 4096882234, 3146662953, 2784264306,
+                          1860954704, 2675279609, 2984212876, 2466966981, 2627986059, 2985545332, 2578042598,
+                          1458940786, 2944243755, 3959506256, 1509151382, 325761900, 942251521, 4184289782, 2756231555,
+                          3297811774, 1169708099, 3280524138, 3805245319, 3227360276, 3199632491, 2235795585,
+                          2865407118, 36763651, 2441503575, 3314890374, 1755526087, 17915536, 1196948233, 949343045,
+                          3815841867, 489007833, 2654997597, 2834744136, 417688687, 2843220846, 85621843, 747339336,
+                          2043645709, 3520444394, 1825470818, 647778910, 275904777, 1249389189, 3640887431, 4200779599,
+                          323384601, 3446088641, 4049835786, 1718989062, 3563787136, 44099190, 3281263107, 22910812,
+                          1826109246, 745118154, 3392171319, 1571490704, 354891067, 815955642, 1453450421, 940015623,
+                          796817754, 1260148619, 3898237757, 176670141, 1870249326, 3317738680, 448918002, 4059166594,
+                          2003827551, 987091377, 224855998, 3520570137, 789522610, 2604445123, 454472869, 475688926,
+                          2990723466, 523362238, 3897608102, 806637149, 2642229586, 2928614432, 1564415411, 1691381054,
+                          3816907227, 4082581003, 1895544448, 3728217394, 3214813157, 4054301607, 1882632454,
+                          2873728645, 3694943071, 1297991732, 2101682438, 3952579552, 678650400, 1391722293, 478833748,
+                          2976468591, 158586606, 2576499787, 662690848, 3799889765, 3328894692, 2474578497, 2383901391,
+                          1718193504, 3003184595, 3630561213, 1929441113, 3848238627, 1594310094, 3040359840,
+                          3051803867, 2462788790, 954409915, 802581771, 681703307, 545982392, 2738993819, 8025358,
+                          2827719383, 770471093, 3484895980, 3111306320, 3900000891, 2116916652, 397746721, 2087689510,
+                          721433935, 1396088885, 2751612384, 1998988613, 2135074843, 2521131298, 707009172, 2398321482,
+                          688041159, 2264560137, 482388305, 207864885, 3735036991, 3490348331, 1963642811, 3260224305,
+                          3493564223, 1939428454, 1128799656, 1366012432, 2858822447, 1428147157, 2261125391,
+                          1611208390, 1134826333, 2374102525, 3833625209, 2266397263, 3189115077, 770080230, 2674657172,
+                          4280146640, 3604531615, 4235071805, 3436987249, 509704467, 2582695198, 4256268040, 3391197562,
+                          1460642842, 1617931012, 457825497, 1031452907, 1330422862, 4125947620, 2280712485, 431892090,
+                          2387410588, 2061126784, 896457479, 3480499461, 2488196663, 4021103792, 1877063114, 2744470201,
+                          1046140599, 2129952955, 3583049218, 4217723693, 2720341743, 820661843, 1079873609, 3360954200,
+                          3652304997, 3335838575, 2178810636, 1908053374, 4026721976, 1793145418, 476541615, 973420250,
+                          515553040, 919292001, 2601786155, 1685119450, 3030170809, 1590676150, 1665099167, 651151584,
+                          2077190587, 957892642, 646336572, 2743719258, 866169074, 851118829, 4225766285, 963748226,
+                          799549420, 1955032629, 799460000, 2425744063, 2441291571, 1928963772, 528930629, 2591962884,
+                          3495142819, 1896021824, 901320159, 3181820243, 843061941, 3338628510, 3782438992, 9515330,
+                          1705797226, 953535929, 764833876, 3202464965, 2970244591, 519154982, 3390617541, 566616744,
+                          3438031503, 1853838297, 170608755, 1393728434, 676900116, 3184965776, 1843100290, 78995357,
+                          2227939888, 3460264600, 1745705055, 1474086965, 572796246, 4081303004, 882828851, 1295445825,
+                          137639900, 3304579600, 2722437017, 4093422709, 273203373, 2666507854, 3998836510, 493829981,
+                          1623949669, 3482036755, 3390023939, 833233937, 1639668730, 1499455075, 249728260, 1210694006,
+                          3836497489, 1551488720, 3253074267, 3388238003, 2372035079, 3945715164, 2029501215,
+                          3362012634, 2007375355, 4074709820, 631485888, 3135015769, 4273087084, 3648076204, 2739943601,
+                          1374020358, 1760722448, 3773939706, 1313027823, 1895251226, 4224465911, 421382535, 1141067370,
+                          3660034846, 3393185650, 1850995280, 1451917312, 3841455409, 3926840308, 1397397252,
+                          2572864479, 2500171350, 3119920613, 531400869, 1626487579, 1099320497, 407414753, 2438623324,
+                          99073255, 3175491512, 656431560, 1153671785, 236307875, 2824738046, 2320621382, 892174056,
+                          230984053, 719791226, 2718891946, 624), None)
+        self.random = random.Random()
+        self.random.setstate(fixedState)
+
+
+"""
+ Data structures useful for implementing SearchAgents
+"""
+
+
+class Stack:
+    "A container with a last-in-first-out (LIFO) queuing policy."
+
+    def __init__(self):
+        self.list = []
+
+    def push(self, item):
+        "Push 'item' onto the stack"
+        self.list.append(item)
+
+    def pop(self):
+        "Pop the most recently pushed item from the stack"
+        return self.list.pop()
+
+    def isEmpty(self):
+        "Returns true if the stack is empty"
+        return len(self.list) == 0
+
+
+class Queue:
+    "A container with a first-in-first-out (FIFO) queuing policy."
+
+    def __init__(self):
+        self.list = []
+
+    def push(self, item):
+        "Enqueue the 'item' into the queue"
+        self.list.insert(0, item)
+
+    def pop(self):
+        """
+          Dequeue the earliest enqueued item still in the queue. This
+          operation removes the item from the queue.
+        """
+        return self.list.pop()
+
+    def isEmpty(self):
+        "Returns true if the queue is empty"
+        return len(self.list) == 0
+
+
+class PriorityQueue:
+    """
+      Implements a priority queue data structure. Each inserted item
+      has a priority associated with it and the client is usually interested
+      in quick retrieval of the lowest-priority item in the queue. This
+      data structure allows O(1) access to the lowest-priority item.
+    """
+
+    def __init__(self):
+        self.heap = []
+        self.count = 0
+
+    def push(self, item, priority):
+        entry = (priority, self.count, item)
+        heapq.heappush(self.heap, entry)
+        self.count += 1
+
+    def pop(self):
+        (_, _, item) = heapq.heappop(self.heap)
+        return item
+
+    def isEmpty(self):
+        return len(self.heap) == 0
+
+    def update(self, item, priority):
+        # If item already in priority queue with higher priority, update its priority and rebuild the heap.
+        # If item already in priority queue with equal or lower priority, do nothing.
+        # If item not in priority queue, do the same thing as self.push.
+        for index, (p, c, i) in enumerate(self.heap):
+            if i == item:
+                if p <= priority:
+                    break
+                del self.heap[index]
+                self.heap.append((priority, c, item))
+                heapq.heapify(self.heap)
+                break
+        else:
+            self.push(item, priority)
+
+
+class PriorityQueueWithFunction(PriorityQueue):
+    """
+    Implements a priority queue with the same push/pop signature of the
+    Queue and the Stack classes. This is designed for drop-in replacement for
+    those two classes. The caller has to provide a priority function, which
+    extracts each item's priority.
+    """
+
+    def __init__(self, priorityFunction):
+        "priorityFunction (item) -> priority"
+        self.priorityFunction = priorityFunction  # store the priority function
+        PriorityQueue.__init__(self)  # super-class initializer
+
+    def push(self, item):
+        "Adds an item to the queue with priority from the priority function"
+        PriorityQueue.push(self, item, self.priorityFunction(item))
+
+
+def manhattanDistance(xy1, xy2):
+    "Returns the Manhattan distance between points xy1 and xy2"
+    return abs(xy1[0] - xy2[0]) + abs(xy1[1] - xy2[1])
+
+
+"""
+  Data structures and functions useful for various course projects
+
+  The search project should not need anything below this line.
+"""
+
+
+class Counter(dict):
+    """
+    A counter keeps track of counts for a set of keys.
+
+    The counter class is an extension of the standard python
+    dictionary type.  It is specialized to have number values
+    (integers or floats), and includes a handful of additional
+    functions to ease the task of counting data.  In particular,
+    all keys are defaulted to have value 0.  Using a dictionary:
+
+    a = {}
+    print(a['test'])
+
+    would give an error, while the Counter class analogue:
+
+    >>> a = Counter()
+    >>> print(a['test'])
+    0
+
+    returns the default 0 value. Note that to reference a key
+    that you know is contained in the counter,
+    you can still use the dictionary syntax:
+
+    >>> a = Counter()
+    >>> a['test'] = 2
+    >>> print(a['test'])
+    2
+
+    This is very useful for counting things without initializing their counts,
+    see for example:
+
+    >>> a['blah'] += 1
+    >>> print(a['blah'])
+    1
+
+    The counter also includes additional functionality useful in implementing
+    the classifiers for this assignment.  Two counters can be added,
+    subtracted or multiplied together.  See below for details.  They can
+    also be normalized and their total count and arg max can be extracted.
+    """
+
+    def __getitem__(self, idx):
+        self.setdefault(idx, 0)
+        return dict.__getitem__(self, idx)
+
+    def incrementAll(self, keys, count):
+        """
+        Increments all elements of keys by the same count.
+
+        >>> a = Counter()
+        >>> a.incrementAll(['one','two', 'three'], 1)
+        >>> a['one']
+        1
+        >>> a['two']
+        1
+        """
+        for key in keys:
+            self[key] += count
+
+    def argMax(self):
+        """
+        Returns the key with the highest value.
+        """
+        if len(self.keys()) == 0: return None
+        all = self.items()
+        values = [x[1] for x in all]
+        maxIndex = values.index(max(values))
+        return all[maxIndex][0]
+
+    def sortedKeys(self):
+        """
+        Returns a list of keys sorted by their values.  Keys
+        with the highest values will appear first.
+
+        >>> a = Counter()
+        >>> a['first'] = -2
+        >>> a['second'] = 4
+        >>> a['third'] = 1
+        >>> a.sortedKeys()
+        ['second', 'third', 'first']
+        """
+        sortedItems = self.items()
+        compare = lambda x, y: sign(y[1] - x[1])
+        sortedItems.sort(cmp=compare)
+        return [x[0] for x in sortedItems]
+
+    def totalCount(self):
+        """
+        Returns the sum of counts for all keys.
+        """
+        return sum(self.values())
+
+    def normalize(self):
+        """
+        Edits the counter such that the total count of all
+        keys sums to 1.  The ratio of counts for all keys
+        will remain the same. Note that normalizing an empty
+        Counter will result in an error.
+        """
+        total = float(self.totalCount())
+        if total == 0: return
+        for key in self.keys():
+            self[key] = self[key] / total
+
+    def divideAll(self, divisor):
+        """
+        Divides all counts by divisor
+        """
+        divisor = float(divisor)
+        for key in self:
+            self[key] /= divisor
+
+    def copy(self):
+        """
+        Returns a copy of the counter
+        """
+        return Counter(dict.copy(self))
+
+    def __mul__(self, y):
+        """
+        Multiplying two counters gives the dot product of their vectors where
+        each unique label is a vector element.
+
+        >>> a = Counter()
+        >>> b = Counter()
+        >>> a['first'] = -2
+        >>> a['second'] = 4
+        >>> b['first'] = 3
+        >>> b['second'] = 5
+        >>> a['third'] = 1.5
+        >>> a['fourth'] = 2.5
+        >>> a * b
+        14
+        """
+        sum = 0
+        x = self
+        if len(x) > len(y):
+            x, y = y, x
+        for key in x:
+            if key not in y:
+                continue
+            sum += x[key] * y[key]
+        return sum
+
+    def __radd__(self, y):
+        """
+        Adding another counter to a counter increments the current counter
+        by the values stored in the second counter.
+
+        >>> a = Counter()
+        >>> b = Counter()
+        >>> a['first'] = -2
+        >>> a['second'] = 4
+        >>> b['first'] = 3
+        >>> b['third'] = 1
+        >>> a += b
+        >>> a['first']
+        1
+        """
+        for key, value in y.items():
+            self[key] += value
+
+    def __add__(self, y):
+        """
+        Adding two counters gives a counter with the union of all keys and
+        counts of the second added to counts of the first.
+
+        >>> a = Counter()
+        >>> b = Counter()
+        >>> a['first'] = -2
+        >>> a['second'] = 4
+        >>> b['first'] = 3
+        >>> b['third'] = 1
+        >>> (a + b)['first']
+        1
+        """
+        addend = Counter()
+        for key in self:
+            if key in y:
+                addend[key] = self[key] + y[key]
+            else:
+                addend[key] = self[key]
+        for key in y:
+            if key in self:
+                continue
+            addend[key] = y[key]
+        return addend
+
+    def __sub__(self, y):
+        """
+        Subtracting a counter from another gives a counter with the union of all keys and
+        counts of the second subtracted from counts of the first.
+
+        >>> a = Counter()
+        >>> b = Counter()
+        >>> a['first'] = -2
+        >>> a['second'] = 4
+        >>> b['first'] = 3
+        >>> b['third'] = 1
+        >>> (a - b)['first']
+        -5
+        """
+        addend = Counter()
+        for key in self:
+            if key in y:
+                addend[key] = self[key] - y[key]
+            else:
+                addend[key] = self[key]
+        for key in y:
+            if key in self:
+                continue
+            addend[key] = -1 * y[key]
+        return addend
+
+
+def raiseNotDefined():
+    fileName = inspect.stack()[1][1]
+    line = inspect.stack()[1][2]
+    method = inspect.stack()[1][3]
+
+    print("*** Method not implemented: %s at line %s of %s" % (method, line, fileName))
+    sys.exit(1)
+
+
+def normalize(vectorOrCounter):
+    """
+    normalize a vector or counter by dividing each value by the sum of all values
+    """
+    normalizedCounter = Counter()
+    if type(vectorOrCounter) == type(normalizedCounter):
+        counter = vectorOrCounter
+        total = float(counter.totalCount())
+        if total == 0: return counter
+        for key in counter.keys():
+            value = counter[key]
+            normalizedCounter[key] = value / total
+        return normalizedCounter
+    else:
+        vector = vectorOrCounter
+        s = float(sum(vector))
+        if s == 0: return vector
+        return [el / s for el in vector]
+
+
+def nSample(distribution, values, n):
+    if sum(distribution) != 1:
+        distribution = normalize(distribution)
+    rand = [random.random() for i in range(n)]
+    rand.sort()
+    samples = []
+    samplePos, distPos, cdf = 0, 0, distribution[0]
+    while samplePos < n:
+        if rand[samplePos] < cdf:
+            samplePos += 1
+            samples.append(values[distPos])
+        else:
+            distPos += 1
+            cdf += distribution[distPos]
+    return samples
+
+
+def sample(distribution, values=None):
+    if type(distribution) == Counter:
+        items = sorted(distribution.items())
+        distribution = [i[1] for i in items]
+        values = [i[0] for i in items]
+    if sum(distribution) != 1:
+        distribution = normalize(distribution)
+    choice = random.random()
+    i, total = 0, distribution[0]
+    while choice > total:
+        i += 1
+        total += distribution[i]
+    return values[i]
+
+
+def sampleFromCounter(ctr):
+    items = sorted(ctr.items())
+    return sample([v for k, v in items], [k for k, v in items])
+
+
+def getProbability(value, distribution, values):
+    """
+      Gives the probability of a value under a discrete distribution
+      defined by (distributions, values).
+    """
+    total = 0.0
+    for prob, val in zip(distribution, values):
+        if val == value:
+            total += prob
+    return total
+
+
+def flipCoin(p):
+    r = random.random()
+    return r < p
+
+
+def chooseFromDistribution(distribution):
+    "Takes either a counter or a list of (prob, key) pairs and samples"
+    if type(distribution) == dict or type(distribution) == Counter:
+        return sample(distribution)
+    r = random.random()
+    base = 0.0
+    for prob, element in distribution:
+        base += prob
+        if r <= base: return element
+
+
+def nearestPoint(pos):
+    """
+    Finds the nearest grid point to a position (discretizes).
+    """
+    (current_row, current_col) = pos
+
+    grid_row = int(current_row + 0.5)
+    grid_col = int(current_col + 0.5)
+    return (grid_row, grid_col)
+
+
+def sign(x):
+    """
+    Returns 1 or -1 depending on the sign of x
+    """
+    if (x >= 0):
+        return 1
+    else:
+        return -1
+
+
+def arrayInvert(array):
+    """
+    Inverts a matrix stored as a list of lists.
+    """
+    result = [[] for i in array]
+    for outer in array:
+        for inner in range(len(outer)):
+            result[inner].append(outer[inner])
+    return result
+
+
+def matrixAsList(matrix, value=True):
+    """
+    Turns a matrix into a list of coordinates matching the specified value
+    """
+    rows, cols = len(matrix), len(matrix[0])
+    cells = []
+    for row in range(rows):
+        for col in range(cols):
+            if matrix[row][col] == value:
+                cells.append((row, col))
+    return cells
+
+
+def lookup(name, namespace):
+    """
+    Get a method or class from any imported module from its name.
+    Usage: lookup(functionName, globals())
+    """
+    dots = name.count('.')
+    if dots > 0:
+        moduleName, objName = '.'.join(name.split('.')[:-1]), name.split('.')[-1]
+        module = __import__(moduleName)
+        return getattr(module, objName)
+    else:
+        modules = [obj for obj in namespace.values() if str(type(obj)) == "<type 'module'>"]
+        options = [getattr(module, name) for module in modules if name in dir(module)]
+        options += [obj[1] for obj in namespace.items() if obj[0] == name]
+        if len(options) == 1: return options[0]
+        if len(options) > 1: raise Exception('Name conflict for %s')
+        raise Exception('%s not found as a method or class' % name)
+
+
+def pause():
+    """
+    Pauses the output stream awaiting user feedback.
+    """
+    print("<Press enter/return to continue>")
+    raw_input()
+
+
+# code to handle timeouts
+#
+# FIXME
+# NOTE: TimeoutFuncton is NOT reentrant.  Later timeouts will silently
+# disable earlier timeouts.  Could be solved by maintaining a global list
+# of active time outs.  Currently, questions which have test cases calling
+# this have all student code so wrapped.
+#
+import signal
+import time
+
+
+class TimeoutFunctionException(Exception):
+    """Exception to raise on a timeout"""
+    pass
+
+
+class TimeoutFunction:
+    def __init__(self, function, timeout):
+        self.timeout = timeout
+        self.function = function
+
+    def handle_timeout(self, signum, frame):
+        raise TimeoutFunctionException()
+
+    def __call__(self, *args, **keyArgs):
+        # If we have SIGALRM signal, use it to cause an exception if and
+        # when this function runs too long.  Otherwise check the time taken
+        # after the method has returned, and throw an exception then.
+        if hasattr(signal, 'SIGALRM'):
+            old = signal.signal(signal.SIGALRM, self.handle_timeout)
+            signal.alarm(self.timeout)
+            try:
+                result = self.function(*args, **keyArgs)
+            finally:
+                signal.signal(signal.SIGALRM, old)
+            signal.alarm(0)
+        else:
+            startTime = time.time()
+            result = self.function(*args, **keyArgs)
+            timeElapsed = time.time() - startTime
+            if timeElapsed >= self.timeout:
+                self.handle_timeout(None, None)
+        return result
+
+
+_ORIGINAL_STDOUT = None
+_ORIGINAL_STDERR = None
+_MUTED = False
+
+
+class WritableNull:
+    def write(self, string):
+        pass
+
+
+def mutePrint():
+    global _ORIGINAL_STDOUT, _ORIGINAL_STDERR, _MUTED
+    if _MUTED:
+        return
+    _MUTED = True
+
+    _ORIGINAL_STDOUT = sys.stdout
+    # _ORIGINAL_STDERR = sys.stderr
+    sys.stdout = WritableNull()
+    # sys.stderr = WritableNull()
+
+
+def unmutePrint():
+    global _ORIGINAL_STDOUT, _ORIGINAL_STDERR, _MUTED
+    if not _MUTED:
+        return
+    _MUTED = False
+
+    sys.stdout = _ORIGINAL_STDOUT
+    # sys.stderr = _ORIGINAL_STDERR
diff --git a/unitgrade_private/__pycache__/hidden_create_files.cpython-36.pyc b/unitgrade_private/__pycache__/hidden_create_files.cpython-36.pyc
index 5bbc47a3f98795e19bc84bc75aea42370b40e855..f40a0a12093b3c455ff55030ca869e4f35c1bea3 100644
Binary files a/unitgrade_private/__pycache__/hidden_create_files.cpython-36.pyc and b/unitgrade_private/__pycache__/hidden_create_files.cpython-36.pyc differ
diff --git a/unitgrade_private/__pycache__/hidden_gather_upload.cpython-36.pyc b/unitgrade_private/__pycache__/hidden_gather_upload.cpython-36.pyc
index d1b8579b18a393e2fdb792be88d9ac27a13a0e6b..2698f77a94381be7470acd475c0945400637cdb6 100644
Binary files a/unitgrade_private/__pycache__/hidden_gather_upload.cpython-36.pyc and b/unitgrade_private/__pycache__/hidden_gather_upload.cpython-36.pyc differ
diff --git a/unitgrade_private/hidden_create_files.py b/unitgrade_private/hidden_create_files.py
index b1dcda2c7794bcf8646670226be2d31fbf6f9ade..a4f03f4cbb706cdc7284526d030e9f48de7526f0 100644
--- a/unitgrade_private/hidden_create_files.py
+++ b/unitgrade_private/hidden_create_files.py
@@ -7,6 +7,8 @@ import pickle
 import inspect
 # import json
 import os
+from unitgrade_private import hidden_gather_upload
+# import time
 
 data = """
 {{head}}
@@ -14,7 +16,7 @@ data = """
 report1_source = {{source}}
 report1_payload = {{payload}}
 name="{{Report1}}"
-from unitgrade.unitgrade_helpers import source_instantiate
+
 report = source_instantiate(name, report1_source, report1_payload)
 output_dir = os.path.dirname(__file__)
 gather_upload_to_campusnet(report, output_dir)
@@ -44,9 +46,9 @@ def strip_main(report1_source):
 
 def setup_grade_file_report(ReportClass, execute=True, obfuscate=False, minify=False, bzip=True, nonlatin=False):
     # from irlc.autograde.autograde import setup_answers
-    import time
     print("Seeting up answers...")
     setup_answers(ReportClass())
+    import time
     time.sleep(0.1)
     print("Packing student files...")
     # pack report into a binary blob thingy the students can run on their own.
@@ -59,13 +61,46 @@ def setup_grade_file_report(ReportClass, execute=True, obfuscate=False, minify=F
     payload = cache_read(report.computed_answers_file)
     picklestring = pickle.dumps(payload)
 
-    from unitgrade_private import hidden_gather_upload
-    # from unitgrade_private import unitgrade_helpers
-
     pyhead = ""
-    for fname in [hidden_gather_upload.__file__]: #, unitgrade_helpers.__file__]:
-        with open(fname, 'r') as f:
-            pyhead += f.read() + "\n" + "\n"
+    from unitgrade import unitgrade_helpers
+    import unitgrade
+
+    # Include all framework-specific stuff in the report.
+
+
+    # for fname in [unitgrade.__file__, unitgrade.unitgrade.__file__, unitgrade_helpers.__file__, hidden_gather_upload.__file__]:
+    #     with open(fname, 'r') as f:
+    #         pyhead += f.read() + "\n" + "\n"
+    excl = ["unitgrade.unitgrade_helpers",
+            "from . import",
+            "from unitgrade.",
+            "from unitgrade ",
+            "import unitgrade"]
+
+    def rmimports(s, excl):
+        s = "\n".join([l for l in s.splitlines() if not any([l.strip().startswith(e) for e in excl])])
+        return s
+
+    unitgrade_framework = [unitgrade.__file__, unitgrade.unitgrade.__file__, unitgrade_helpers.__file__, hidden_gather_upload.__file__]
+    def lload(flist, excl):
+        s = ""
+        for fname in flist:
+            with open(fname, 'r', encoding="utf-8") as f:
+                s += f.read() + "\n" + "\n"
+        s = rmimports(s, excl)  # remove import statements from helper class.
+        return s
+    report1_source = rmimports(report1_source, excl)
+
+    pyhead = lload(unitgrade_framework, excl)
+    report1_source = pyhead + "\n" + report1_source
+
+    print("\n".join([l for l in pyhead.splitlines() if l.find("unitgrade") > 0]))
+    # pyhead = "\n".join([l for l in pyhead.splitlines() if
+    #                     not any([l.startswith(e) for e in excl])])  # remove import statements from helper class.
+
+    # for fname in [hidden_gather_upload.__file__]:
+    #     with open(fname, 'r') as f:
+    #         pyhead += f.read() + "\n" + "\n"
 
     s = jinja2.Environment().from_string(data).render({'Report1': ReportClass.__name__,
                                                        'source': repr(report1_source),
@@ -74,7 +109,7 @@ def setup_grade_file_report(ReportClass, execute=True, obfuscate=False, minify=F
                                                        'head': pyhead})
 
     output = fn[:-3] + "_grade.py"
-    with open(output, 'w') as f:
+    with open(output, 'w', encoding="utf-8") as f:
         f.write(s)
 
     if minify:  # obfuscate:
diff --git a/unitgrade_private/hidden_gather_upload.py b/unitgrade_private/hidden_gather_upload.py
index 21a02d6532c3aa6afec734f5247de0af06bc3e75..ff1d809c93562e0ff94d757f0df95a5acd5b99af 100644
--- a/unitgrade_private/hidden_gather_upload.py
+++ b/unitgrade_private/hidden_gather_upload.py
@@ -5,6 +5,7 @@ import inspect
 import json
 import os
 import bz2
+import pickle
 
 def bzwrite(json_str, token): # to get around obfuscation issues
     with getattr(bz2, 'open')(token, "wt") as f:
@@ -44,3 +45,10 @@ def gather_upload_to_campusnet(report, output_dir=None):
     print("To get credit for your results, please upload the single file: ")
     print(">", token)
     print("To campusnet without any modifications.")
+
+def source_instantiate(name, report1_source, payload):
+    eval("exec")(report1_source, globals())
+    report = eval(name)()
+    pl = pickle.loads(payload)
+    report.set_payload(pl)
+    return report