Worked on Enigma, mainly changing how the notch positions are handled development
authorNeil Smith <neil.git@njae.me.uk>
Tue, 12 Sep 2017 15:18:57 +0000 (16:18 +0100)
committerNeil Smith <neil.git@njae.me.uk>
Tue, 12 Sep 2017 15:18:57 +0000 (16:18 +0100)
bombe.ipynb
dtu_notch_big.jpg [new file with mode: 0644]
enigma-notch.jpg [new file with mode: 0644]
enigma-old.ipynb [new file with mode: 0644]
enigma.ipynb
enigma.py
test_enigma.py

index 93e0ccbfcd5ac4996251c5eb08db7e6deadd969b..e28af9aac5316617032de9fa7f1becc61f6ccea5 100644 (file)
@@ -75,9 +75,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 31,
+   "execution_count": 4,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
     "                    self.pending += [Signal(current.wire, current.bank)]\n",
     "                for c in self.connections:\n",
     "                    if current.bank in c.banks:\n",
-    "                        other_bank = [b for b in c.banks if b != current.bank][0]\n",
+    "                        if len(set(c.banks)) == 1:\n",
+    "                            other_bank = c.banks[0]\n",
+    "                        else:\n",
+    "                            other_bank = [b for b in c.banks if b != current.bank][0]\n",
     "                        other_wire = c.scrambler.lookup(current.wire)\n",
     "                        # print(\"  adding\", other_bank, other_wire, \"because\", c.banks)\n",
     "                        self.pending += [Signal(other_bank, other_wire)]\n",
   },
   {
    "cell_type": "code",
-   "execution_count": 32,
+   "execution_count": 5,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 33,
+   "execution_count": 6,
    "metadata": {
     "collapsed": true
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 34,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 7,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'opgndxcrwomnlnecjz'"
       ]
      },
-     "execution_count": 34,
+     "execution_count": 7,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 35,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 8,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'aas'"
       ]
      },
-     "execution_count": 35,
+     "execution_count": 8,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 36,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 9,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        " MenuIem(before='e', after='z', number=18)]"
       ]
      },
-     "execution_count": 36,
+     "execution_count": 9,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 37,
+   "execution_count": 10,
    "metadata": {
     "collapsed": true
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 38,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 11,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        " MenuIem(before='e', after='z', number=18)]"
       ]
      },
-     "execution_count": 38,
+     "execution_count": 11,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 39,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 12,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'s'"
       ]
      },
-     "execution_count": 39,
+     "execution_count": 12,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 42,
+   "execution_count": 13,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 43,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 14,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "18"
       ]
      },
-     "execution_count": 43,
+     "execution_count": 14,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 27,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 15,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "['t', 'o'] aaa\n",
-      "['h', 'p'] aab\n",
-      "['i', 'g'] aac\n",
-      "['s', 'n'] aad\n",
-      "['i', 'd'] aae\n",
-      "['s', 'x'] aaf\n",
-      "['a', 'c'] aag\n",
-      "['t', 'r'] aah\n",
-      "['e', 'w'] aai\n",
-      "['s', 'o'] aaj\n",
-      "['t', 'm'] aak\n",
-      "['m', 'n'] aal\n",
-      "['e', 'l'] aam\n",
-      "['s', 'n'] aan\n",
-      "['s', 'e'] aao\n",
-      "['a', 'c'] aap\n",
-      "['g', 'j'] aaq\n",
-      "['e', 'z'] aar\n",
       "['t', 'o'] aaa\n",
       "['h', 'p'] aab\n",
       "['i', 'g'] aac\n",
   },
   {
    "cell_type": "code",
-   "execution_count": 44,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 16,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'ot:hp:gi:ns:di:sx:ac:rt:ew:os:mt:mn:el:ns:es:ac:gj:ez'"
       ]
      },
-     "execution_count": 44,
+     "execution_count": 16,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 45,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 17,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'aaa:aab:aac:aad:aae:aaf:aag:aah:aai:aaj:aak:aal:aam:aan:aao:aap:aaq:aar'"
       ]
      },
-     "execution_count": 45,
+     "execution_count": 17,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 56,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 18,
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "True"
+       "False"
       ]
      },
-     "execution_count": 56,
+     "execution_count": 18,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 57,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 19,
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "{'a': False,\n",
-       " 'b': False,\n",
-       " 'c': False,\n",
-       " 'd': False,\n",
-       " 'e': False,\n",
-       " 'f': False,\n",
-       " 'g': False,\n",
-       " 'h': False,\n",
-       " 'i': False,\n",
-       " 'j': False,\n",
-       " 'k': False,\n",
-       " 'l': False,\n",
-       " 'm': False,\n",
-       " 'n': False,\n",
-       " 'o': False,\n",
-       " 'p': False,\n",
-       " 'q': False,\n",
-       " 'r': False,\n",
-       " 's': False,\n",
+       "{'a': True,\n",
+       " 'b': True,\n",
+       " 'c': True,\n",
+       " 'd': True,\n",
+       " 'e': True,\n",
+       " 'f': True,\n",
+       " 'g': True,\n",
+       " 'h': True,\n",
+       " 'i': True,\n",
+       " 'j': True,\n",
+       " 'k': True,\n",
+       " 'l': True,\n",
+       " 'm': True,\n",
+       " 'n': True,\n",
+       " 'o': True,\n",
+       " 'p': True,\n",
+       " 'q': True,\n",
+       " 'r': True,\n",
+       " 's': True,\n",
        " 't': True,\n",
-       " 'u': False,\n",
-       " 'v': False,\n",
-       " 'w': False,\n",
-       " 'x': False,\n",
-       " 'y': False,\n",
-       " 'z': False}"
+       " 'u': True,\n",
+       " 'v': True,\n",
+       " 'w': True,\n",
+       " 'x': True,\n",
+       " 'y': True,\n",
+       " 'z': True}"
       ]
      },
-     "execution_count": 57,
+     "execution_count": 19,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 48,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 20,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
   },
   {
    "cell_type": "code",
-   "execution_count": 49,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 21,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
   },
   {
    "cell_type": "code",
-   "execution_count": 50,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 22,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "('a', 'a', 'a')"
       ]
      },
-     "execution_count": 50,
+     "execution_count": 22,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 51,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 23,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
   },
   {
    "cell_type": "code",
-   "execution_count": 52,
+   "execution_count": 24,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 53,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 25,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
   },
   {
    "cell_type": "code",
-   "execution_count": 54,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 26,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
   },
   {
    "cell_type": "code",
-   "execution_count": 55,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 27,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "1"
       ]
      },
-     "execution_count": 55,
+     "execution_count": 27,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 25,
+   "execution_count": 28,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 26,
+   "execution_count": 29,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 27,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 30,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "('a', 'a', 'b')"
       ]
      },
-     "execution_count": 27,
+     "execution_count": 30,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 28,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 31,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "False"
       ]
      },
-     "execution_count": 28,
+     "execution_count": 31,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 29,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 32,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "('p', 'p', 'p')"
       ]
      },
-     "execution_count": 29,
+     "execution_count": 32,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 30,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 33,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
   },
   {
    "cell_type": "code",
-   "execution_count": 31,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 34,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "17576"
       ]
      },
-     "execution_count": 31,
+     "execution_count": 34,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 32,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 35,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "(('a', 'a', 'b'), True)"
       ]
      },
-     "execution_count": 32,
+     "execution_count": 35,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 33,
+   "execution_count": 36,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 34,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 37,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "[('a', 'a', 'b')]"
       ]
      },
-     "execution_count": 34,
+     "execution_count": 37,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 35,
+   "execution_count": 38,
    "metadata": {
     "collapsed": true
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 36,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 39,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "[('a', 'a', 'b')]"
       ]
      },
-     "execution_count": 36,
+     "execution_count": 39,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 58,
+   "execution_count": 40,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 59,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 41,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "('e', 'l', 'e')"
       ]
      },
-     "execution_count": 59,
+     "execution_count": 41,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 39,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 42,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'dhnpforeeimgg'"
       ]
      },
-     "execution_count": 39,
+     "execution_count": 42,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 40,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 43,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "('j', 'e', 'o')"
       ]
      },
-     "execution_count": 40,
+     "execution_count": 43,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 41,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 44,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        " MenuIem(before='t', after='g', number=13)]"
       ]
      },
-     "execution_count": 41,
+     "execution_count": 44,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 42,
+   "execution_count": 45,
    "metadata": {
-    "collapsed": false,
     "scrolled": true
    },
    "outputs": [
        " ('z', 'z', 'k')]"
       ]
      },
-     "execution_count": 42,
+     "execution_count": 45,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 43,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 46,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "62"
       ]
      },
-     "execution_count": 43,
+     "execution_count": 46,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 44,
+   "execution_count": 47,
    "metadata": {
-    "collapsed": false,
     "scrolled": true
    },
    "outputs": [
        " ('y', 'n', 'c')]"
       ]
      },
-     "execution_count": 44,
+     "execution_count": 47,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 45,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 48,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        " ('y', 'n', 'c')]"
       ]
      },
-     "execution_count": 45,
+     "execution_count": 48,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 46,
+   "execution_count": 49,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 47,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 50,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "13"
       ]
      },
-     "execution_count": 47,
+     "execution_count": 50,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 48,
+   "execution_count": 51,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 49,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 52,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "Signal(bank='e', wire='e')"
       ]
      },
-     "execution_count": 49,
+     "execution_count": 52,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 50,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 53,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "True"
       ]
      },
-     "execution_count": 50,
+     "execution_count": 53,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 51,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 54,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "True"
       ]
      },
-     "execution_count": 51,
+     "execution_count": 54,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 52,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 55,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
   },
   {
    "cell_type": "code",
-   "execution_count": 53,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 56,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
   },
   {
    "cell_type": "code",
-   "execution_count": 54,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 57,
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "{frozenset({'m'}),\n",
+       "{frozenset({'t', 'x'}),\n",
        " frozenset({'i', 'n'}),\n",
-       " frozenset({'f', 'p'}),\n",
-       " frozenset({'t', 'x'}),\n",
+       " frozenset({'m'}),\n",
        " frozenset({'e', 'y'}),\n",
+       " frozenset({'f', 'p'}),\n",
        " frozenset({'b', 'g'})}"
       ]
      },
-     "execution_count": 54,
+     "execution_count": 57,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 55,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 58,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "True"
       ]
      },
-     "execution_count": 55,
+     "execution_count": 58,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 56,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 59,
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "({frozenset({1, 2}), frozenset({3, 4}), frozenset({2, 3})},\n",
+       "({frozenset({1, 2}), frozenset({2, 3}), frozenset({3, 4})},\n",
        " frozenset({1, 2}),\n",
        " frozenset({3, 4}),\n",
        " frozenset({2, 3}))"
       ]
      },
-     "execution_count": 56,
+     "execution_count": 59,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 57,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 60,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "False"
       ]
      },
-     "execution_count": 57,
+     "execution_count": 60,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 58,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 61,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "False"
       ]
      },
-     "execution_count": 58,
+     "execution_count": 61,
      "metadata": {},
      "output_type": "execute_result"
     }
     "{1, 2}.isdisjoint({1, 6})"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "collapsed": true
+   },
+   "source": [
+    "# Tsest"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 62,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'slgncszxltkzebghstgywdmpr'"
+      ]
+     },
+     "execution_count": 62,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "target_ct = ''.join(c.lower() for c in 'SLGNC SZXLT KZEBG HSTGY WDMPR' if c in string.ascii_letters)\n",
+    "target_ct"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 63,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'theyweredetectedbybritishshipsinclud'"
+      ]
+     },
+     "execution_count": 63,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "target_pt = ''.join(c.lower() for c in 'Theyw erede tecte d byBri tishs hipsi nclud' if c in string.ascii_letters)\n",
+    "target_pt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 64,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[MenuIem(before='w', after='c', number=1),\n",
+       " MenuIem(before='e', after='s', number=2),\n",
+       " MenuIem(before='r', after='z', number=3),\n",
+       " MenuIem(before='e', after='x', number=4),\n",
+       " MenuIem(before='d', after='l', number=5),\n",
+       " MenuIem(before='e', after='t', number=6),\n",
+       " MenuIem(before='t', after='k', number=7),\n",
+       " MenuIem(before='e', after='z', number=8),\n",
+       " MenuIem(before='c', after='e', number=9),\n",
+       " MenuIem(before='t', after='b', number=10),\n",
+       " MenuIem(before='e', after='g', number=11),\n",
+       " MenuIem(before='d', after='h', number=12),\n",
+       " MenuIem(before='b', after='s', number=13),\n",
+       " MenuIem(before='y', after='t', number=14),\n",
+       " MenuIem(before='b', after='g', number=15),\n",
+       " MenuIem(before='r', after='y', number=16),\n",
+       " MenuIem(before='i', after='w', number=17),\n",
+       " MenuIem(before='t', after='d', number=18),\n",
+       " MenuIem(before='i', after='m', number=19),\n",
+       " MenuIem(before='s', after='p', number=20)]"
+      ]
+     },
+     "execution_count": 64,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_menu = [MenuItem(p, c, i+1) for i, (p, c) in enumerate(zip(target_pt[4:24], target_ct[4:24]))]\n",
+    "tbt_menu"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 65,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "tbt_bombe = Bombe(wheel_iii_spec, wheel_i_spec, wheel_ii_spec, reflector_b_spec, \n",
+    "                         menu=tbt_menu)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 66,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[('k', 'r', 'n')]"
+      ]
+     },
+     "execution_count": 66,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_wheel_posns = run_multi_bombe(wheel_iii_spec, wheel_i_spec, wheel_ii_spec, reflector_b_spec, tbt_menu)\n",
+    "tbt_wheel_posns"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 67,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Signal(bank='e', wire='e')"
+      ]
+     },
+     "execution_count": 67,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_bombe.test_start"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 68,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "False\n",
+      "a : abcdefghi..lmnop.rst.v.x.z\n",
+      "b : a...ef.hi..lmnop.r.t...x..\n",
+      "c : a..defg.i..lmn.p.rst...x..\n",
+      "d : a.c.efghi.klmnopqrstu.wxyz\n",
+      "e : abcdefghijklmnopqrstuvwx.z\n",
+      "f : abcdefghijklmno.qrstuvwxyz\n",
+      "g : a.cdefghijklmnopqrstuvwxyz\n",
+      "h : ab.defghi...mnopqrstuvwxyz\n",
+      "i : abcdefghijklm.opqrstuvwxyz\n",
+      "j : ....efg.i..lmnop...t...x..\n",
+      "k : ...defg.i..lmn.p..st...x..\n",
+      "l : abcdefg.ijklmnopqrstuv.x..\n",
+      "m : abcdefghijkl.nopqrstuvwxyz\n",
+      "n : abcdefgh.jklmnopqrstuvwxyz\n",
+      "o : ab.defghij.lmnop.r.tuvwxyz\n",
+      "p : abcde.ghijklmnopqrstuvwxyz\n",
+      "q : ...defghi..lmn.p..st...x..\n",
+      "r : abcdefghi..lmnop.rst.v.x.z\n",
+      "s : a.cdefghi.klmn.pqr.tuvwxyz\n",
+      "t : abcdefghijklmnopqrstuvw.yz\n",
+      "u : ...defghi..lmnop..st...x..\n",
+      "v : a...efghi..lmnop.rst...x..\n",
+      "w : ...defghi...mnop..st...x..\n",
+      "x : abcdefghijklmnopqrs.uvwxyz\n",
+      "y : ...d.fghi...mnop..st...x..\n",
+      "z : a..defghi...mnop.rst...x..\n"
+     ]
+    }
+   ],
+   "source": [
+    "r = tbt_bombe.test(start_positions=('l', 's', 'd'))\n",
+    "print(r)\n",
+    "for b in sorted(w_bombe.banks):\n",
+    "    print(b, ': ', end='')\n",
+    "    for w in sorted(w_bombe.banks[b]):\n",
+    "        if w_bombe.banks[b][w]:\n",
+    "            print(w, end='')\n",
+    "        else:\n",
+    "            print('.', end='')\n",
+    "    print('')\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 69,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "set()"
+      ]
+     },
+     "execution_count": 69,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "ps = tbt_bombe.possible_plugboards()\n",
+    "ps"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.5.1+"
+   "version": "3.5.3"
   }
  },
  "nbformat": 4,
- "nbformat_minor": 0
+ "nbformat_minor": 1
 }
diff --git a/dtu_notch_big.jpg b/dtu_notch_big.jpg
new file mode 100644 (file)
index 0000000..dbdbbf1
Binary files /dev/null and b/dtu_notch_big.jpg differ
diff --git a/enigma-notch.jpg b/enigma-notch.jpg
new file mode 100644 (file)
index 0000000..31781e6
Binary files /dev/null and b/enigma-notch.jpg differ
diff --git a/enigma-old.ipynb b/enigma-old.ipynb
new file mode 100644 (file)
index 0000000..a9e832d
--- /dev/null
@@ -0,0 +1,2639 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Enigma machine\n",
+    "Specification from [Codes and Ciphers](http://www.codesandciphers.org.uk/enigma/rotorspec.htm) page.\n",
+    "\n",
+    "Example Enigma machines from [Louise Dale](http://enigma.louisedade.co.uk/enigma.html) (full simulation) and [EnigmaCo](http://enigmaco.de/enigma/enigma.html) (good animation of the wheels, but no ring settings).\n",
+    "\n",
+    "There's also the nice Enigma simulator for Android by [Franklin Heath](https://franklinheath.co.uk/2012/02/04/our-first-app-published-enigma-simulator/), available on the [Google Play store](https://play.google.com/store/apps/details?id=uk.co.franklinheath.enigmasim&hl=en_GB)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "import string\n",
+    "import collections\n",
+    "\n",
+    "cat = ''.join\n",
+    "\n",
+    "def clean(text): return cat(l.lower() for l in text if l in string.ascii_letters)\n",
+    "\n",
+    "def pos(letter): \n",
+    "    if letter in string.ascii_lowercase:\n",
+    "        return ord(letter) - ord('a')\n",
+    "    elif letter in string.ascii_uppercase:\n",
+    "        return ord(letter) - ord('A')\n",
+    "    else:\n",
+    "        return ''\n",
+    "    \n",
+    "def unpos(number): return chr(number % 26 + ord('a'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "wheel_i_spec = 'ekmflgdqvzntowyhxuspaibrcj'\n",
+    "wheel_ii_spec = 'ajdksiruxblhwtmcqgznpyfvoe'\n",
+    "wheel_iii_spec = 'bdfhjlcprtxvznyeiwgakmusqo'\n",
+    "wheel_iv_spec = 'esovpzjayquirhxlnftgkdcmwb'\n",
+    "wheel_v_spec = 'vzbrgityupsdnhlxawmjqofeck'\n",
+    "wheel_vi_spec = 'jpgvoumfyqbenhzrdkasxlictw'\n",
+    "wheel_vii_spec = 'nzjhgrcxmyswboufaivlpekqdt'\n",
+    "wheel_viii_spec = 'fkqhtlxocbjspdzramewniuygv'\n",
+    "beta_wheel_spec = 'leyjvcnixwpbqmdrtakzgfuhos'\n",
+    "gamma_wheel_spec = 'fsokanuerhmbtiycwlqpzxvgjd'\n",
+    "\n",
+    "wheel_i_pegs = ['q']\n",
+    "wheel_ii_pegs = ['e']\n",
+    "wheel_iii_pegs = ['v']\n",
+    "wheel_iv_pegs = ['j']\n",
+    "wheel_v_pegs = ['z']\n",
+    "wheel_vi_pegs = ['z', 'm']\n",
+    "wheel_vii_pegs = ['z', 'm']\n",
+    "wheel_viii_pegs = ['z', 'm']\n",
+    "\n",
+    "reflector_b_spec = 'ay br cu dh eq fs gl ip jx kn mo tz vw'\n",
+    "reflector_c_spec = 'af bv cp dj ei go hy kr lz mx nw tq su'"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "class LetterTransformer(object):\n",
+    "    def __init__(self, specification, raw_transform=False):\n",
+    "        if raw_transform:\n",
+    "            transform = specification\n",
+    "        else:\n",
+    "            transform = self.parse_specification(specification)\n",
+    "        self.validate_transform(transform)\n",
+    "        self.make_transform_map(transform)\n",
+    "    \n",
+    "    def parse_specification(self, specification):\n",
+    "        return list(zip(string.ascii_lowercase, clean(specification)))\n",
+    "        # return specification\n",
+    "    \n",
+    "    def validate_transform(self, transform):\n",
+    "        \"\"\"A set of pairs, of from-to\"\"\"\n",
+    "        if len(transform) != 26:\n",
+    "            raise ValueError(\"Transform specification has {} pairs, requires 26\".\n",
+    "                format(len(transform)))\n",
+    "        for p in transform:\n",
+    "            if len(p) != 2:\n",
+    "                raise ValueError(\"Not all mappings in transform \"\n",
+    "                    \"have two elements\")\n",
+    "        if len(set([p[0] for p in transform])) != 26:\n",
+    "            raise ValueError(\"Transform specification must list 26 origin letters\") \n",
+    "        if len(set([p[1] for p in transform])) != 26:\n",
+    "            raise ValueError(\"Transform specification must list 26 destination letters\") \n",
+    "\n",
+    "    def make_empty_transform(self):\n",
+    "        self.forward_map = [0] * 26\n",
+    "        self.backward_map = [0] * 26\n",
+    "            \n",
+    "    def make_transform_map(self, transform):\n",
+    "        self.make_empty_transform()\n",
+    "        for p in transform:\n",
+    "            self.forward_map[pos(p[0])] = pos(p[1])\n",
+    "            self.backward_map[pos(p[1])] = pos(p[0])\n",
+    "        return self.forward_map, self.backward_map\n",
+    "    \n",
+    "    def forward(self, letter):\n",
+    "        if letter in string.ascii_lowercase:\n",
+    "            return unpos(self.forward_map[pos(letter)])\n",
+    "        else:\n",
+    "            return ''\n",
+    "                \n",
+    "    def backward(self, letter):\n",
+    "        if letter in string.ascii_lowercase:\n",
+    "            return unpos(self.backward_map[pos(letter)])\n",
+    "        else:\n",
+    "            return ''"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[('z', 'a'),\n",
+       " ('a', 'b'),\n",
+       " ('b', 'c'),\n",
+       " ('c', 'd'),\n",
+       " ('d', 'e'),\n",
+       " ('e', 'f'),\n",
+       " ('f', 'g'),\n",
+       " ('g', 'h'),\n",
+       " ('h', 'i'),\n",
+       " ('i', 'j'),\n",
+       " ('j', 'k'),\n",
+       " ('k', 'l'),\n",
+       " ('l', 'm'),\n",
+       " ('m', 'n'),\n",
+       " ('n', 'o'),\n",
+       " ('o', 'p'),\n",
+       " ('p', 'q'),\n",
+       " ('q', 'r'),\n",
+       " ('r', 's'),\n",
+       " ('s', 't'),\n",
+       " ('t', 'u'),\n",
+       " ('u', 'v'),\n",
+       " ('v', 'w'),\n",
+       " ('w', 'x'),\n",
+       " ('x', 'y'),\n",
+       " ('y', 'z')]"
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tmap = [('z', 'a')] + [(l, string.ascii_lowercase[i+1]) for i, l in enumerate(string.ascii_lowercase[:-1])]\n",
+    "tmap"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'zyxwcabdefghijklmnopqrstuv'"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(collections.OrderedDict.fromkeys('zyxwc' + string.ascii_lowercase))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {
+    "collapsed": true,
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "tmap2 = list(zip(string.ascii_lowercase, cat(collections.OrderedDict.fromkeys('zyxwc' + string.ascii_lowercase))))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "([1,\n",
+       "  2,\n",
+       "  3,\n",
+       "  4,\n",
+       "  5,\n",
+       "  6,\n",
+       "  7,\n",
+       "  8,\n",
+       "  9,\n",
+       "  10,\n",
+       "  11,\n",
+       "  12,\n",
+       "  13,\n",
+       "  14,\n",
+       "  15,\n",
+       "  16,\n",
+       "  17,\n",
+       "  18,\n",
+       "  19,\n",
+       "  20,\n",
+       "  21,\n",
+       "  22,\n",
+       "  23,\n",
+       "  24,\n",
+       "  25,\n",
+       "  0],\n",
+       " [25,\n",
+       "  0,\n",
+       "  1,\n",
+       "  2,\n",
+       "  3,\n",
+       "  4,\n",
+       "  5,\n",
+       "  6,\n",
+       "  7,\n",
+       "  8,\n",
+       "  9,\n",
+       "  10,\n",
+       "  11,\n",
+       "  12,\n",
+       "  13,\n",
+       "  14,\n",
+       "  15,\n",
+       "  16,\n",
+       "  17,\n",
+       "  18,\n",
+       "  19,\n",
+       "  20,\n",
+       "  21,\n",
+       "  22,\n",
+       "  23,\n",
+       "  24])"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "lt = LetterTransformer(tmap, raw_transform = True)\n",
+    "assert(lt.forward_map == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0])\n",
+    "assert(lt.backward_map == [25, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24])\n",
+    "lt.forward_map, lt.backward_map"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "([25,\n",
+       "  24,\n",
+       "  23,\n",
+       "  22,\n",
+       "  2,\n",
+       "  0,\n",
+       "  1,\n",
+       "  3,\n",
+       "  4,\n",
+       "  5,\n",
+       "  6,\n",
+       "  7,\n",
+       "  8,\n",
+       "  9,\n",
+       "  10,\n",
+       "  11,\n",
+       "  12,\n",
+       "  13,\n",
+       "  14,\n",
+       "  15,\n",
+       "  16,\n",
+       "  17,\n",
+       "  18,\n",
+       "  19,\n",
+       "  20,\n",
+       "  21],\n",
+       " [5,\n",
+       "  6,\n",
+       "  4,\n",
+       "  7,\n",
+       "  8,\n",
+       "  9,\n",
+       "  10,\n",
+       "  11,\n",
+       "  12,\n",
+       "  13,\n",
+       "  14,\n",
+       "  15,\n",
+       "  16,\n",
+       "  17,\n",
+       "  18,\n",
+       "  19,\n",
+       "  20,\n",
+       "  21,\n",
+       "  22,\n",
+       "  23,\n",
+       "  24,\n",
+       "  25,\n",
+       "  3,\n",
+       "  2,\n",
+       "  1,\n",
+       "  0])"
+      ]
+     },
+     "execution_count": 8,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "lt = LetterTransformer(cat(collections.OrderedDict.fromkeys('zyxwc' + string.ascii_lowercase)))\n",
+    "assert(lt.forward_map == [25, 24, 23, 22, 2, 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21])\n",
+    "assert(lt.backward_map == [5, 6, 4, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 3, 2, 1, 0])\n",
+    "assert(cat(lt.forward(l) for l in string.ascii_lowercase) == 'zyxwcabdefghijklmnopqrstuv')\n",
+    "assert(cat(lt.backward(l) for l in string.ascii_lowercase) == 'fgehijklmnopqrstuvwxyzdcba')\n",
+    "lt.forward_map, lt.backward_map"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'zyxwcabdefghijklmnopqrstuv'"
+      ]
+     },
+     "execution_count": 9,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(lt.forward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'fgehijklmnopqrstuvwxyzdcba'"
+      ]
+     },
+     "execution_count": 10,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(lt.backward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "class Plugboard(LetterTransformer):\n",
+    "    def parse_specification(self, specification):\n",
+    "        return [tuple(clean(p)) for p in specification.split()]\n",
+    "    \n",
+    "    def validate_transform(self, transform):\n",
+    "        \"\"\"A set of pairs, of from-to\"\"\"\n",
+    "        for p in transform:\n",
+    "            if len(p) != 2:\n",
+    "                raise ValueError(\"Not all mappings in transform\"\n",
+    "                    \"have two elements\")\n",
+    "    \n",
+    "    def make_empty_transform(self):\n",
+    "        self.forward_map = list(range(26))\n",
+    "        self.backward_map = list(range(26))\n",
+    "        \n",
+    "    def make_transform_map(self, transform):\n",
+    "        expanded_transform = transform + [tuple(reversed(p)) for p in transform]\n",
+    "        return super(Plugboard, self).make_transform_map(expanded_transform)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "pb = Plugboard([('a', 'z'), ('b', 'y')], raw_transform=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'zycdefghijklmnopqrstuvwxba'"
+      ]
+     },
+     "execution_count": 13,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(pb.forward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'zycdefghijklmnopqrstuvwxba'"
+      ]
+     },
+     "execution_count": 14,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(pb.backward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "pb = Plugboard('az by')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('zycdefghijklmnopqrstuvwxba', 'zycdefghijklmnopqrstuvwxba')"
+      ]
+     },
+     "execution_count": 16,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(pb.forward(l) for l in string.ascii_lowercase), cat(pb.backward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('ugcdypblnzkhmisfrqoxavwtej', 'ugcdypblnzkhmisfrqoxavwtej')"
+      ]
+     },
+     "execution_count": 17,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "pb = Plugboard('ua pf rq so ni ey bg hl tx zj'.upper())\n",
+    "assert(pb.forward_map == pb.backward_map)\n",
+    "assert(pb.forward_map == [20, 6, 2, 3, 24, 15, 1, 11, 13, 25, 10, 7, 12, 8, 18, 5, 17, 16, 14, 23, 0, 21, 22, 19, 4, 9])\n",
+    "assert(cat(pb.forward(l) for l in string.ascii_lowercase) == 'ugcdypblnzkhmisfrqoxavwtej')\n",
+    "assert(cat(pb.backward(l) for l in string.ascii_lowercase) == 'ugcdypblnzkhmisfrqoxavwtej')\n",
+    "cat(pb.forward(l) for l in string.ascii_lowercase), cat(pb.backward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "class Reflector(Plugboard):\n",
+    "    def validate_transform(self, transform):\n",
+    "        if len(transform) != 13:\n",
+    "            raise ValueError(\"Reflector specification has {} pairs, requires 13\".\n",
+    "                format(len(transform)))\n",
+    "        if len(set([p[0] for p in transform] + \n",
+    "                    [p[1] for p in transform])) != 26:\n",
+    "            raise ValueError(\"Reflector specification does not contain 26 letters\")\n",
+    "        try:\n",
+    "            super(Reflector, self).validate_transform(transform)\n",
+    "        except ValueError as v:\n",
+    "            raise ValueError(\"Not all mappings in reflector have two elements\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[('a', 'y'),\n",
+       " ('b', 'r'),\n",
+       " ('c', 'u'),\n",
+       " ('d', 'h'),\n",
+       " ('e', 'q'),\n",
+       " ('f', 's'),\n",
+       " ('g', 'l'),\n",
+       " ('i', 'p'),\n",
+       " ('j', 'x'),\n",
+       " ('k', 'n'),\n",
+       " ('m', 'o'),\n",
+       " ('t', 'z'),\n",
+       " ('v', 'w')]"
+      ]
+     },
+     "execution_count": 19,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "# reflector_b_text = '(AY) (BR) (CU) (DH) (EQ) (FS) (GL) (IP) (JX) (KN) (MO) (TZ) (VW)'\n",
+    "reflector_b_l = [tuple(clean(p)) for p in reflector_b_spec.split()]\n",
+    "reflector_b_l"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "reflector_b = Reflector(reflector_b_spec)\n",
+    "assert(reflector_b.forward_map == reflector_b.backward_map)\n",
+    "assert(reflector_b.forward_map == [24, 17, 20, 7, 16, 18, 11, 3, 15, 23, 13, 6, 14, 10, 12, 8, 4, 1, 5, 25, 2, 22, 21, 9, 0, 19])\n",
+    "assert(cat(reflector_b.forward(l) for l in string.ascii_lowercase) == 'yruhqsldpxngokmiebfzcwvjat')\n",
+    "assert(cat(reflector_b.backward(l) for l in string.ascii_lowercase) == 'yruhqsldpxngokmiebfzcwvjat')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'yruhqsldpxngokmiebfzcwvjat'"
+      ]
+     },
+     "execution_count": 21,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(reflector_b.forward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 22,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "reflector_c = Reflector(reflector_c_spec)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'fvpjiaoyedrzxwgctkuqsbnmhl'"
+      ]
+     },
+     "execution_count": 23,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(reflector_c.forward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "class SimpleWheel(LetterTransformer):\n",
+    "    def __init__(self, transform, position='a', raw_transform=False):\n",
+    "        super(SimpleWheel, self).__init__(transform, raw_transform)\n",
+    "        self.set_position(position)\n",
+    "        \n",
+    "    def __getattribute__(self,name):\n",
+    "        if name=='position_l':\n",
+    "            return unpos(self.position)\n",
+    "        else:\n",
+    "            return object.__getattribute__(self, name)\n",
+    "    \n",
+    "    def set_position(self, position):\n",
+    "        self.position = ord(position) - ord('a')\n",
+    "    \n",
+    "    def forward(self, letter):\n",
+    "        if letter in string.ascii_lowercase:\n",
+    "            return unpos((self.forward_map[(pos(letter) + self.position) % 26] - self.position))\n",
+    "        else:\n",
+    "            return ''\n",
+    "                \n",
+    "    def backward(self, letter):\n",
+    "        if letter in string.ascii_lowercase:\n",
+    "            return unpos((self.backward_map[(pos(letter) + self.position) % 26] - self.position))\n",
+    "        else:\n",
+    "            return ''\n",
+    "        \n",
+    "    def advance(self):\n",
+    "        self.position = (self.position + 1) % 26\n",
+    "        return self.position"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 25,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[('a', 'e'),\n",
+       " ('b', 'k'),\n",
+       " ('c', 'm'),\n",
+       " ('d', 'f'),\n",
+       " ('e', 'l'),\n",
+       " ('f', 'g'),\n",
+       " ('g', 'd'),\n",
+       " ('h', 'q'),\n",
+       " ('i', 'v'),\n",
+       " ('j', 'z'),\n",
+       " ('k', 'n'),\n",
+       " ('l', 't'),\n",
+       " ('m', 'o'),\n",
+       " ('n', 'w'),\n",
+       " ('o', 'y'),\n",
+       " ('p', 'h'),\n",
+       " ('q', 'x'),\n",
+       " ('r', 'u'),\n",
+       " ('s', 's'),\n",
+       " ('t', 'p'),\n",
+       " ('u', 'a'),\n",
+       " ('v', 'i'),\n",
+       " ('w', 'b'),\n",
+       " ('x', 'r'),\n",
+       " ('y', 'c'),\n",
+       " ('z', 'j')]"
+      ]
+     },
+     "execution_count": 25,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "rotor_1_transform = list(zip(string.ascii_lowercase, 'EKMFLGDQVZNTOWYHXUSPAIBRCJ'.lower()))\n",
+    "rotor_1_transform"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "rotor_1_transform = list(zip(string.ascii_lowercase, 'EKMFLGDQVZNTOWYHXUSPAIBRCJ'.lower()))\n",
+    "wheel_1 = SimpleWheel(rotor_1_transform, raw_transform=True)\n",
+    "assert(cat(wheel_1.forward(l) for l in string.ascii_lowercase) == 'ekmflgdqvzntowyhxuspaibrcj')\n",
+    "assert(cat(wheel_1.backward(l) for l in string.ascii_lowercase) == 'uwygadfpvzbeckmthxslrinqoj')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('ekmflgdqvzntowyhxuspaibrcj', 'uwygadfpvzbeckmthxslrinqoj')"
+      ]
+     },
+     "execution_count": 27,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(wheel_1.forward(l) for l in string.ascii_lowercase), cat(wheel_1.backward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 28,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'a'"
+      ]
+     },
+     "execution_count": 28,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel_1.position_l"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 29,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "wheel_2 = SimpleWheel(wheel_ii_spec)\n",
+    "assert(cat(wheel_2.forward(l) for l in string.ascii_lowercase) == 'ajdksiruxblhwtmcqgznpyfvoe')\n",
+    "assert(cat(wheel_2.backward(l) for l in string.ascii_lowercase) == 'ajpczwrlfbdkotyuqgenhxmivs')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 30,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('ajdksiruxblhwtmcqgznpyfvoe', 'ajpczwrlfbdkotyuqgenhxmivs')"
+      ]
+     },
+     "execution_count": 30,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(wheel_2.forward(l) for l in string.ascii_lowercase), cat(wheel_2.backward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 31,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('bdfhjlcprtxvznyeiwgakmusqo', 'tagbpcsdqeufvnzhyixjwlrkom')"
+      ]
+     },
+     "execution_count": 31,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel_3 = SimpleWheel(wheel_iii_spec)\n",
+    "wheel_3.set_position('a')\n",
+    "wheel_3.advance()\n",
+    "assert(cat(wheel_3.forward(l) for l in string.ascii_lowercase) == 'cegikboqswuymxdhvfzjltrpna')\n",
+    "assert(cat(wheel_3.backward(l) for l in string.ascii_lowercase) == 'zfaobrcpdteumygxhwivkqjnls')\n",
+    "assert(wheel_3.position == 1)\n",
+    "assert(wheel_3.position_l == 'b')\n",
+    "\n",
+    "for _ in range(24): wheel_3.advance()\n",
+    "assert(wheel_3.position == 25)\n",
+    "assert(wheel_3.position_l == 'z')\n",
+    "assert(cat(wheel_3.forward(l) for l in string.ascii_lowercase) == 'pcegikmdqsuywaozfjxhblnvtr')\n",
+    "assert(cat(wheel_3.backward(l) for l in string.ascii_lowercase) == 'nubhcqdterfvgwoaizjykxmslp')\n",
+    "\n",
+    "wheel_3.advance()\n",
+    "assert(wheel_3.position == 0)\n",
+    "assert(wheel_3.position_l == 'a')\n",
+    "assert(cat(wheel_3.forward(l) for l in string.ascii_lowercase) == 'bdfhjlcprtxvznyeiwgakmusqo')\n",
+    "assert(cat(wheel_3.backward(l) for l in string.ascii_lowercase) == 'tagbpcsdqeufvnzhyixjwlrkom')\n",
+    "\n",
+    "cat(wheel_3.forward(l) for l in string.ascii_lowercase), cat(wheel_3.backward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 32,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "class Wheel(SimpleWheel):\n",
+    "    def __init__(self, transform, ring_peg_letters, ring_setting=1, position='a', raw_transform=False):\n",
+    "        self.ring_peg_letters = ring_peg_letters\n",
+    "        self.ring_setting = ring_setting\n",
+    "        super(Wheel, self).__init__(transform, position=position, raw_transform=raw_transform)\n",
+    "        self.set_position(position)\n",
+    "        \n",
+    "    def __getattribute__(self,name):\n",
+    "        if name=='position_l':\n",
+    "            return unpos(self.position + self.ring_setting - 1)\n",
+    "        else:\n",
+    "            return object.__getattribute__(self, name)\n",
+    "\n",
+    "    def set_position(self, position):\n",
+    "        self.position = (pos(position) - self.ring_setting + 1) % 26\n",
+    "        # self.position_l = position\n",
+    "        self.peg_positions = [(pos(p) - pos(position)) % 26  for p in self.ring_peg_letters]\n",
+    "        \n",
+    "    def advance(self):\n",
+    "        super(Wheel, self).advance()\n",
+    "        self.peg_positions = [(p - 1) % 26 for p in self.peg_positions]\n",
+    "        # self.position_l = unpos(self.position + self.ring_setting - 1)\n",
+    "        return self.position"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 33,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "wheel_3 = Wheel(wheel_iii_spec, wheel_iii_pegs, position='b', ring_setting=1)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 34,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(1, [20])"
+      ]
+     },
+     "execution_count": 34,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel_3.position, wheel_3.peg_positions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 35,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(25, [24, 11])"
+      ]
+     },
+     "execution_count": 35,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel_6 = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', ring_setting=3)\n",
+    "wheel_6.position, wheel_6.peg_positions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 36,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "0 [23, 10]\n",
+      "1 [22, 9]\n",
+      "2 [21, 8]\n",
+      "3 [20, 7]\n",
+      "4 [19, 6]\n",
+      "5 [18, 5]\n",
+      "6 [17, 4]\n",
+      "7 [16, 3]\n",
+      "8 [15, 2]\n",
+      "9 [14, 1]\n",
+      "10 [13, 0]\n",
+      "11 [12, 25]\n",
+      "12 [11, 24]\n",
+      "13 [10, 23]\n",
+      "14 [9, 22]\n",
+      "15 [8, 21]\n",
+      "16 [7, 20]\n",
+      "17 [6, 19]\n",
+      "18 [5, 18]\n",
+      "19 [4, 17]\n",
+      "20 [3, 16]\n",
+      "21 [2, 15]\n",
+      "22 [1, 14]\n",
+      "23 [0, 13]\n",
+      "24 [25, 12]\n",
+      "25 [24, 11]\n",
+      "0 [23, 10]\n"
+     ]
+    }
+   ],
+   "source": [
+    "for _ in range(27):\n",
+    "    wheel_6.advance()\n",
+    "    print(wheel_6.position, wheel_6.peg_positions)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 37,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "wheel_3 = Wheel(wheel_iii_spec, wheel_iii_pegs, position='b', ring_setting=1)\n",
+    "assert(wheel_3.position == 1)\n",
+    "assert(wheel_3.peg_positions == [20])\n",
+    "assert(wheel_3.position_l == 'b')\n",
+    "wheel_3.advance()\n",
+    "assert(wheel_3.position == 2)\n",
+    "assert(wheel_3.peg_positions == [19])\n",
+    "assert(wheel_3.position_l == 'c')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 38,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "wheel_6 = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', ring_setting=3)\n",
+    "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'xkqhwpvngzrcfoiaselbtymjdu')\n",
+    "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'ptlyrmidoxbswhnfckquzgeavj')\n",
+    "assert(wheel_6.position == 25)\n",
+    "assert(11 in wheel_6.peg_positions)\n",
+    "assert(24 in wheel_6.peg_positions)\n",
+    "assert(wheel_6.position_l == 'b')\n",
+    "\n",
+    "wheel_6.advance()\n",
+    "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'jpgvoumfyqbenhzrdkasxlictw')\n",
+    "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'skxqlhcnwarvgmebjptyfdzuio')\n",
+    "assert(wheel_6.position == 0)\n",
+    "assert(10 in wheel_6.peg_positions)\n",
+    "assert(23 in wheel_6.peg_positions)\n",
+    "assert(wheel_6.position_l == 'c')\n",
+    "\n",
+    "for _ in range(22): wheel_6.advance()\n",
+    "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'mgxantkzsyqjcufirldvhoewbp')\n",
+    "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'dymswobuplgraevzkqifntxcjh')\n",
+    "assert(wheel_6.position == 22)\n",
+    "assert(1 in wheel_6.peg_positions)\n",
+    "assert(14 in wheel_6.peg_positions)\n",
+    "assert(wheel_6.position_l == 'y')\n",
+    "\n",
+    "wheel_6.advance()\n",
+    "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'fwzmsjyrxpibtehqkcugndvaol')\n",
+    "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'xlrvnatokfqzduyjphemswbigc')\n",
+    "assert(wheel_6.position == 23)\n",
+    "assert(0 in wheel_6.peg_positions)\n",
+    "assert(13 in wheel_6.peg_positions)\n",
+    "assert(wheel_6.position_l == 'z')\n",
+    "\n",
+    "wheel_6.advance()\n",
+    "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'vylrixqwohasdgpjbtfmcuznke')\n",
+    "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'kqumzsnjepyctxiogdlrvahfbw')\n",
+    "assert(wheel_6.position == 24)\n",
+    "assert(25 in wheel_6.peg_positions)\n",
+    "assert(12 in wheel_6.peg_positions)\n",
+    "assert(wheel_6.position_l == 'a')\n",
+    "\n",
+    "wheel_6.advance()\n",
+    "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'xkqhwpvngzrcfoiaselbtymjdu')\n",
+    "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'ptlyrmidoxbswhnfckquzgeavj')\n",
+    "assert(wheel_6.position == 25)\n",
+    "assert(24 in wheel_6.peg_positions)\n",
+    "assert(11 in wheel_6.peg_positions)\n",
+    "assert(wheel_6.position_l == 'b')\n",
+    "\n",
+    "wheel_6.advance()\n",
+    "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'jpgvoumfyqbenhzrdkasxlictw')\n",
+    "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'skxqlhcnwarvgmebjptyfdzuio')\n",
+    "assert(wheel_6.position == 0)\n",
+    "assert(23 in wheel_6.peg_positions)\n",
+    "assert(10 in wheel_6.peg_positions)\n",
+    "assert(wheel_6.position_l == 'c')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 39,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(0, 'c', [23, 10])"
+      ]
+     },
+     "execution_count": 39,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel_6.position, wheel_6.position_l, wheel_6.peg_positions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 83,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "class Enigma(object):\n",
+    "    def __init__(self, reflector_spec,\n",
+    "                 left_wheel_spec, left_wheel_pegs,\n",
+    "                 middle_wheel_spec, middle_wheel_pegs,\n",
+    "                 right_wheel_spec, right_wheel_pegs,\n",
+    "                 left_ring_setting, middle_ring_setting, right_ring_setting,\n",
+    "                 plugboard_setting):\n",
+    "        self.reflector = Reflector(reflector_spec)\n",
+    "        self.left_wheel = Wheel(left_wheel_spec, left_wheel_pegs, ring_setting=left_ring_setting)\n",
+    "        self.middle_wheel = Wheel(middle_wheel_spec, middle_wheel_pegs, ring_setting=middle_ring_setting)\n",
+    "        self.right_wheel = Wheel(right_wheel_spec, right_wheel_pegs, ring_setting=right_ring_setting)\n",
+    "        self.plugboard = Plugboard(plugboard_setting)\n",
+    "        \n",
+    "    def __getattribute__(self,name):\n",
+    "        if name=='wheel_positions':\n",
+    "            return self.left_wheel.position, self.middle_wheel.position, self.right_wheel.position \n",
+    "        elif name=='wheel_positions_l':\n",
+    "            return self.left_wheel.position_l, self.middle_wheel.position_l, self.right_wheel.position_l \n",
+    "        elif name=='peg_positions':\n",
+    "            return self.left_wheel.peg_positions, self.middle_wheel.peg_positions, self.right_wheel.peg_positions\n",
+    "        else:\n",
+    "            return object.__getattribute__(self, name)\n",
+    "\n",
+    "    \n",
+    "    def set_wheels(self, left_wheel_position, middle_wheel_position, right_wheel_position):\n",
+    "        self.left_wheel.set_position(left_wheel_position)\n",
+    "        self.middle_wheel.set_position(middle_wheel_position)\n",
+    "        self.right_wheel.set_position(right_wheel_position)\n",
+    "        \n",
+    "    def lookup(self, letter):\n",
+    "        a = self.plugboard.forward(letter)\n",
+    "        b = self.right_wheel.forward(a)\n",
+    "        c = self.middle_wheel.forward(b)\n",
+    "        d = self.left_wheel.forward(c)\n",
+    "        e = self.reflector.forward(d)\n",
+    "        f = self.left_wheel.backward(e)\n",
+    "        g = self.middle_wheel.backward(f)\n",
+    "        h = self.right_wheel.backward(g)\n",
+    "        i = self.plugboard.backward(h)\n",
+    "        return i\n",
+    "    \n",
+    "    def advance(self):\n",
+    "        advance_middle = False\n",
+    "        advance_left = False\n",
+    "        if 0 in self.right_wheel.peg_positions:\n",
+    "            advance_middle = True\n",
+    "        if 0 in self.middle_wheel.peg_positions:\n",
+    "            advance_left = True\n",
+    "            advance_middle = True\n",
+    "        self.right_wheel.advance()\n",
+    "        if advance_middle: self.middle_wheel.advance()\n",
+    "        if advance_left: self.left_wheel.advance()\n",
+    "            \n",
+    "    def encipher_letter(self, letter):\n",
+    "        self.advance()\n",
+    "        return self.lookup(letter)\n",
+    "    \n",
+    "    def encipher(self, message, debug=False):\n",
+    "        enciphered = ''\n",
+    "        for letter in clean(message):\n",
+    "            enciphered += self.encipher_letter(letter)\n",
+    "            if debug:\n",
+    "                print('Wheels now', list(self.wheel_positions_l), 'enciphering {} -> {}'.format(letter, self.lookup(letter)))\n",
+    "        return enciphered"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 82,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\"('a', 'b', 'c')\""
+      ]
+     },
+     "execution_count": 82,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "sfsdf = ('a', 'b', 'c')\n",
+    "str(sfsdf)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 41,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "enigma = Enigma(reflector_b_spec, \n",
+    "                wheel_i_spec, wheel_i_pegs,\n",
+    "                wheel_ii_spec, wheel_ii_pegs,\n",
+    "                wheel_iii_spec, wheel_iii_pegs,\n",
+    "                1, 1, 1,\n",
+    "                '')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 42,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'u'"
+      ]
+     },
+     "execution_count": 42,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "enigma.lookup('a')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 43,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'a'"
+      ]
+     },
+     "execution_count": 43,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "enigma.lookup('u')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 44,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'uejobtpzwcnsrkdgvmlfaqiyxh'"
+      ]
+     },
+     "execution_count": 44,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(enigma.lookup(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 45,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "0 :: a a a ; [16] [4] [21] uejobtpzwcnsrkdgvmlfaqiyxh\n",
+      "1 :: a a b ; [16] [4] [20] baqmfexihswpdytlcvjozrkgnu\n",
+      "2 :: a a c ; [16] [4] [19] djralkwpobfeyqihncxzvugsmt\n",
+      "3 :: a a d ; [16] [4] [18] zlejcuitgdmbkonsvxphfqyrwa\n",
+      "4 :: a a e ; [16] [4] [17] gcblwtakqzhdosmxiunfryepvj\n",
+      "5 :: a a f ; [16] [4] [16] osnirgfmdpvuhcajwebxlkqtzy\n",
+      "6 :: a a g ; [16] [4] [15] wymvnqzjlhoicekuftxrpdasbg\n",
+      "7 :: a a h ; [16] [4] [14] cjafkdztpbeuormiwnvhlsqyxg\n",
+      "8 :: a a i ; [16] [4] [13] xijuyslvbczgnmqwotfrdhpaek\n",
+      "9 :: a a j ; [16] [4] [12] lfzrwbytjisaovmuxdkhpneqgc\n",
+      "10 :: a a k ; [16] [4] [11] tkezcqynuwbpvhslfxoaimjrgd\n",
+      "11 :: a a l ; [16] [4] [10] kiwfnduxbsaotelqpvjmgrchzy\n",
+      "12 :: a a m ; [16] [4] [9] sfkutbpoxycrnmhgwlaedzqijv\n",
+      "13 :: a a n ; [16] [4] [8] baqwlkhgrsfextpocijnvudmzy\n",
+      "14 :: a a o ; [16] [4] [7] teofbdzxqkjyrscvimnawpuhlg\n",
+      "15 :: a a p ; [16] [4] [6] mhypswrbzxqvaondkgeutlfjci\n",
+      "16 :: a a q ; [16] [4] [5] cpasnrhgkuixzevbyfdwjotlqm\n",
+      "17 :: a a r ; [16] [4] [4] dlfatcjwygvbnmzrxpueskhqio\n",
+      "18 :: a a s ; [16] [4] [3] lxymzjuqtfpadsrkhonigwvbce\n",
+      "19 :: a a t ; [16] [4] [2] puvioztjdhxmlyeawsrgbcqknf\n",
+      "20 :: a a u ; [16] [4] [1] baigpldqcowfyzjehvtsxrkumn\n",
+      "21 :: a a v ; [16] [4] [0] mnvfydiwgzsoablrxpkutchqej\n",
+      "22 :: a b w ; [16] [3] [25] ulfopcykswhbzvderqixanjtgm\n",
+      "23 :: a b x ; [16] [3] [24] qmwftdyovursbzhxaklejicpgn\n",
+      "24 :: a b y ; [16] [3] [23] oljmzxrvucybdqasngpwihtfke\n",
+      "25 :: a b z ; [16] [3] [22] fwevcalzxutgysrqponkjdbimh\n"
+     ]
+    }
+   ],
+   "source": [
+    "enigma.set_wheels('a', 'a', 'a')\n",
+    "for i in range(26):\n",
+    "    print(i, '::', \n",
+    "          enigma.left_wheel.position_l, enigma.middle_wheel.position_l, enigma.right_wheel.position_l, ';',\n",
+    "          enigma.left_wheel.peg_positions, enigma.middle_wheel.peg_positions, enigma.right_wheel.peg_positions, \n",
+    "         cat(enigma.lookup(l) for l in string.ascii_lowercase))\n",
+    "    enigma.advance()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 46,
+   "metadata": {
+    "collapsed": true,
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "enigma.set_wheels('a', 'a', 't')\n",
+    "assert(enigma.wheel_positions == (0, 0, 19))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'aat')\n",
+    "assert(enigma.peg_positions == ([16], [4], [2]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'puvioztjdhxmlyeawsrgbcqknf')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (0, 0, 20))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'aau')\n",
+    "assert(enigma.peg_positions == ([16], [4], [1]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'baigpldqcowfyzjehvtsxrkumn')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (0, 0, 21))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'aav')\n",
+    "assert(enigma.peg_positions == ([16], [4], [0]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'mnvfydiwgzsoablrxpkutchqej')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (0, 1, 22))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'abw')\n",
+    "assert(enigma.peg_positions == ([16], [3], [25]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ulfopcykswhbzvderqixanjtgm')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (0, 1, 23))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'abx')\n",
+    "assert(enigma.peg_positions == ([16], [3], [24]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qmwftdyovursbzhxaklejicpgn')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (0, 1, 24))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'aby')\n",
+    "assert(enigma.peg_positions == ([16], [3], [23]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'oljmzxrvucybdqasngpwihtfke')\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 47,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "(1, 5, 24) ('b', 'f', 'y') ([15], [25], [23]) baknstqzrmcxjdvygiefwoulph\n"
+     ]
+    }
+   ],
+   "source": [
+    "enigma.set_wheels('a', 'd', 't')\n",
+    "assert(enigma.wheel_positions == (0, 3, 19))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'adt')\n",
+    "assert(enigma.peg_positions == ([16], [1], [2]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'zcbpqxwsjiuonmldethrkygfva')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (0, 3, 20))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'adu')\n",
+    "assert(enigma.peg_positions == ([16], [1], [1]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ehprawjbngotxikcsdqlzyfmvu')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (0, 3, 21))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'adv')\n",
+    "assert(enigma.peg_positions == ([16], [1], [0]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'eqzxarpihmnvjkwgbfuyslodtc')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (0, 4, 22))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'aew')\n",
+    "assert(enigma.peg_positions == ([16], [0], [25]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qedcbtpluzmhkongavwfirsyxj')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (1, 5, 23))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'bfx')\n",
+    "assert(enigma.peg_positions == ([15], [25], [24]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'iwuedhsfazqxytvrkpgncoblmj')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (1, 5, 24))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'bfy')\n",
+    "assert(enigma.peg_positions == ([15], [25], [23]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'baknstqzrmcxjdvygiefwoulph')\n",
+    "\n",
+    "print(enigma.wheel_positions, enigma.wheel_positions_l, enigma.peg_positions, \n",
+    "         cat(enigma.lookup(l) for l in string.ascii_lowercase))\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 48,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'olpfhnvflyn'"
+      ]
+     },
+     "execution_count": 48,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "enigma.set_wheels('a', 'a', 'a')\n",
+    "ct = enigma.encipher('testmessage')\n",
+    "assert(ct == 'olpfhnvflyn')\n",
+    "ct"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 49,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'lawnjgpwjik'"
+      ]
+     },
+     "execution_count": 49,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "enigma.set_wheels('a', 'd', 't')\n",
+    "ct = enigma.encipher('testmessage')\n",
+    "assert(ct == 'lawnjgpwjik')\n",
+    "ct"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 50,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 5))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bff')\n",
+      "assert(enigma.peg_positions == ([15], [25], [16]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'zpqiogfsdlmjkyebcvhxwrutna')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 6))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfg')\n",
+      "assert(enigma.peg_positions == ([15], [25], [15]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'fjmnwayslbxicdpouthrqzekgv')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 7))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfh')\n",
+      "assert(enigma.peg_positions == ([15], [25], [14]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'csafzdyloxuhnmitwvbpkrqjge')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 8))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfi')\n",
+      "assert(enigma.peg_positions == ([15], [25], [13]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'kihyvulcbtagwrqzonxjfemsdp')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 9))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfj')\n",
+      "assert(enigma.peg_positions == ([15], [25], [12]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'pgzrytbksqhwxvuajdifonlmec')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 10))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfk')\n",
+      "assert(enigma.peg_positions == ([15], [25], [11]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'fkirsazncwbvyhpoudexqljtmg')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 11))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfl')\n",
+      "assert(enigma.peg_positions == ([15], [25], [10]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'mhkronubsvctafeqpdilgjxwzy')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 12))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfm')\n",
+      "assert(enigma.peg_positions == ([15], [25], [9]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'gnkuoxarzycmlbetvhwpdqsfji')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 13))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfn')\n",
+      "assert(enigma.peg_positions == ([15], [25], [8]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'bainslqkcxhfudpogtermwvjzy')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 14))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfo')\n",
+      "assert(enigma.peg_positions == ([15], [25], [7]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'xemfbdnwjitycgzusvqkprhalo')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 15))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfp')\n",
+      "assert(enigma.peg_positions == ([15], [25], [6]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qixksmhgbtdvfonrapejwluczy')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 16))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfq')\n",
+      "assert(enigma.peg_positions == ([15], [25], [5]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cgaulmbskwiefrtzynhodxjvqp')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 17))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfr')\n",
+      "assert(enigma.peg_positions == ([15], [25], [4]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'iwqfldszaxvenmyrcpgutkbjoh')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 18))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfs')\n",
+      "assert(enigma.peg_positions == ([15], [25], [3]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'vxykrjilgfdhqtusmepnoazbcw')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 19))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bft')\n",
+      "assert(enigma.peg_positions == ([15], [25], [2]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ieysbvkjahgmlpxnwtdrzfqocu')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 20))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfu')\n",
+      "assert(enigma.peg_positions == ([15], [25], [1]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'baihkjvdcfepywsltxoqzgnrmu')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 21))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfv')\n",
+      "assert(enigma.peg_positions == ([15], [25], [0]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'bayjtrilgdshvzuwxfkeompqcn')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 6, 22))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bgw')\n",
+      "assert(enigma.peg_positions == ([15], [24], [25]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'rszqohpfxyutvwegdablkmnijc')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 6, 23))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bgx')\n",
+      "assert(enigma.peg_positions == ([15], [24], [24]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'nfoxhbzeyrwqpacmljtsvukdig')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 6, 24))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bgy')\n",
+      "assert(enigma.peg_positions == ([15], [24], [23]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'czaogmeihtuqfsdxlwnjkyrpvb')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 6, 25))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bgz')\n",
+      "assert(enigma.peg_positions == ([15], [24], [22]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'omwgysdjkhizbxarupfvqtcnel')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 6, 0))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bga')\n",
+      "assert(enigma.peg_positions == ([15], [24], [21]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cxafmdrzoqutepinjgvlksybwh')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 6, 1))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bgb')\n",
+      "assert(enigma.peg_positions == ([15], [24], [20]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'jymvnrxkoahwceiuzftspdlgbq')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 6, 2))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bgc')\n",
+      "assert(enigma.peg_positions == ([15], [24], [19]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'uzlyiqwrestcnmxvfhjkapgodb')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 6, 3))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bgd')\n",
+      "assert(enigma.peg_positions == ([15], [24], [18]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'veosbuhgpzqynmcikwdxfartlj')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 6, 4))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bge')\n",
+      "assert(enigma.peg_positions == ([15], [24], [17]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ydhbmtrclxsiezpougkfqwvjan')\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "\n",
+    "for i in range(26):\n",
+    "    enigma.advance()\n",
+    "    print('enigma.advance()')\n",
+    "    print(\"assert(enigma.wheel_positions == {})\".format(enigma.wheel_positions))\n",
+    "    print(\"assert(cat(enigma.wheel_positions_l) == '{}')\".format(cat(enigma.wheel_positions_l)))\n",
+    "    print(\"assert(enigma.peg_positions == {})\".format(enigma.peg_positions))\n",
+    "    print(\"assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == '{}')\".format(cat(enigma.lookup(l) for l in string.ascii_lowercase)))\n",
+    "    print()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 51,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'bahxvfrpdc'"
+      ]
+     },
+     "execution_count": 51,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "enigma.set_wheels('a', 'd', 't')\n",
+    "ct = enigma.encipher('hellothere')\n",
+    "assert(ct == 'bahxvfrpdc')\n",
+    "ct"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 52,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'kvmmwrlqlqsqpeugjrcxzwpfyiyybwloewrouvkpoztceuwtfjzqwpbqldttsr'"
+      ]
+     },
+     "execution_count": 52,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "enigma.set_wheels('b', 'd', 'q')\n",
+    "ct = enigma.encipher('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')\n",
+    "assert(ct == 'kvmmwrlqlqsqpeugjrcxzwpfyiyybwloewrouvkpoztceuwtfjzqwpbqldttsr')\n",
+    "assert(enigma.left_wheel.position_l == 'c')\n",
+    "assert(enigma.middle_wheel.position_l == 'h')\n",
+    "assert(enigma.right_wheel.position_l == 'a')\n",
+    "ct"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 53,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'c'"
+      ]
+     },
+     "execution_count": 53,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "enigma.left_wheel.position_l"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 54,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "# Setting sheet line 31 from http://www.codesandciphers.org.uk/enigma/enigma3.htm\n",
+    "# Enigma simulation settings are \n",
+    "# http://enigma.louisedade.co.uk/enigma.html?m3;b;b153;AFTX;AJFE;AU-BG-EY-FP-HL-IN-JZ-OS-QR-TX\n",
+    "w_enigma = Enigma(reflector_b_spec, \n",
+    "                wheel_i_spec, wheel_i_pegs,\n",
+    "                wheel_v_spec, wheel_v_pegs,\n",
+    "                wheel_iii_spec, wheel_iii_pegs,\n",
+    "                6, 20, 24,\n",
+    "                'ua pf rq so ni ey bg hl tx zj')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 55,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "# Setting sheet line 31 from http://www.codesandciphers.org.uk/enigma/enigma3.htm\n",
+    "# Enigma simulation settings are \n",
+    "# http://enigma.louisedade.co.uk/enigma.html?m3;b;b153;AFTX;AJFE;AU-BG-EY-FP-HL-IN-JZ-OS-QR-TX\n",
+    "enigma = Enigma(reflector_b_spec, \n",
+    "                wheel_i_spec, wheel_i_pegs,\n",
+    "                wheel_v_spec, wheel_v_pegs,\n",
+    "                wheel_iii_spec, wheel_iii_pegs,\n",
+    "                6, 20, 24,\n",
+    "                'ua pf rq so ni ey bg hl tx zj')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 56,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "enigma.set_wheels('j', 'e', 'u')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (4, 11, 24))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'jev')\n",
+    "assert(enigma.peg_positions == ([7], [21], [0]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'mvqjlyowkdieasgzcunxrbhtfp')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (4, 12, 25))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'jfw')\n",
+    "assert(enigma.peg_positions == ([7], [20], [25]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'sjolzuyvrbwdpxcmtiaqfhknge')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (4, 12, 0))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'jfx')\n",
+    "assert(enigma.peg_positions == ([7], [20], [24]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qrxedkoywufmlvgsabpzjnicht')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (4, 12, 1))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'jfy')\n",
+    "assert(enigma.peg_positions == ([7], [20], [23]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'hpsukliagqefwvtbjxcodnmrzy')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (4, 12, 2))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'jfz')\n",
+    "assert(enigma.peg_positions == ([7], [20], [22]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'zevnbpyqowrtxdifhkulscjmga')\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 57,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "enigma.set_wheels('i', 'd', 'z')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 3))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'ida')\n",
+    "assert(enigma.peg_positions == ([8], [22], [21]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ikhpqrvcambzjondefwyxgsutl')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 4))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idb')\n",
+    "assert(enigma.peg_positions == ([8], [22], [20]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cdabskhgzwfmlqvunyexpojtri')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 5))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idc')\n",
+    "assert(enigma.peg_positions == ([8], [22], [19]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'pcbwiqhgemyvjsuaftnroldzkx')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 6))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idd')\n",
+    "assert(enigma.peg_positions == ([8], [22], [18]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'xcbfvdnouptmlghjzwykierasq')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 7))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'ide')\n",
+    "assert(enigma.peg_positions == ([8], [22], [17]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'xfvglbdynuseriwqpmkzjcoaht')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 8))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idf')\n",
+    "assert(enigma.peg_positions == ([8], [22], [16]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'tfpqlbouynsewjgcdxkahzmriv')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 9))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idg')\n",
+    "assert(enigma.peg_positions == ([8], [22], [15]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cjaunvlwtbygzexrspqidfhokm')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 10))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idh')\n",
+    "assert(enigma.peg_positions == ([8], [22], [14]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'yltxkrqvowebzpingfucshjdam')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 11))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idi')\n",
+    "assert(enigma.peg_positions == ([8], [22], [13]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'myktluzrnxceaiqsohpdfwvjbg')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 12))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idj')\n",
+    "assert(enigma.peg_positions == ([8], [22], [12]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'pynjrmiugdqxfcvakewzhoslbt')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 13))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idk')\n",
+    "assert(enigma.peg_positions == ([8], [22], [11]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'mwvedyplnoxhaijgrqtszcbkfu')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 14))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idl')\n",
+    "assert(enigma.peg_positions == ([8], [22], [10]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qcbrfeutvoxpnmjladzhgiykws')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 15))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idm')\n",
+    "assert(enigma.peg_positions == ([8], [22], [9]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'dnoahryetsmukbcvwfjilpqzgx')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 16))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idn')\n",
+    "assert(enigma.peg_positions == ([8], [22], [8]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'nidcfehgbqsovalyjzkxwmutpr')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 17))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'ido')\n",
+    "assert(enigma.peg_positions == ([8], [22], [7]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'joifxdulcarhzpbntkwqgysevm')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 18))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idp')\n",
+    "assert(enigma.peg_positions == ([8], [22], [6]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ptnlsxvozmwdjchayuebrgkfqi')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 19))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idq')\n",
+    "assert(enigma.peg_positions == ([8], [22], [5]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'slwopzqnmxybihdeguavrtcjkf')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 20))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idr')\n",
+    "assert(enigma.peg_positions == ([8], [22], [4]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'hcbedwlamzogixkytsrqvufnpj')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 21))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'ids')\n",
+    "assert(enigma.peg_positions == ([8], [22], [3]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'odxbjwzrmelkisavuhnyqpfctg')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 22))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idt')\n",
+    "assert(enigma.peg_positions == ([8], [22], [2]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'udgbfeclrwnhxksvtioqapjmzy')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 23))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idu')\n",
+    "assert(enigma.peg_positions == ([8], [22], [1]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'nrdczqxmowvshaiufblypkjgte')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 10, 24))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'idv')\n",
+    "assert(enigma.peg_positions == ([8], [22], [0]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'hkifjdoacebqtzgulyvmpsxwrn')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 11, 25))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'iew')\n",
+    "assert(enigma.peg_positions == ([8], [21], [25]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'yptzuhofqvnmlkgbixwcejsrad')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 11, 0))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'iex')\n",
+    "assert(enigma.peg_positions == ([8], [21], [24]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'vkdcwhqfjibzsptngumoraeyxl')\n",
+    "\n",
+    "enigma.advance()\n",
+    "assert(enigma.wheel_positions == (3, 11, 1))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'iey')\n",
+    "assert(enigma.peg_positions == ([8], [21], [23]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'wenpbqrouxlkychdfgzvitajms')\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 58,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('verylongtestmessagewithanextrabitofmessageforgoodmeasure',\n",
+       " (3, 12, 6),\n",
+       " ('i', 'f', 'd'),\n",
+       " ([8], [20], [18]),\n",
+       " 'urygzpdmxtwshqvfnbljaokice')"
+      ]
+     },
+     "execution_count": 58,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "enigma.set_wheels('i', 'd', 'z')\n",
+    "ct = enigma.encipher('verylongtestmessagewithanextrabitofmessageforgoodmeasure')\n",
+    "assert(ct == 'gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz')\n",
+    "assert(enigma.wheel_positions == (3, 12, 6))\n",
+    "assert(cat(enigma.wheel_positions_l) == 'ifd')\n",
+    "assert(enigma.peg_positions == ([8], [20], [18]))\n",
+    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'urygzpdmxtwshqvfnbljaokice')\n",
+    "\n",
+    "enigma.set_wheels('i', 'd', 'z')\n",
+    "pt = enigma.encipher('gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz')\n",
+    "assert(pt == 'verylongtestmessagewithanextrabitofmessageforgoodmeasure')\n",
+    "\n",
+    "pt, enigma.wheel_positions, enigma.wheel_positions_l, enigma.peg_positions, cat(enigma.lookup(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 59,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 3))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'ida')\n",
+      "assert(enigma.peg_positions == ([8], [22], [21]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ikhpqrvcambzjondefwyxgsutl')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 4))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idb')\n",
+      "assert(enigma.peg_positions == ([8], [22], [20]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cdabskhgzwfmlqvunyexpojtri')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 5))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idc')\n",
+      "assert(enigma.peg_positions == ([8], [22], [19]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'pcbwiqhgemyvjsuaftnroldzkx')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 6))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idd')\n",
+      "assert(enigma.peg_positions == ([8], [22], [18]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'xcbfvdnouptmlghjzwykierasq')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 7))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'ide')\n",
+      "assert(enigma.peg_positions == ([8], [22], [17]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'xfvglbdynuseriwqpmkzjcoaht')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 8))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idf')\n",
+      "assert(enigma.peg_positions == ([8], [22], [16]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'tfpqlbouynsewjgcdxkahzmriv')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 9))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idg')\n",
+      "assert(enigma.peg_positions == ([8], [22], [15]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cjaunvlwtbygzexrspqidfhokm')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 10))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idh')\n",
+      "assert(enigma.peg_positions == ([8], [22], [14]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'yltxkrqvowebzpingfucshjdam')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 11))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idi')\n",
+      "assert(enigma.peg_positions == ([8], [22], [13]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'myktluzrnxceaiqsohpdfwvjbg')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 12))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idj')\n",
+      "assert(enigma.peg_positions == ([8], [22], [12]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'pynjrmiugdqxfcvakewzhoslbt')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 13))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idk')\n",
+      "assert(enigma.peg_positions == ([8], [22], [11]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'mwvedyplnoxhaijgrqtszcbkfu')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 14))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idl')\n",
+      "assert(enigma.peg_positions == ([8], [22], [10]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qcbrfeutvoxpnmjladzhgiykws')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 15))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idm')\n",
+      "assert(enigma.peg_positions == ([8], [22], [9]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'dnoahryetsmukbcvwfjilpqzgx')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 16))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idn')\n",
+      "assert(enigma.peg_positions == ([8], [22], [8]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'nidcfehgbqsovalyjzkxwmutpr')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 17))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'ido')\n",
+      "assert(enigma.peg_positions == ([8], [22], [7]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'joifxdulcarhzpbntkwqgysevm')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 18))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idp')\n",
+      "assert(enigma.peg_positions == ([8], [22], [6]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ptnlsxvozmwdjchayuebrgkfqi')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 19))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idq')\n",
+      "assert(enigma.peg_positions == ([8], [22], [5]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'slwopzqnmxybihdeguavrtcjkf')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 20))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idr')\n",
+      "assert(enigma.peg_positions == ([8], [22], [4]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'hcbedwlamzogixkytsrqvufnpj')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 21))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'ids')\n",
+      "assert(enigma.peg_positions == ([8], [22], [3]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'odxbjwzrmelkisavuhnyqpfctg')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 22))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idt')\n",
+      "assert(enigma.peg_positions == ([8], [22], [2]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'udgbfeclrwnhxksvtioqapjmzy')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 23))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idu')\n",
+      "assert(enigma.peg_positions == ([8], [22], [1]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'nrdczqxmowvshaiufblypkjgte')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 10, 24))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'idv')\n",
+      "assert(enigma.peg_positions == ([8], [22], [0]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'hkifjdoacebqtzgulyvmpsxwrn')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 11, 25))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'iew')\n",
+      "assert(enigma.peg_positions == ([8], [21], [25]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'yptzuhofqvnmlkgbixwcejsrad')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 11, 0))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'iex')\n",
+      "assert(enigma.peg_positions == ([8], [21], [24]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'vkdcwhqfjibzsptngumoraeyxl')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 11, 1))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'iey')\n",
+      "assert(enigma.peg_positions == ([8], [21], [23]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'wenpbqrouxlkychdfgzvitajms')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (3, 11, 2))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'iez')\n",
+      "assert(enigma.peg_positions == ([8], [21], [22]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'szgyqvclkoihurjwenaxmfptdb')\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "enigma.set_wheels('i', 'd', 'z')\n",
+    "\n",
+    "for i in range(26):\n",
+    "    enigma.advance()\n",
+    "    print('enigma.advance()')\n",
+    "    print(\"assert(enigma.wheel_positions == {})\".format(enigma.wheel_positions))\n",
+    "    print(\"assert(cat(enigma.wheel_positions_l) == '{}')\".format(cat(enigma.wheel_positions_l)))\n",
+    "    print(\"assert(enigma.peg_positions == {})\".format(enigma.peg_positions))\n",
+    "    print(\"assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == '{}')\".format(cat(enigma.lookup(l) for l in string.ascii_lowercase)))\n",
+    "    print()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 86,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "# Reflector B\n",
+    "# Rotors III, I, II with rings 17, 11, 19\n",
+    "# Plugboard pairs GU FZ BD LK TC PS HV WN JE AM\n",
+    "\n",
+    "tbt_enigma = Enigma(reflector_b_spec, \n",
+    "                wheel_iii_spec, wheel_iii_pegs,\n",
+    "                wheel_i_spec, wheel_i_pegs,\n",
+    "                wheel_ii_spec, wheel_ii_pegs,\n",
+    "                17, 11, 19,\n",
+    "                'GU FZ BD LK TC PS HV WN JE AM'.lower())"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 61,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'jbvbwwzfslhxnhzzccsngebmrnswgjonwbjnzcfgadeuoyameylmpvny'"
+      ]
+     },
+     "execution_count": 61,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'q', 'v')\n",
+    "ct = tbt_enigma.encipher('verylongtestmessagewithanextrabitofmessageforgoodmeasure')\n",
+    "ct"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 62,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'slgncszxltkzebghstgywdmpr'"
+      ]
+     },
+     "execution_count": 62,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "target_ct = ''.join(c.lower() for c in 'SLGNC SZXLT KZEBG HSTGY WDMPR' if c in string.ascii_letters)\n",
+    "target_ct"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 63,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'theyweredetectebybritishshipsinclud'"
+      ]
+     },
+     "execution_count": 63,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "target_pt = ''.join(c.lower() for c in 'Theyw erede tecte byBri tishs hipsi nclud' if c in string.ascii_letters)\n",
+    "target_pt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 91,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'theyweredetectedbybritishshipsinclud'"
+      ]
+     },
+     "execution_count": 91,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "target_pt = ''.join(c.lower() for c in 'Theyw erede tecte d byBri tishs hipsi nclud' if c in string.ascii_letters)\n",
+    "target_pt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 64,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(25, 35)"
+      ]
+     },
+     "execution_count": 64,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "len(target_ct), len(target_pt)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 88,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "slgncszxltkzebghstgywdmpr\n",
+      "theyweredetectebybritishshipsinclud\n"
+     ]
+    }
+   ],
+   "source": [
+    "print('{}\\n{}'.format(target_ct, target_pt))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 92,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "theyweredetectedbybritishshipsinclud\n",
+      "slgncszxltkzebghstgywdmpr\n",
+      "slgncszxltkzebghstgywdmprucuzqdqzpve\n",
+      "theyweredetectedbybritish\n"
+     ]
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "this_pt = tbt_enigma.encipher(target_ct)\n",
+    "\n",
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "this_ct = tbt_enigma.encipher(target_pt)\n",
+    "\n",
+    "\n",
+    "print('{}\\n{}\\n{}\\n{}'.format(target_pt, target_ct, this_ct, this_pt))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 65,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "import itertools"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 67,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def str_ham(s1, s2):\n",
+    "    \"\"\"Hamming distance for strings\"\"\"\n",
+    "    return sum(1 for c1, c2 in zip(s1, s2) if c1 == c2)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 68,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "5"
+      ]
+     },
+     "execution_count": 68,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "str_ham('hello', 'hello')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 93,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "best ('a', 'a', 'a') 25\n",
+      "best ('a', 'a', 'a') 25\n",
+      "best ('a', 'a', 'a') 25\n",
+      "best ('a', 'a', 'a') 25\n",
+      "1 loop, best of 3: 17.6 s per loop\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit\n",
+    "best = ('a', 'a', 'a')\n",
+    "best_hd = 0\n",
+    "for w1, w2, w3 in itertools.product(string.ascii_lowercase, repeat=3):\n",
+    "    tbt_enigma.set_wheels(w1, w2, w3)\n",
+    "    this_ct = tbt_enigma.encipher(target_pt)\n",
+    "    if this_ct == target_ct:\n",
+    "        print(w1, w2, w3)\n",
+    "    if str_ham(this_ct, target_ct) > best_hd:\n",
+    "        best = (w1, w2, w3)\n",
+    "        best_hd = str_ham(this_ct, target_ct)\n",
+    "print('best', best, best_hd)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 94,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Wheels now ['a', 'a', 'b'] enciphering t -> s\n",
+      "Wheels now ['a', 'a', 'c'] enciphering h -> l\n",
+      "Wheels now ['a', 'a', 'd'] enciphering e -> g\n",
+      "Wheels now ['a', 'a', 'e'] enciphering y -> n\n",
+      "Wheels now ['a', 'b', 'f'] enciphering w -> c\n",
+      "Wheels now ['a', 'b', 'g'] enciphering e -> s\n",
+      "Wheels now ['a', 'b', 'h'] enciphering r -> z\n",
+      "Wheels now ['a', 'b', 'i'] enciphering e -> x\n",
+      "Wheels now ['a', 'b', 'j'] enciphering d -> l\n",
+      "Wheels now ['a', 'b', 'k'] enciphering e -> t\n",
+      "Wheels now ['a', 'b', 'l'] enciphering t -> k\n",
+      "Wheels now ['a', 'b', 'm'] enciphering e -> z\n",
+      "Wheels now ['a', 'b', 'n'] enciphering c -> e\n",
+      "Wheels now ['a', 'b', 'o'] enciphering t -> b\n",
+      "Wheels now ['a', 'b', 'p'] enciphering e -> g\n",
+      "Wheels now ['a', 'b', 'q'] enciphering d -> h\n",
+      "Wheels now ['a', 'b', 'r'] enciphering b -> s\n",
+      "Wheels now ['a', 'b', 's'] enciphering y -> t\n",
+      "Wheels now ['a', 'b', 't'] enciphering b -> g\n",
+      "Wheels now ['a', 'b', 'u'] enciphering r -> y\n",
+      "Wheels now ['a', 'b', 'v'] enciphering i -> w\n",
+      "Wheels now ['a', 'b', 'w'] enciphering t -> d\n",
+      "Wheels now ['a', 'b', 'x'] enciphering i -> m\n",
+      "Wheels now ['a', 'b', 'y'] enciphering s -> p\n",
+      "Wheels now ['a', 'b', 'z'] enciphering h -> r\n",
+      "Wheels now ['a', 'b', 'a'] enciphering s -> u\n",
+      "Wheels now ['a', 'b', 'b'] enciphering h -> c\n",
+      "Wheels now ['a', 'b', 'c'] enciphering i -> u\n",
+      "Wheels now ['a', 'b', 'd'] enciphering p -> z\n",
+      "Wheels now ['a', 'b', 'e'] enciphering s -> q\n",
+      "Wheels now ['a', 'c', 'f'] enciphering i -> d\n",
+      "Wheels now ['a', 'c', 'g'] enciphering n -> q\n",
+      "Wheels now ['a', 'c', 'h'] enciphering c -> z\n",
+      "Wheels now ['a', 'c', 'i'] enciphering l -> p\n",
+      "Wheels now ['a', 'c', 'j'] enciphering u -> v\n",
+      "Wheels now ['a', 'c', 'k'] enciphering d -> e\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "('slgncszxltkzebghstgywdmprucuzqdqzpve', 'slgncszxltkzebghstgywdmpr')"
+      ]
+     },
+     "execution_count": 94,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "tbt_enigma.encipher(target_pt, debug=True), target_ct"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 95,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Wheels now ['a', 'a', 'b'] enciphering s -> t\n",
+      "Wheels now ['a', 'a', 'c'] enciphering l -> h\n",
+      "Wheels now ['a', 'a', 'd'] enciphering g -> e\n",
+      "Wheels now ['a', 'a', 'e'] enciphering n -> y\n",
+      "Wheels now ['a', 'b', 'f'] enciphering c -> w\n",
+      "Wheels now ['a', 'b', 'g'] enciphering s -> e\n",
+      "Wheels now ['a', 'b', 'h'] enciphering z -> r\n",
+      "Wheels now ['a', 'b', 'i'] enciphering x -> e\n",
+      "Wheels now ['a', 'b', 'j'] enciphering l -> d\n",
+      "Wheels now ['a', 'b', 'k'] enciphering t -> e\n",
+      "Wheels now ['a', 'b', 'l'] enciphering k -> t\n",
+      "Wheels now ['a', 'b', 'm'] enciphering z -> e\n",
+      "Wheels now ['a', 'b', 'n'] enciphering e -> c\n",
+      "Wheels now ['a', 'b', 'o'] enciphering b -> t\n",
+      "Wheels now ['a', 'b', 'p'] enciphering g -> e\n",
+      "Wheels now ['a', 'b', 'q'] enciphering h -> d\n",
+      "Wheels now ['a', 'b', 'r'] enciphering s -> b\n",
+      "Wheels now ['a', 'b', 's'] enciphering t -> y\n",
+      "Wheels now ['a', 'b', 't'] enciphering g -> b\n",
+      "Wheels now ['a', 'b', 'u'] enciphering y -> r\n",
+      "Wheels now ['a', 'b', 'v'] enciphering w -> i\n",
+      "Wheels now ['a', 'b', 'w'] enciphering d -> t\n",
+      "Wheels now ['a', 'b', 'x'] enciphering m -> i\n",
+      "Wheels now ['a', 'b', 'y'] enciphering p -> s\n",
+      "Wheels now ['a', 'b', 'z'] enciphering r -> h\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "('theyweredetectedbybritish', 'theyweredetectedbybritishshipsinclud')"
+      ]
+     },
+     "execution_count": 95,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "tbt_enigma.encipher(target_ct, debug=True), target_pt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 96,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('theyweredetectedbybritish', 'theyweredetectedbybritishshipsinclud')"
+      ]
+     },
+     "execution_count": 96,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "tbt_enigma.encipher(target_ct), target_pt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 97,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'mdtbjzuvielkawosqrpcghnxyf'"
+      ]
+     },
+     "execution_count": 97,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(tbt_enigma.plugboard.forward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 99,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "10"
+      ]
+     },
+     "execution_count": 99,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "tbt_enigma.left_wheel.position"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.5.3"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
index 06f684a426cccb6ec23ed46fe3ee2651ab03a755..50d708f859b6e1f2816491366da701e3b9b77592 100644 (file)
@@ -4,17 +4,71 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "<a name=\"top\"></a>\n",
     "# Enigma machine\n",
-    "Specification from [Codes and Ciphers](http://www.codesandciphers.org.uk/enigma/rotorspec.htm) page.\n",
     "\n",
-    "Example Enigma machines from [Louise Dale](http://enigma.louisedade.co.uk/enigma.html) (full simulation) and [EnigmaCo](http://enigmaco.de/enigma/enigma.html) (good animation of the wheels, but no ring settings).\n",
+    "This is an implementation of an Enigma machine in Python. See below for links that describe the Enigma machine and how it was used.\n",
     "\n",
-    "There's also the nice Enigma simulator for Android by [Franklin Heath](https://franklinheath.co.uk/2012/02/04/our-first-app-published-enigma-simulator/), available on the [Google Play store](https://play.google.com/store/apps/details?id=uk.co.franklinheath.enigmasim&hl=en_GB)."
+    "The Enigma machine has a bunch of components which can be swapped or modified to change its behaviour. The components are:\n",
+    "\n",
+    "* The plugboard\n",
+    "* The wheels (three chosen from a set of five)\n",
+    "* The reflector (two available, though generally not swapped)\n",
+    "\n",
+    "The plugboard and wheel selection were changed every day. The wheel orientation was changed every message.\n",
+    "\n",
+    "## Design sketch\n",
+    "Each of the components can be thought of as a \"letter transformer\", which take a letter and input and give a different letter as output. From a given setup (plugboard setting or wheel orientation), these transformations are deterministic: if nothing moves, the same letter input will give the same letter output. Depending on the component, forward and backward transformations can be different. For instance, the wheel I converts `a` to `e` forward, and `a` to `u` backward.\n",
+    "\n",
+    "This means we can take an object oriented approach to building the Enigma machine. The machine itself is a collection (aggregation) of components. Each component keeps track of its current state. \n",
+    "\n",
+    "The components have an inheritance hierarchy.\n",
+    "\n",
+    "* [LetterTransformer](#lettertransformer)\n",
+    "  * [Plugobard](#plugboard)\n",
+    "    * [Reflector](#reflector)\n",
+    "  * [SimpleWheel](#simplewheel)\n",
+    "    * [Wheel](#wheel)\n",
+    "\n",
+    "The `LetterTransformer` is the base class and defines the basic operations all the transformers apply. \n",
+    "\n",
+    "A `Plugboard` is a type of `LetterTransformer` that swaps only some letters, and acts the same both forward and backward. The `Reflector` acts like a `Plugboard` but with 13 pairs of swaps.\n",
+    "\n",
+    "A `SimpleWheel` has different forward and backward transforms, and also rotates. A `Wheel` is the same, but the indicator \"ring\" around the outside can be rotated around the core. This ring of a `Wheel` has a notch that can control when other `SimpleWheel`s and `Wheel`s are rotated in the Enigma.\n",
+    "\n",
+    "Note that all the logic of when the wheels rotate is controlled by the Enigma machine.\n",
+    "\n",
+    "* [Engima](#enigma)\n",
+    "* [Testing Enigma](#testingenigma)\n",
+    "\n",
+    "### Implmentation note\n",
+    "The normal way to define a class in Python is to define all the methods, class variables, and instance variables at the same time. However, that makes it difficult to place the discussion of the various methods and what they do near the definitions. \n",
+    "\n",
+    "This notebook takes a variant approach of defining the base class, then defining methods in separate cells and adding them to the class with the `setattr()` procedure.\n",
+    "\n",
+    "\n",
+    "## See also\n",
+    "* Specification from [Codes and Ciphers](http://www.codesandciphers.org.uk/enigma/rotorspec.htm) page.\n",
+    "\n",
+    "* Example Enigma machines from [Louise Dale](http://enigma.louisedade.co.uk/enigma.html) (full simulation) and [EnigmaCo](http://enigmaco.de/enigma/enigma.html) (good animation of the wheels, but no ring settings).\n",
+    "\n",
+    "* There's also the nice Enigma simulator for Android by [Franklin Heath](https://franklinheath.co.uk/2012/02/04/our-first-app-published-enigma-simulator/), available on the [Google Play store](https://play.google.com/store/apps/details?id=uk.co.franklinheath.enigmasim&hl=en_GB).\n",
+    "\n",
+    "* Enigma wiring from the [Crypto Museum](http://www.cryptomuseum.com/crypto/enigma/wiring.htm)."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "First, some general-purpose and utility imports. \n",
+    "\n",
+    "`pos` and `unpos` convert between letters and numbers (in range 0-25 inclusive)."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": 108,
    "metadata": {
     "collapsed": true
    },
     "def unpos(number): return chr(number % 26 + ord('a'))"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The wheel specifications show what positions `a` to `z` (ignoring the ring) go to. For instance, Wheel 1 converts `a` to `e` forward, and `a` to `u` backward. The notch positions show where the wheel advance notches are on the wheel rings. The reflector specifications show the reflected pairs."
+   ]
+  },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 109,
    "metadata": {
     "collapsed": true
    },
     "beta_wheel_spec = 'leyjvcnixwpbqmdrtakzgfuhos'\n",
     "gamma_wheel_spec = 'fsokanuerhmbtiycwlqpzxvgjd'\n",
     "\n",
-    "wheel_i_pegs = ['q']\n",
-    "wheel_ii_pegs = ['e']\n",
-    "wheel_iii_pegs = ['v']\n",
-    "wheel_iv_pegs = ['j']\n",
-    "wheel_v_pegs = ['z']\n",
-    "wheel_vi_pegs = ['z', 'm']\n",
-    "wheel_vii_pegs = ['z', 'm']\n",
-    "wheel_viii_pegs = ['z', 'm']\n",
+    "wheel_i_notches = ['q']\n",
+    "wheel_ii_notches = ['e']\n",
+    "wheel_iii_notches = ['v']\n",
+    "wheel_iv_notches = ['j']\n",
+    "wheel_v_notches = ['z']\n",
+    "wheel_vi_notches = ['z', 'm']\n",
+    "wheel_vii_notches = ['z', 'm']\n",
+    "wheel_viii_notches = ['z', 'm']\n",
     "\n",
     "reflector_b_spec = 'ay br cu dh eq fs gl ip jx kn mo tz vw'\n",
     "reflector_c_spec = 'af bv cp dj ei go hy kr lz mx nw tq su'"
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 110,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "# class LetterTransformer(object):\n",
+    "#     def __init__(self, specification, raw_transform=False):\n",
+    "#         if raw_transform:\n",
+    "#             transform = specification\n",
+    "#         else:\n",
+    "#             transform = self.parse_specification(specification)\n",
+    "#         self.validate_transform(transform)\n",
+    "#         self.make_transform_map(transform)\n",
+    "    \n",
+    "#     def parse_specification(self, specification):\n",
+    "#         return list(zip(string.ascii_lowercase, clean(specification)))\n",
+    "#         # return specification\n",
+    "    \n",
+    "#     def validate_transform(self, transform):\n",
+    "#         \"\"\"A set of pairs, of from-to\"\"\"\n",
+    "#         if len(transform) != 26:\n",
+    "#             raise ValueError(\"Transform specification has {} pairs, requires 26\".\n",
+    "#                 format(len(transform)))\n",
+    "#         for p in transform:\n",
+    "#             if len(p) != 2:\n",
+    "#                 raise ValueError(\"Not all mappings in transform \"\n",
+    "#                     \"have two elements\")\n",
+    "#         if len(set([p[0] for p in transform])) != 26:\n",
+    "#             raise ValueError(\"Transform specification must list 26 origin letters\") \n",
+    "#         if len(set([p[1] for p in transform])) != 26:\n",
+    "#             raise ValueError(\"Transform specification must list 26 destination letters\") \n",
+    "\n",
+    "#     def make_empty_transform(self):\n",
+    "#         self.forward_map = [0] * 26\n",
+    "#         self.backward_map = [0] * 26\n",
+    "            \n",
+    "#     def make_transform_map(self, transform):\n",
+    "#         self.make_empty_transform()\n",
+    "#         for p in transform:\n",
+    "#             self.forward_map[pos(p[0])] = pos(p[1])\n",
+    "#             self.backward_map[pos(p[1])] = pos(p[0])\n",
+    "#         return self.forward_map, self.backward_map\n",
+    "    \n",
+    "#     def forward(self, letter):\n",
+    "#         if letter in string.ascii_lowercase:\n",
+    "#             return unpos(self.forward_map[pos(letter)])\n",
+    "#         else:\n",
+    "#             return ''\n",
+    "                \n",
+    "#     def backward(self, letter):\n",
+    "#         if letter in string.ascii_lowercase:\n",
+    "#             return unpos(self.backward_map[pos(letter)])\n",
+    "#         else:\n",
+    "#             return ''"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a name=\"lettertransformer\"></a>\n",
+    "# Letter transformer\n",
+    "[Top](#top)\n",
+    "\n",
+    "A generic transformer of letters. All components in the Enigma are based on this. \n",
+    "\n",
+    "The transformer has two directions, `forward` and `backward`. In each direction, a given letter is transformed into a different letter. Both transformations are general permutations of the alphabet (i.e. each letter goes to one and only one new letter). There is no general requirement for the `forward` and `backward` transformations to have any particular relationship to each other (even though most do in the Enigma machine).\n",
+    "\n",
+    "When created, it must be given the transformation which should be applied. A raw transform is a sequence of letter pairs, such that `p[0]` is transformed to `p[1]` forwards, and `p[1]` goes to `p[0]` backwards.\n",
+    "\n",
+    "If the transform is not raw, it's assumed that the specification is a sequence of the `p[1]`s, and the standard alphabet gives the `p[0]`s."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 111,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
     "        else:\n",
     "            transform = self.parse_specification(specification)\n",
     "        self.validate_transform(transform)\n",
-    "        self.make_transform_map(transform)\n",
-    "    \n",
-    "    def parse_specification(self, specification):\n",
-    "        return list(zip(string.ascii_lowercase, clean(specification)))\n",
-    "        # return specification\n",
-    "    \n",
-    "    def validate_transform(self, transform):\n",
-    "        \"\"\"A set of pairs, of from-to\"\"\"\n",
-    "        if len(transform) != 26:\n",
-    "            raise ValueError(\"Transform specification has {} pairs, requires 26\".\n",
-    "                format(len(transform)))\n",
-    "        for p in transform:\n",
-    "            if len(p) != 2:\n",
-    "                raise ValueError(\"Not all mappings in transform \"\n",
-    "                    \"have two elements\")\n",
-    "        if len(set([p[0] for p in transform])) != 26:\n",
-    "            raise ValueError(\"Transform specification must list 26 origin letters\") \n",
-    "        if len(set([p[1] for p in transform])) != 26:\n",
-    "            raise ValueError(\"Transform specification must list 26 destination letters\") \n",
-    "\n",
-    "    def make_empty_transform(self):\n",
-    "        self.forward_map = [0] * 26\n",
-    "        self.backward_map = [0] * 26\n",
-    "            \n",
-    "    def make_transform_map(self, transform):\n",
-    "        self.make_empty_transform()\n",
-    "        for p in transform:\n",
-    "            self.forward_map[pos(p[0])] = pos(p[1])\n",
-    "            self.backward_map[pos(p[1])] = pos(p[0])\n",
-    "        return self.forward_map, self.backward_map\n",
-    "    \n",
-    "    def forward(self, letter):\n",
-    "        if letter in string.ascii_lowercase:\n",
-    "            return unpos(self.forward_map[pos(letter)])\n",
-    "        else:\n",
-    "            return ''\n",
-    "                \n",
-    "    def backward(self, letter):\n",
-    "        if letter in string.ascii_lowercase:\n",
-    "            return unpos(self.backward_map[pos(letter)])\n",
-    "        else:\n",
-    "            return ''"
+    "        self.make_transform_map(transform)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Parse a specification: convert a string of destination letters into a list of pairs. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 112,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def parse_specification(self, specification):\n",
+    "    return list(zip(string.ascii_lowercase, clean(specification)))\n",
+    "    # return specification\n",
+    "\n",
+    "setattr(LetterTransformer, \"parse_specification\", parse_specification)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 113,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[('a', 'e'),\n",
+       " ('b', 'k'),\n",
+       " ('c', 'm'),\n",
+       " ('d', 'f'),\n",
+       " ('e', 'l'),\n",
+       " ('f', 'g'),\n",
+       " ('g', 'd'),\n",
+       " ('h', 'q'),\n",
+       " ('i', 'v'),\n",
+       " ('j', 'z'),\n",
+       " ('k', 'n'),\n",
+       " ('l', 't'),\n",
+       " ('m', 'o'),\n",
+       " ('n', 'w'),\n",
+       " ('o', 'y'),\n",
+       " ('p', 'h'),\n",
+       " ('q', 'x'),\n",
+       " ('r', 'u'),\n",
+       " ('s', 's'),\n",
+       " ('t', 'p'),\n",
+       " ('u', 'a'),\n",
+       " ('v', 'i'),\n",
+       " ('w', 'b'),\n",
+       " ('x', 'r'),\n",
+       " ('y', 'c'),\n",
+       " ('z', 'j')]"
+      ]
+     },
+     "execution_count": 113,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "parse_specification(None, wheel_i_spec)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Checks that a transform is valid."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 114,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def validate_transform(self, transform):\n",
+    "    \"\"\"A set of pairs, of from-to\"\"\"\n",
+    "    if len(transform) != 26:\n",
+    "        raise ValueError(\"Transform specification has {} pairs, requires 26\".\n",
+    "            format(len(transform)))\n",
+    "    for p in transform:\n",
+    "        if len(p) != 2:\n",
+    "            raise ValueError(\"Not all mappings in transform \"\n",
+    "                \"have two elements\")\n",
+    "    if len(set([p[0] for p in transform])) != 26:\n",
+    "        raise ValueError(\"Transform specification must list 26 origin letters\") \n",
+    "    if len(set([p[1] for p in transform])) != 26:\n",
+    "        raise ValueError(\"Transform specification must list 26 destination letters\") \n",
+    "\n",
+    "setattr(LetterTransformer, \"validate_transform\", validate_transform)        "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The empty transform maps each letter to itself, forward and backward. A useful starting point for creating the maps needed.\n",
+    "\n",
+    "The forward and backward maps are `list`s of numbers (rather than `dict`s of letters to letters) to make the calculations easier when it comes to the wheels, and wheels with turnable indicator rings."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 115,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "# def make_empty_transform(self):\n",
+    "#     self.forward_map = [0] * 26\n",
+    "#     self.backward_map = [0] * 26\n",
+    "\n",
+    "# setattr(LetterTransformer, \"make_empty_transform\", make_empty_transform)    "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 116,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def make_empty_transform(self):\n",
+    "    self.forward_map = list(range(26))\n",
+    "    self.backward_map = list(range(26))\n",
+    "\n",
+    "setattr(LetterTransformer, \"make_empty_transform\", make_empty_transform)    "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Make the transform. Starting from an empty transform, mutate it to include the swaps. Note that the forward and backward swaps are stored separately. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 117,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def make_transform_map(self, transform):\n",
+    "    self.make_empty_transform()\n",
+    "    for p in transform:\n",
+    "        self.forward_map[pos(p[0])] = pos(p[1])\n",
+    "        self.backward_map[pos(p[1])] = pos(p[0])\n",
+    "    return self.forward_map, self.backward_map\n",
+    "\n",
+    "setattr(LetterTransformer, \"make_transform_map\", make_transform_map)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 118,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
+   "outputs": [],
+   "source": [
+    "def forward(self, letter):\n",
+    "    if letter in string.ascii_lowercase:\n",
+    "        return unpos(self.forward_map[pos(letter)])\n",
+    "    else:\n",
+    "        return ''\n",
+    "\n",
+    "def backward(self, letter):\n",
+    "    if letter in string.ascii_lowercase:\n",
+    "        return unpos(self.backward_map[pos(letter)])\n",
+    "    else:\n",
+    "        return ''\n",
+    "\n",
+    "setattr(LetterTransformer, \"forward\", forward)\n",
+    "setattr(LetterTransformer, \"backward\", backward)    "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 119,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        " ('y', 'z')]"
       ]
      },
-     "execution_count": 4,
+     "execution_count": 119,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 120,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'zyxwcabdefghijklmnopqrstuv'"
       ]
      },
-     "execution_count": 5,
+     "execution_count": 120,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 121,
    "metadata": {
-    "collapsed": false,
+    "collapsed": true,
     "scrolled": true
    },
    "outputs": [],
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 122,
    "metadata": {
-    "collapsed": false,
     "scrolled": true
    },
    "outputs": [
        "  24])"
       ]
      },
-     "execution_count": 7,
+     "execution_count": 122,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 123,
    "metadata": {
-    "collapsed": false,
     "scrolled": true
    },
    "outputs": [
        "  0])"
       ]
      },
-     "execution_count": 8,
+     "execution_count": 123,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 124,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'zyxwcabdefghijklmnopqrstuv'"
       ]
      },
-     "execution_count": 9,
+     "execution_count": 124,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 125,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'fgehijklmnopqrstuvwxyzdcba'"
       ]
      },
-     "execution_count": 10,
+     "execution_count": 125,
      "metadata": {},
      "output_type": "execute_result"
     }
     "cat(lt.backward(l) for l in string.ascii_lowercase)"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a name=\"plugboard\"></a>\n",
+    "## Plugboard\n",
+    "[Top](#top)\n",
+    "\n",
+    "A `Plugboard` is a `LetterTransformer` that swaps some pairs of letters, and does the same swaps forward and backward."
+   ]
+  },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 126,
    "metadata": {
     "collapsed": true
    },
     "                raise ValueError(\"Not all mappings in transform\"\n",
     "                    \"have two elements\")\n",
     "    \n",
-    "    def make_empty_transform(self):\n",
-    "        self.forward_map = list(range(26))\n",
-    "        self.backward_map = list(range(26))\n",
+    "    def make_empty_transform(self):\n",
+    "        self.forward_map = list(range(26))\n",
+    "        self.backward_map = list(range(26))\n",
     "        \n",
     "    def make_transform_map(self, transform):\n",
     "        expanded_transform = transform + [tuple(reversed(p)) for p in transform]\n",
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 127,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 128,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'zycdefghijklmnopqrstuvwxba'"
       ]
      },
-     "execution_count": 13,
+     "execution_count": 128,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 129,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'zycdefghijklmnopqrstuvwxba'"
       ]
      },
-     "execution_count": 14,
+     "execution_count": 129,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 130,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 131,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "('zycdefghijklmnopqrstuvwxba', 'zycdefghijklmnopqrstuvwxba')"
       ]
      },
-     "execution_count": 16,
+     "execution_count": 131,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 132,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "('ugcdypblnzkhmisfrqoxavwtej', 'ugcdypblnzkhmisfrqoxavwtej')"
       ]
      },
-     "execution_count": 17,
+     "execution_count": 132,
      "metadata": {},
      "output_type": "execute_result"
     }
     "cat(pb.forward(l) for l in string.ascii_lowercase), cat(pb.backward(l) for l in string.ascii_lowercase)"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a name=\"reflector\"></a>\n",
+    "## Reflector\n",
+    "[Top](#top)\n",
+    "\n",
+    "A `Reflector` is a `Plugboard` that takes exactly 13 pairs of letters to swap."
+   ]
+  },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 133,
    "metadata": {
     "collapsed": true
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 134,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        " ('v', 'w')]"
       ]
      },
-     "execution_count": 19,
+     "execution_count": 134,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 135,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 136,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'yruhqsldpxngokmiebfzcwvjat'"
       ]
      },
-     "execution_count": 21,
+     "execution_count": 136,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 137,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 138,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'fvpjiaoyedrzxwgctkuqsbnmhl'"
       ]
      },
-     "execution_count": 23,
+     "execution_count": 138,
      "metadata": {},
      "output_type": "execute_result"
     }
    ]
   },
   {
-   "cell_type": "code",
-   "execution_count": 24,
-   "metadata": {
-    "collapsed": true
-   },
-   "outputs": [],
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a name=\"simplewheel\"></a>\n",
+    "## SimpleWheel\n",
+    "[Top](#top)\n",
+    "\n",
+    "A `SimpleWheel` has different forward and backward maps, and also a position. The position is set with the `set_position` method (and initially in the creator), and the wheel can advance using the `advance` method. \n",
+    "\n",
+    "How the position is used is best explained with an example. The Enigma wheel 1, in the neutral position, transforms `a` to `e` (+4 letters) and `b` to `k` (+10 letters). When the wheel is in position `b` and an `a` in enciphered, it's the _second_ element of the map that's used, so `a` would be advanced 10 letters, to give `j`.\n",
+    "\n",
+    "This means that when using the letter transformation maps, you use the element in the map that's offset by the position of the wheel. When enciphering a `c`, you'd normally use transformation at position 2 in the map; if the wheel is in position 7, you'd instead use the transform at position 2 + 7 = 9 in the map.\n",
+    "\n",
+    "There are various modulus operators to keep the numbers in the requried range, meaning you can wrap around the map and around the wheel.\n",
+    "\n",
+    "Note the use of `__getattribute__` to give a more human-friendly version of the position without making it a method call. That allows you to write `wheel.position` and `wheel.position_l` and get the appropriate answers."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 139,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
    "source": [
     "class SimpleWheel(LetterTransformer):\n",
     "    def __init__(self, transform, position='a', raw_transform=False):\n",
     "        super(SimpleWheel, self).__init__(transform, raw_transform)\n",
     "        self.set_position(position)\n",
     "        \n",
-    "    def __getattribute__(self,name):\n",
+    "    def __getattribute__(self, name):\n",
     "        if name=='position_l':\n",
     "            return unpos(self.position)\n",
     "        else:\n",
-    "            return object.__getattribute__(self, name)\n",
-    "    \n",
-    "    def set_position(self, position):\n",
-    "        self.position = ord(position) - ord('a')\n",
-    "    \n",
-    "    def forward(self, letter):\n",
-    "        if letter in string.ascii_lowercase:\n",
-    "            return unpos((self.forward_map[(pos(letter) + self.position) % 26] - self.position))\n",
-    "        else:\n",
-    "            return ''\n",
-    "                \n",
-    "    def backward(self, letter):\n",
-    "        if letter in string.ascii_lowercase:\n",
-    "            return unpos((self.backward_map[(pos(letter) + self.position) % 26] - self.position))\n",
-    "        else:\n",
-    "            return ''\n",
+    "            return object.__getattribute__(self, name)  "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Set the wheel to a new position. Note that it expects a letter, not a number."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 140,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def set_position(self, position):\n",
+    "    self.position = ord(position) - ord('a')\n",
+    "        \n",
+    "setattr(SimpleWheel, 'set_position', set_position)       "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Advance the wheel one step. Note that advancing beyond position 25 moves back to 0."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 141,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def advance(self):\n",
+    "    self.position = (self.position + 1) % 26\n",
+    "    return self.position\n",
+    "\n",
+    "setattr(SimpleWheel, 'advance', advance)    "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Do the encipherment forward and backward. Note how the map element to use is affected by the wheel position, and how the modulus wraps that map element around the wheel if needed."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 142,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def forward(self, letter):\n",
+    "    if letter in string.ascii_lowercase:\n",
+    "        return unpos((self.forward_map[(pos(letter) + self.position) % 26] - self.position))\n",
+    "    else:\n",
+    "        return ''\n",
+    "\n",
+    "def backward(self, letter):\n",
+    "    if letter in string.ascii_lowercase:\n",
+    "        return unpos((self.backward_map[(pos(letter) + self.position) % 26] - self.position))\n",
+    "    else:\n",
+    "        return ''\n",
     "        \n",
-    "    def advance(self):\n",
-    "        self.position = (self.position + 1) % 26\n",
-    "        return self.position"
+    "setattr(SimpleWheel, 'forward', forward)        \n",
+    "setattr(SimpleWheel, 'backward', backward)  "
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 25,
+   "execution_count": 143,
    "metadata": {
-    "collapsed": false,
     "scrolled": true
    },
    "outputs": [
        " ('z', 'j')]"
       ]
      },
-     "execution_count": 25,
+     "execution_count": 143,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 26,
+   "execution_count": 144,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 27,
+   "execution_count": 145,
    "metadata": {
-    "collapsed": false
+    "scrolled": true
    },
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "('ekmflgdqvzntowyhxuspaibrcj', 'uwygadfpvzbeckmthxslrinqoj')"
+       "[4,\n",
+       " 10,\n",
+       " 12,\n",
+       " 5,\n",
+       " 11,\n",
+       " 6,\n",
+       " 3,\n",
+       " 16,\n",
+       " 21,\n",
+       " 25,\n",
+       " 13,\n",
+       " 19,\n",
+       " 14,\n",
+       " 22,\n",
+       " 24,\n",
+       " 7,\n",
+       " 23,\n",
+       " 20,\n",
+       " 18,\n",
+       " 15,\n",
+       " 0,\n",
+       " 8,\n",
+       " 1,\n",
+       " 17,\n",
+       " 2,\n",
+       " 9]"
+      ]
+     },
+     "execution_count": 145,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel_1.forward_map"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 146,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'j'"
+      ]
+     },
+     "execution_count": 146,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel_1.advance()\n",
+    "wheel_1.forward('a')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 147,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('jlekfcpuymsnvxgwtrozhaqbid', 'vxfzceouyadbjlsgwrkqhmpnit')"
       ]
      },
-     "execution_count": 27,
+     "execution_count": 147,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 28,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 148,
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "'a'"
+       "'b'"
       ]
      },
-     "execution_count": 28,
+     "execution_count": 148,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 29,
+   "execution_count": 149,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 30,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 150,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "('ajdksiruxblhwtmcqgznpyfvoe', 'ajpczwrlfbdkotyuqgenhxmivs')"
       ]
      },
-     "execution_count": 30,
+     "execution_count": 150,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 31,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 151,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "('bdfhjlcprtxvznyeiwgakmusqo', 'tagbpcsdqeufvnzhyixjwlrkom')"
       ]
      },
-     "execution_count": 31,
+     "execution_count": 151,
      "metadata": {},
      "output_type": "execute_result"
     }
     "cat(wheel_3.forward(l) for l in string.ascii_lowercase), cat(wheel_3.backward(l) for l in string.ascii_lowercase)"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a name=\"wheel\"></a>\n",
+    "## Wheel\n",
+    "[Top](#top)\n",
+    "\n",
+    "This is the same as a `SimpleWheel`, but with the addition of a ring.\n",
+    "\n",
+    "The ring is moveable around the core of the wheel (with the wiring). This means that moving the ring changes the orientation of the core and wiring for the same setting.\n",
+    "\n",
+    "| Wheel with notch | Notch showing peg to hold it in place |\n",
+    "| ---------------- | ------------------------------------- |\n",
+    "| <img src=\"enigma-notch.jpg\" alt=\"Enigma wheel with notch\" width=300> | <img src=\"dtu_notch_big.jpg\" alt=\"Enigma wheel with notch\" width=300> |\n",
+    "| (From [Crypto museum](http://www.cryptomuseum.com/crypto/enigma/img/300879/035/full.jpg)) | From [Matematik Sider](http://www.matematiksider.dk/enigma/dtu_notch_big.jpg) |\n",
+    "\n",
+    "Though it's not very visible in the right hand image, the extra metal below the ring shows a spring-loaded peg on the wheel core which drops into the ring, with one hole per letter. The ring setting is where the peg drops into the ring.\n",
+    "\n",
+    "The notch setting is used in the full Enigma to control when the wheels step forward.\n",
+    "\n",
+    "Note that the constructor calls the superclass's constructor, then sets the positions properly with a call to `set_position`."
+   ]
+  },
   {
    "cell_type": "code",
-   "execution_count": 32,
+   "execution_count": 152,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
     "class Wheel(SimpleWheel):\n",
-    "    def __init__(self, transform, ring_peg_letters, ring_setting=1, position='a', raw_transform=False):\n",
-    "        self.ring_peg_letters = ring_peg_letters\n",
+    "    def __init__(self, transform, ring_notch_letters, ring_setting=1, position='a', raw_transform=False):\n",
+    "        self.ring_notch_letters = ring_notch_letters\n",
     "        self.ring_setting = ring_setting\n",
     "        super(Wheel, self).__init__(transform, position=position, raw_transform=raw_transform)\n",
-    "        self.set_position(position)\n",
-    "        \n",
-    "    def __getattribute__(self,name):\n",
-    "        if name=='position_l':\n",
-    "            return unpos(self.position + self.ring_setting - 1)\n",
-    "        else:\n",
-    "            return object.__getattribute__(self, name)\n",
+    "        self.set_position(position)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The `position` of the wheel is the orientation of the core. It's the same as the ring if the ring setting is 1. The `position_l` attribute is used to report the position of the ring letter, which is what the Enigma operator would see. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 153,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def __getattribute__(self,name):\n",
+    "    if name=='position_l':\n",
+    "        return unpos(self.position + self.ring_setting - 1)\n",
+    "    else:\n",
+    "        return object.__getattribute__(self, name)\n",
     "\n",
-    "    def set_position(self, position):\n",
+    "def set_position(self, position):\n",
+    "#     self.position = (pos(position) - self.ring_setting + 1) % 26\n",
+    "    if isinstance(position, str):\n",
     "        self.position = (pos(position) - self.ring_setting + 1) % 26\n",
-    "        # self.position_l = position\n",
-    "        self.peg_positions = [(pos(p) - pos(position)) % 26  for p in self.ring_peg_letters]\n",
+    "    else:\n",
+    "        self.position = (position - self.ring_setting) % 26\n",
+    "#     self.notch_positions = [(pos(position) - pos(p)) % 26  for p in self.ring_notch_letters]\n",
+    "    self.notch_positions = [(self.position + self.ring_setting - 1 - pos(p)) % 26  for p in self.ring_notch_letters]\n",
     "        \n",
-    "    def advance(self):\n",
-    "        super(Wheel, self).advance()\n",
-    "        self.peg_positions = [(p - 1) % 26 for p in self.peg_positions]\n",
-    "        # self.position_l = unpos(self.position + self.ring_setting - 1)\n",
-    "        return self.position"
+    "setattr(Wheel, '__getattribute__', __getattribute__)        \n",
+    "setattr(Wheel, 'set_position', set_position)    "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Advance the wheel. Again, note the superclass call, followed by the update of the notch positions."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 33,
+   "execution_count": 154,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
-    "wheel_3 = Wheel(wheel_iii_spec, wheel_iii_pegs, position='b', ring_setting=1)"
+    "def advance(self):\n",
+    "        super(Wheel, self).advance()\n",
+    "        self.notch_positions = [(p + 1) % 26 for p in self.notch_positions]\n",
+    "        # self.position_l = unpos(self.position + self.ring_setting - 1)\n",
+    "        return self.position\n",
+    "    \n",
+    "setattr(Wheel, 'advance', advance)    "
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 34,
+   "execution_count": 155,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
+   "outputs": [],
+   "source": [
+    "wheel_3 = Wheel(wheel_iii_spec, wheel_iii_notches, position='b', ring_setting=1)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 156,
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "(1, [20])"
+       "(1, [6])"
       ]
      },
-     "execution_count": 34,
+     "execution_count": 156,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "wheel_3.position, wheel_3.peg_positions"
+    "wheel_3.position, wheel_3.notch_positions"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 35,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 157,
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "(25, [24, 11])"
+       "(25, [2, 15])"
       ]
      },
-     "execution_count": 35,
+     "execution_count": 157,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "wheel_6 = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', ring_setting=3)\n",
-    "wheel_6.position, wheel_6.peg_positions"
+    "wheel_6 = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', ring_setting=3)\n",
+    "wheel_6.position, wheel_6.notch_positions"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 36,
+   "execution_count": 158,
    "metadata": {
-    "collapsed": false,
     "scrolled": true
    },
    "outputs": [
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "0 [23, 10]\n",
-      "1 [22, 9]\n",
-      "2 [21, 8]\n",
-      "3 [20, 7]\n",
-      "4 [19, 6]\n",
-      "5 [18, 5]\n",
-      "6 [17, 4]\n",
-      "7 [16, 3]\n",
-      "8 [15, 2]\n",
-      "9 [14, 1]\n",
+      "0 [3, 16]\n",
+      "1 [4, 17]\n",
+      "2 [5, 18]\n",
+      "3 [6, 19]\n",
+      "4 [7, 20]\n",
+      "5 [8, 21]\n",
+      "6 [9, 22]\n",
+      "7 [10, 23]\n",
+      "8 [11, 24]\n",
+      "9 [12, 25]\n",
       "10 [13, 0]\n",
-      "11 [12, 25]\n",
-      "12 [11, 24]\n",
-      "13 [10, 23]\n",
-      "14 [9, 22]\n",
-      "15 [8, 21]\n",
-      "16 [7, 20]\n",
-      "17 [6, 19]\n",
-      "18 [5, 18]\n",
-      "19 [4, 17]\n",
-      "20 [3, 16]\n",
-      "21 [2, 15]\n",
-      "22 [1, 14]\n",
+      "11 [14, 1]\n",
+      "12 [15, 2]\n",
+      "13 [16, 3]\n",
+      "14 [17, 4]\n",
+      "15 [18, 5]\n",
+      "16 [19, 6]\n",
+      "17 [20, 7]\n",
+      "18 [21, 8]\n",
+      "19 [22, 9]\n",
+      "20 [23, 10]\n",
+      "21 [24, 11]\n",
+      "22 [25, 12]\n",
       "23 [0, 13]\n",
-      "24 [25, 12]\n",
-      "25 [24, 11]\n",
-      "0 [23, 10]\n"
+      "24 [1, 14]\n",
+      "25 [2, 15]\n",
+      "0 [3, 16]\n"
      ]
     }
    ],
    "source": [
     "for _ in range(27):\n",
     "    wheel_6.advance()\n",
-    "    print(wheel_6.position, wheel_6.peg_positions)"
+    "    print(wheel_6.position, wheel_6.notch_positions)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 159,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "wheel = Wheel(wheel_iii_spec, wheel_iii_notches, position='b', \n",
+    "            ring_setting=3)\n",
+    "wheel.set_position(12)\n",
+    "assert(wheel.position == 9)\n",
+    "assert(16 in wheel.notch_positions)\n",
+    "assert(wheel.position_l =='l')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 160,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[16]"
+      ]
+     },
+     "execution_count": 160,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel.notch_positions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 161,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'l'"
+      ]
+     },
+     "execution_count": 161,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel.position_l"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 37,
+   "execution_count": 162,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
-    "wheel_3 = Wheel(wheel_iii_spec, wheel_iii_pegs, position='b', ring_setting=1)\n",
+    "wheel_3 = Wheel(wheel_iii_spec, wheel_iii_notches, position='b', ring_setting=1)\n",
     "assert(wheel_3.position == 1)\n",
-    "assert(wheel_3.peg_positions == [20])\n",
+    "assert(wheel_3.notch_positions == [6])\n",
     "assert(wheel_3.position_l == 'b')\n",
     "wheel_3.advance()\n",
     "assert(wheel_3.position == 2)\n",
-    "assert(wheel_3.peg_positions == [19])\n",
+    "assert(wheel_3.notch_positions == [7])\n",
     "assert(wheel_3.position_l == 'c')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 38,
+   "execution_count": 163,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[2, 15]"
+      ]
+     },
+     "execution_count": 163,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel_6 = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', ring_setting=3)\n",
+    "wheel_6.notch_positions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 164,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
-    "wheel_6 = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', ring_setting=3)\n",
+    "wheel_6 = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', ring_setting=3)\n",
     "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'xkqhwpvngzrcfoiaselbtymjdu')\n",
     "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'ptlyrmidoxbswhnfckquzgeavj')\n",
     "assert(wheel_6.position == 25)\n",
-    "assert(11 in wheel_6.peg_positions)\n",
-    "assert(24 in wheel_6.peg_positions)\n",
+    "assert(2 in wheel_6.notch_positions)\n",
+    "assert(15 in wheel_6.notch_positions)\n",
     "assert(wheel_6.position_l == 'b')\n",
     "\n",
     "wheel_6.advance()\n",
     "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'jpgvoumfyqbenhzrdkasxlictw')\n",
     "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'skxqlhcnwarvgmebjptyfdzuio')\n",
     "assert(wheel_6.position == 0)\n",
-    "assert(10 in wheel_6.peg_positions)\n",
-    "assert(23 in wheel_6.peg_positions)\n",
+    "assert(3 in wheel_6.notch_positions)\n",
+    "assert(16 in wheel_6.notch_positions)\n",
     "assert(wheel_6.position_l == 'c')\n",
     "\n",
     "for _ in range(22): wheel_6.advance()\n",
     "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'mgxantkzsyqjcufirldvhoewbp')\n",
     "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'dymswobuplgraevzkqifntxcjh')\n",
     "assert(wheel_6.position == 22)\n",
-    "assert(1 in wheel_6.peg_positions)\n",
-    "assert(14 in wheel_6.peg_positions)\n",
+    "assert(25 in wheel_6.notch_positions)\n",
+    "assert(12 in wheel_6.notch_positions)\n",
     "assert(wheel_6.position_l == 'y')\n",
     "\n",
     "wheel_6.advance()\n",
     "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'fwzmsjyrxpibtehqkcugndvaol')\n",
     "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'xlrvnatokfqzduyjphemswbigc')\n",
     "assert(wheel_6.position == 23)\n",
-    "assert(0 in wheel_6.peg_positions)\n",
-    "assert(13 in wheel_6.peg_positions)\n",
+    "assert(0 in wheel_6.notch_positions)\n",
+    "assert(13 in wheel_6.notch_positions)\n",
     "assert(wheel_6.position_l == 'z')\n",
     "\n",
     "wheel_6.advance()\n",
     "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'vylrixqwohasdgpjbtfmcuznke')\n",
     "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'kqumzsnjepyctxiogdlrvahfbw')\n",
     "assert(wheel_6.position == 24)\n",
-    "assert(25 in wheel_6.peg_positions)\n",
-    "assert(12 in wheel_6.peg_positions)\n",
+    "assert(1 in wheel_6.notch_positions)\n",
+    "assert(14 in wheel_6.notch_positions)\n",
     "assert(wheel_6.position_l == 'a')\n",
     "\n",
     "wheel_6.advance()\n",
     "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'xkqhwpvngzrcfoiaselbtymjdu')\n",
     "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'ptlyrmidoxbswhnfckquzgeavj')\n",
     "assert(wheel_6.position == 25)\n",
-    "assert(24 in wheel_6.peg_positions)\n",
-    "assert(11 in wheel_6.peg_positions)\n",
+    "assert(2 in wheel_6.notch_positions)\n",
+    "assert(15 in wheel_6.notch_positions)\n",
     "assert(wheel_6.position_l == 'b')\n",
     "\n",
     "wheel_6.advance()\n",
     "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'jpgvoumfyqbenhzrdkasxlictw')\n",
     "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'skxqlhcnwarvgmebjptyfdzuio')\n",
     "assert(wheel_6.position == 0)\n",
-    "assert(23 in wheel_6.peg_positions)\n",
-    "assert(10 in wheel_6.peg_positions)\n",
+    "assert(3 in wheel_6.notch_positions)\n",
+    "assert(16 in wheel_6.notch_positions)\n",
     "assert(wheel_6.position_l == 'c')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 39,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 165,
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "(0, 'c', [23, 10])"
+       "(0, 'c', [3, 16])"
       ]
      },
-     "execution_count": 39,
+     "execution_count": 165,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "wheel_6.position, wheel_6.position_l, wheel_6.peg_positions"
+    "wheel_6.position, wheel_6.position_l, wheel_6.notch_positions"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a name=\"enigma\"></a>\n",
+    "## Enigma\n",
+    "[Top](#top)\n",
+    "\n",
+    "This is the full Enigma machine.\n",
+    "\n",
+    "It's a collection of the various components defined above. There are three wheels (left, middle, and right), a plugboard, and a reflector.\n",
+    "\n",
+    "The `__getattribute__` method returns the state of the machine in friendly form, generally by asking the components to return the relevant attributes."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 40,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 167,
+   "metadata": {},
    "outputs": [],
    "source": [
     "class Enigma(object):\n",
     "    def __init__(self, reflector_spec,\n",
-    "                 left_wheel_spec, left_wheel_pegs,\n",
-    "                 middle_wheel_spec, middle_wheel_pegs,\n",
-    "                 right_wheel_spec, right_wheel_pegs,\n",
+    "                 left_wheel_spec, left_wheel_notches,\n",
+    "                 middle_wheel_spec, middle_wheel_notches,\n",
+    "                 right_wheel_spec, right_wheel_notches,\n",
     "                 left_ring_setting, middle_ring_setting, right_ring_setting,\n",
     "                 plugboard_setting):\n",
     "        self.reflector = Reflector(reflector_spec)\n",
-    "        self.left_wheel = Wheel(left_wheel_spec, left_wheel_pegs, ring_setting=left_ring_setting)\n",
-    "        self.middle_wheel = Wheel(middle_wheel_spec, middle_wheel_pegs, ring_setting=middle_ring_setting)\n",
-    "        self.right_wheel = Wheel(right_wheel_spec, right_wheel_pegs, ring_setting=right_ring_setting)\n",
+    "        self.left_wheel = Wheel(left_wheel_spec, left_wheel_notches, ring_setting=left_ring_setting)\n",
+    "        self.middle_wheel = Wheel(middle_wheel_spec, middle_wheel_notches, ring_setting=middle_ring_setting)\n",
+    "        self.right_wheel = Wheel(right_wheel_spec, right_wheel_notches, ring_setting=right_ring_setting)\n",
     "        self.plugboard = Plugboard(plugboard_setting)\n",
     "        \n",
     "    def __getattribute__(self,name):\n",
     "            return self.left_wheel.position, self.middle_wheel.position, self.right_wheel.position \n",
     "        elif name=='wheel_positions_l':\n",
     "            return self.left_wheel.position_l, self.middle_wheel.position_l, self.right_wheel.position_l \n",
-    "        elif name=='peg_positions':\n",
-    "            return self.left_wheel.peg_positions, self.middle_wheel.peg_positions, self.right_wheel.peg_positions\n",
+    "        elif name=='notch_positions':\n",
+    "            return (self.left_wheel.notch_positions, \n",
+    "                    self.middle_wheel.notch_positions, \n",
+    "                    self.right_wheel.notch_positions)\n",
     "        else:\n",
-    "            return object.__getattribute__(self, name)\n",
-    "\n",
-    "    \n",
-    "    def set_wheels(self, left_wheel_position, middle_wheel_position, right_wheel_position):\n",
-    "        self.left_wheel.set_position(left_wheel_position)\n",
-    "        self.middle_wheel.set_position(middle_wheel_position)\n",
-    "        self.right_wheel.set_position(right_wheel_position)\n",
-    "        \n",
-    "    def lookup(self, letter):\n",
-    "        a = self.plugboard.forward(letter)\n",
-    "        b = self.right_wheel.forward(a)\n",
-    "        c = self.middle_wheel.forward(b)\n",
-    "        d = self.left_wheel.forward(c)\n",
-    "        e = self.reflector.forward(d)\n",
-    "        f = self.left_wheel.backward(e)\n",
-    "        g = self.middle_wheel.backward(f)\n",
-    "        h = self.right_wheel.backward(g)\n",
-    "        i = self.plugboard.backward(h)\n",
-    "        return i\n",
-    "    \n",
-    "    def advance(self):\n",
-    "        advance_middle = False\n",
-    "        advance_left = False\n",
-    "        if 0 in self.right_wheel.peg_positions:\n",
-    "            advance_middle = True\n",
-    "        if 0 in self.middle_wheel.peg_positions:\n",
-    "            advance_left = True\n",
-    "            advance_middle = True\n",
-    "        self.right_wheel.advance()\n",
-    "        if advance_middle: self.middle_wheel.advance()\n",
-    "        if advance_left: self.left_wheel.advance()\n",
-    "            \n",
-    "    def encipher_letter(self, letter):\n",
-    "        self.advance()\n",
-    "        return self.lookup(letter)\n",
-    "    \n",
-    "    def encipher(self, message):\n",
-    "        enciphered = ''\n",
-    "        for letter in clean(message):\n",
-    "            enciphered += self.encipher_letter(letter)\n",
-    "        return enciphered"
+    "            return object.__getattribute__(self, name)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Set the wheels to the initial positions. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 168,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def set_wheels(self, left_wheel_position, middle_wheel_position, right_wheel_position):\n",
+    "    self.left_wheel.set_position(left_wheel_position)\n",
+    "    self.middle_wheel.set_position(middle_wheel_position)\n",
+    "    self.right_wheel.set_position(right_wheel_position)\n",
+    "\n",
+    "setattr(Enigma, 'set_wheels', set_wheels)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "`lookup` just follows a path through the machine without changing the positions of any parts. It just follows a signal from the input, thorough all the components, to the reflector, back through all the components, to the output."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 169,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def lookup(self, letter):\n",
+    "    a = self.plugboard.forward(letter)\n",
+    "    b = self.right_wheel.forward(a)\n",
+    "    c = self.middle_wheel.forward(b)\n",
+    "    d = self.left_wheel.forward(c)\n",
+    "    e = self.reflector.forward(d)\n",
+    "    f = self.left_wheel.backward(e)\n",
+    "    g = self.middle_wheel.backward(f)\n",
+    "    h = self.right_wheel.backward(g)\n",
+    "    i = self.plugboard.backward(h)\n",
+    "    return i\n",
+    "\n",
+    "setattr(Enigma, 'lookup', lookup)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "`advance` moves the wheels on one step. The right wheel always advances. If the notch is in the zero position, the wheel also advances the wheel to the left. \n",
+    "\n",
+    "It follows the 'double stepping' behaviour of the engima machines."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 170,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def advance(self):\n",
+    "    advance_middle = False\n",
+    "    advance_left = False\n",
+    "    if 0 in self.right_wheel.notch_positions:\n",
+    "        advance_middle = True\n",
+    "    if 0 in self.middle_wheel.notch_positions:\n",
+    "        advance_left = True\n",
+    "        advance_middle = True\n",
+    "    self.right_wheel.advance()\n",
+    "    if advance_middle: self.middle_wheel.advance()\n",
+    "    if advance_left: self.left_wheel.advance()\n",
+    "\n",
+    "setattr(Enigma, 'advance', advance)        "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Finally, encipher letters and messages. \n",
+    "\n",
+    "Note that the wheels advance _before_ the letter signal is sent through the machine: in the physical machine, the advancing is done by pressing the key on the keyboard. \n",
+    "\n",
+    "Also note that the messages are cleaned before use, so letters are converted to lower case and non-letters are removed."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 220,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def encipher_letter(self, letter):\n",
+    "    self.advance()\n",
+    "    return self.lookup(letter)\n",
+    "\n",
+    "def encipher(self, message, debug=False):\n",
+    "    enciphered = ''\n",
+    "    for letter in clean(message):\n",
+    "        enciphered += self.encipher_letter(letter)\n",
+    "        if debug:\n",
+    "            print('Wheels now', list(self.wheel_positions_l), 'enciphering {} -> {}'.format(letter, self.lookup(letter)))\n",
+    "    return enciphered\n",
+    "\n",
+    "setattr(Enigma, 'encipher_letter', encipher_letter)\n",
+    "setattr(Enigma, 'encipher', encipher)\n",
+    "setattr(Enigma, 'decipher', encipher)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<a name=\"testingenigma\"></a>\n",
+    "## Testing Enigma\n",
+    "[Top](#top)\n",
+    "\n",
+    "Some tests of the Enigma machine."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 41,
+   "execution_count": 172,
    "metadata": {
-    "collapsed": false
+    "collapsed": true
    },
    "outputs": [],
    "source": [
     "enigma = Enigma(reflector_b_spec, \n",
-    "                wheel_i_spec, wheel_i_pegs,\n",
-    "                wheel_ii_spec, wheel_ii_pegs,\n",
-    "                wheel_iii_spec, wheel_iii_pegs,\n",
+    "                wheel_i_spec, wheel_i_notches,\n",
+    "                wheel_ii_spec, wheel_ii_notches,\n",
+    "                wheel_iii_spec, wheel_iii_notches,\n",
     "                1, 1, 1,\n",
     "                '')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 42,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 173,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'u'"
       ]
      },
-     "execution_count": 42,
+     "execution_count": 173,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 43,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 174,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'a'"
       ]
      },
-     "execution_count": 43,
+     "execution_count": 174,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 44,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 175,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'uejobtpzwcnsrkdgvmlfaqiyxh'"
       ]
      },
-     "execution_count": 44,
+     "execution_count": 175,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 45,
+   "execution_count": 176,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'abcdefghijklmnopqrstuvwxyz'"
+      ]
+     },
+     "execution_count": 176,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(enigma.lookup(enigma.lookup(l)) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 177,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "assert(cat(enigma.lookup(enigma.lookup(l)) for l in string.ascii_lowercase) == string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 178,
    "metadata": {
-    "collapsed": false,
     "scrolled": true
    },
    "outputs": [
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "0 :: a a a ; [16] [4] [21] uejobtpzwcnsrkdgvmlfaqiyxh\n",
-      "1 :: a a b ; [16] [4] [20] baqmfexihswpdytlcvjozrkgnu\n",
-      "2 :: a a c ; [16] [4] [19] djralkwpobfeyqihncxzvugsmt\n",
-      "3 :: a a d ; [16] [4] [18] zlejcuitgdmbkonsvxphfqyrwa\n",
-      "4 :: a a e ; [16] [4] [17] gcblwtakqzhdosmxiunfryepvj\n",
-      "5 :: a a f ; [16] [4] [16] osnirgfmdpvuhcajwebxlkqtzy\n",
-      "6 :: a a g ; [16] [4] [15] wymvnqzjlhoicekuftxrpdasbg\n",
-      "7 :: a a h ; [16] [4] [14] cjafkdztpbeuormiwnvhlsqyxg\n",
-      "8 :: a a i ; [16] [4] [13] xijuyslvbczgnmqwotfrdhpaek\n",
-      "9 :: a a j ; [16] [4] [12] lfzrwbytjisaovmuxdkhpneqgc\n",
-      "10 :: a a k ; [16] [4] [11] tkezcqynuwbpvhslfxoaimjrgd\n",
-      "11 :: a a l ; [16] [4] [10] kiwfnduxbsaotelqpvjmgrchzy\n",
-      "12 :: a a m ; [16] [4] [9] sfkutbpoxycrnmhgwlaedzqijv\n",
-      "13 :: a a n ; [16] [4] [8] baqwlkhgrsfextpocijnvudmzy\n",
-      "14 :: a a o ; [16] [4] [7] teofbdzxqkjyrscvimnawpuhlg\n",
-      "15 :: a a p ; [16] [4] [6] mhypswrbzxqvaondkgeutlfjci\n",
-      "16 :: a a q ; [16] [4] [5] cpasnrhgkuixzevbyfdwjotlqm\n",
-      "17 :: a a r ; [16] [4] [4] dlfatcjwygvbnmzrxpueskhqio\n",
-      "18 :: a a s ; [16] [4] [3] lxymzjuqtfpadsrkhonigwvbce\n",
-      "19 :: a a t ; [16] [4] [2] puvioztjdhxmlyeawsrgbcqknf\n",
-      "20 :: a a u ; [16] [4] [1] baigpldqcowfyzjehvtsxrkumn\n",
-      "21 :: a a v ; [16] [4] [0] mnvfydiwgzsoablrxpkutchqej\n",
-      "22 :: a b w ; [16] [3] [25] ulfopcykswhbzvderqixanjtgm\n",
-      "23 :: a b x ; [16] [3] [24] qmwftdyovursbzhxaklejicpgn\n",
-      "24 :: a b y ; [16] [3] [23] oljmzxrvucybdqasngpwihtfke\n",
-      "25 :: a b z ; [16] [3] [22] fwevcalzxutgysrqponkjdbimh\n"
+      "0 :: a a a ; [10] [22] [5] uejobtpzwcnsrkdgvmlfaqiyxh\n",
+      "1 :: a a b ; [10] [22] [6] baqmfexihswpdytlcvjozrkgnu\n",
+      "2 :: a a c ; [10] [22] [7] djralkwpobfeyqihncxzvugsmt\n",
+      "3 :: a a d ; [10] [22] [8] zlejcuitgdmbkonsvxphfqyrwa\n",
+      "4 :: a a e ; [10] [22] [9] gcblwtakqzhdosmxiunfryepvj\n",
+      "5 :: a a f ; [10] [22] [10] osnirgfmdpvuhcajwebxlkqtzy\n",
+      "6 :: a a g ; [10] [22] [11] wymvnqzjlhoicekuftxrpdasbg\n",
+      "7 :: a a h ; [10] [22] [12] cjafkdztpbeuormiwnvhlsqyxg\n",
+      "8 :: a a i ; [10] [22] [13] xijuyslvbczgnmqwotfrdhpaek\n",
+      "9 :: a a j ; [10] [22] [14] lfzrwbytjisaovmuxdkhpneqgc\n",
+      "10 :: a a k ; [10] [22] [15] tkezcqynuwbpvhslfxoaimjrgd\n",
+      "11 :: a a l ; [10] [22] [16] kiwfnduxbsaotelqpvjmgrchzy\n",
+      "12 :: a a m ; [10] [22] [17] sfkutbpoxycrnmhgwlaedzqijv\n",
+      "13 :: a a n ; [10] [22] [18] baqwlkhgrsfextpocijnvudmzy\n",
+      "14 :: a a o ; [10] [22] [19] teofbdzxqkjyrscvimnawpuhlg\n",
+      "15 :: a a p ; [10] [22] [20] mhypswrbzxqvaondkgeutlfjci\n",
+      "16 :: a a q ; [10] [22] [21] cpasnrhgkuixzevbyfdwjotlqm\n",
+      "17 :: a a r ; [10] [22] [22] dlfatcjwygvbnmzrxpueskhqio\n",
+      "18 :: a a s ; [10] [22] [23] lxymzjuqtfpadsrkhonigwvbce\n",
+      "19 :: a a t ; [10] [22] [24] puvioztjdhxmlyeawsrgbcqknf\n",
+      "20 :: a a u ; [10] [22] [25] baigpldqcowfyzjehvtsxrkumn\n",
+      "21 :: a a v ; [10] [22] [0] mnvfydiwgzsoablrxpkutchqej\n",
+      "22 :: a b w ; [10] [23] [1] ulfopcykswhbzvderqixanjtgm\n",
+      "23 :: a b x ; [10] [23] [2] qmwftdyovursbzhxaklejicpgn\n",
+      "24 :: a b y ; [10] [23] [3] oljmzxrvucybdqasngpwihtfke\n",
+      "25 :: a b z ; [10] [23] [4] fwevcalzxutgysrqponkjdbimh\n"
      ]
     }
    ],
    "source": [
+    "# check the middle wheel turns over\n",
     "enigma.set_wheels('a', 'a', 'a')\n",
     "for i in range(26):\n",
     "    print(i, '::', \n",
     "          enigma.left_wheel.position_l, enigma.middle_wheel.position_l, enigma.right_wheel.position_l, ';',\n",
-    "          enigma.left_wheel.peg_positions, enigma.middle_wheel.peg_positions, enigma.right_wheel.peg_positions, \n",
+    "          enigma.left_wheel.notch_positions, enigma.middle_wheel.notch_positions, enigma.right_wheel.notch_positions, \n",
     "         cat(enigma.lookup(l) for l in string.ascii_lowercase))\n",
     "    enigma.advance()"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Formal test of middle wheel turnover"
+   ]
+  },
   {
    "cell_type": "code",
-   "execution_count": 46,
+   "execution_count": 179,
    "metadata": {
-    "collapsed": false,
+    "collapsed": true,
     "scrolled": true
    },
    "outputs": [],
     "enigma.set_wheels('a', 'a', 't')\n",
     "assert(enigma.wheel_positions == (0, 0, 19))\n",
     "assert(cat(enigma.wheel_positions_l) == 'aat')\n",
-    "assert(enigma.peg_positions == ([16], [4], [2]))\n",
+    "assert(enigma.notch_positions == ([10], [22], [24]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'puvioztjdhxmlyeawsrgbcqknf')\n",
     "\n",
     "enigma.advance()\n",
     "assert(enigma.wheel_positions == (0, 0, 20))\n",
     "assert(cat(enigma.wheel_positions_l) == 'aau')\n",
-    "assert(enigma.peg_positions == ([16], [4], [1]))\n",
+    "assert(enigma.notch_positions == ([10], [22], [25]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'baigpldqcowfyzjehvtsxrkumn')\n",
     "\n",
     "enigma.advance()\n",
     "assert(enigma.wheel_positions == (0, 0, 21))\n",
     "assert(cat(enigma.wheel_positions_l) == 'aav')\n",
-    "assert(enigma.peg_positions == ([16], [4], [0]))\n",
+    "assert(enigma.notch_positions == ([10], [22], [0]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'mnvfydiwgzsoablrxpkutchqej')\n",
     "\n",
     "enigma.advance()\n",
     "assert(enigma.wheel_positions == (0, 1, 22))\n",
     "assert(cat(enigma.wheel_positions_l) == 'abw')\n",
-    "assert(enigma.peg_positions == ([16], [3], [25]))\n",
+    "assert(enigma.notch_positions == ([10], [23], [1]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ulfopcykswhbzvderqixanjtgm')\n",
     "\n",
     "enigma.advance()\n",
     "assert(enigma.wheel_positions == (0, 1, 23))\n",
     "assert(cat(enigma.wheel_positions_l) == 'abx')\n",
-    "assert(enigma.peg_positions == ([16], [3], [24]))\n",
+    "assert(enigma.notch_positions == ([10], [23], [2]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qmwftdyovursbzhxaklejicpgn')\n",
     "\n",
     "enigma.advance()\n",
     "assert(enigma.wheel_positions == (0, 1, 24))\n",
     "assert(cat(enigma.wheel_positions_l) == 'aby')\n",
-    "assert(enigma.peg_positions == ([16], [3], [23]))\n",
+    "assert(enigma.notch_positions == ([10], [23 ], [3]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'oljmzxrvucybdqasngpwihtfke')\n"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Test of middle wheel advancing the left wheel, exhibiting the \"double step\" behaviour."
+   ]
+  },
   {
    "cell_type": "code",
-   "execution_count": 47,
+   "execution_count": 180,
    "metadata": {
-    "collapsed": false,
     "scrolled": true
    },
    "outputs": [
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "(1, 5, 24) ('b', 'f', 'y') ([15], [25], [23]) baknstqzrmcxjdvygiefwoulph\n"
+      "(1, 5, 24) ('b', 'f', 'y') ([11], [1], [3]) baknstqzrmcxjdvygiefwoulph\n"
      ]
     }
    ],
     "enigma.set_wheels('a', 'd', 't')\n",
     "assert(enigma.wheel_positions == (0, 3, 19))\n",
     "assert(cat(enigma.wheel_positions_l) == 'adt')\n",
-    "assert(enigma.peg_positions == ([16], [1], [2]))\n",
+    "assert(enigma.notch_positions == ([10], [25], [24]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'zcbpqxwsjiuonmldethrkygfva')\n",
     "\n",
     "enigma.advance()\n",
     "assert(enigma.wheel_positions == (0, 3, 20))\n",
     "assert(cat(enigma.wheel_positions_l) == 'adu')\n",
-    "assert(enigma.peg_positions == ([16], [1], [1]))\n",
+    "assert(enigma.notch_positions == ([10], [25], [25]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ehprawjbngotxikcsdqlzyfmvu')\n",
     "\n",
     "enigma.advance()\n",
     "assert(enigma.wheel_positions == (0, 3, 21))\n",
     "assert(cat(enigma.wheel_positions_l) == 'adv')\n",
-    "assert(enigma.peg_positions == ([16], [1], [0]))\n",
+    "assert(enigma.notch_positions == ([10], [25], [0]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'eqzxarpihmnvjkwgbfuyslodtc')\n",
     "\n",
     "enigma.advance()\n",
     "assert(enigma.wheel_positions == (0, 4, 22))\n",
     "assert(cat(enigma.wheel_positions_l) == 'aew')\n",
-    "assert(enigma.peg_positions == ([16], [0], [25]))\n",
+    "assert(enigma.notch_positions == ([10], [0], [1]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qedcbtpluzmhkongavwfirsyxj')\n",
     "\n",
     "enigma.advance()\n",
     "assert(enigma.wheel_positions == (1, 5, 23))\n",
     "assert(cat(enigma.wheel_positions_l) == 'bfx')\n",
-    "assert(enigma.peg_positions == ([15], [25], [24]))\n",
+    "assert(enigma.notch_positions == ([11], [1], [2]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'iwuedhsfazqxytvrkpgncoblmj')\n",
     "\n",
     "enigma.advance()\n",
     "assert(enigma.wheel_positions == (1, 5, 24))\n",
     "assert(cat(enigma.wheel_positions_l) == 'bfy')\n",
-    "assert(enigma.peg_positions == ([15], [25], [23]))\n",
+    "assert(enigma.notch_positions == ([11], [1], [3]))\n",
     "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'baknstqzrmcxjdvygiefwoulph')\n",
     "\n",
-    "print(enigma.wheel_positions, enigma.wheel_positions_l, enigma.peg_positions, \n",
+    "print(enigma.wheel_positions, enigma.wheel_positions_l, enigma.notch_positions, \n",
     "         cat(enigma.lookup(l) for l in string.ascii_lowercase))\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 48,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 181,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'olpfhnvflyn'"
       ]
      },
-     "execution_count": 48,
+     "execution_count": 181,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 49,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 182,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'lawnjgpwjik'"
       ]
      },
-     "execution_count": 49,
+     "execution_count": 182,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 50,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 183,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (0, 3, 20))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'adu')\n",
+      "assert(enigma.notch_positions == ([10], [25], [25]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ehprawjbngotxikcsdqlzyfmvu')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (0, 3, 21))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'adv')\n",
+      "assert(enigma.notch_positions == ([10], [25], [0]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'eqzxarpihmnvjkwgbfuyslodtc')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (0, 4, 22))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'aew')\n",
+      "assert(enigma.notch_positions == ([10], [0], [1]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qedcbtpluzmhkongavwfirsyxj')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 23))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfx')\n",
+      "assert(enigma.notch_positions == ([11], [1], [2]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'iwuedhsfazqxytvrkpgncoblmj')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 24))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfy')\n",
+      "assert(enigma.notch_positions == ([11], [1], [3]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'baknstqzrmcxjdvygiefwoulph')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 25))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfz')\n",
+      "assert(enigma.notch_positions == ([11], [1], [4]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'mudcgteyjiwravxzslqfbnkohp')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 0))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfa')\n",
+      "assert(enigma.notch_positions == ([11], [1], [5]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'gjkuotarmbcziwesvhpfdqnyxl')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 1))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfb')\n",
+      "assert(enigma.notch_positions == ([11], [1], [6]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'fcbmpaqihxytdrvegnwlzosjku')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 2))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfc')\n",
+      "assert(enigma.notch_positions == ([11], [1], [7]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'jktrlpmywabegzqfodxcvuishn')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 3))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfd')\n",
+      "assert(enigma.notch_positions == ([11], [1], [8]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'baetclivgurfzqponkxdjhyswm')\n",
+      "\n",
+      "enigma.advance()\n",
+      "assert(enigma.wheel_positions == (1, 5, 4))\n",
+      "assert(cat(enigma.wheel_positions_l) == 'bfe')\n",
+      "assert(enigma.notch_positions == ([11], [1], [9]))\n",
+      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'wvjlkpzxtcedqsyfmunirbahog')\n",
+      "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 5))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bff')\n",
-      "assert(enigma.peg_positions == ([15], [25], [16]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [10]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'zpqiogfsdlmjkyebcvhxwrutna')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 6))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfg')\n",
-      "assert(enigma.peg_positions == ([15], [25], [15]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [11]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'fjmnwayslbxicdpouthrqzekgv')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 7))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfh')\n",
-      "assert(enigma.peg_positions == ([15], [25], [14]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [12]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'csafzdyloxuhnmitwvbpkrqjge')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 8))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfi')\n",
-      "assert(enigma.peg_positions == ([15], [25], [13]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [13]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'kihyvulcbtagwrqzonxjfemsdp')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 9))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfj')\n",
-      "assert(enigma.peg_positions == ([15], [25], [12]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [14]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'pgzrytbksqhwxvuajdifonlmec')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 10))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfk')\n",
-      "assert(enigma.peg_positions == ([15], [25], [11]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [15]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'fkirsazncwbvyhpoudexqljtmg')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 11))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfl')\n",
-      "assert(enigma.peg_positions == ([15], [25], [10]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [16]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'mhkronubsvctafeqpdilgjxwzy')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 12))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfm')\n",
-      "assert(enigma.peg_positions == ([15], [25], [9]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [17]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'gnkuoxarzycmlbetvhwpdqsfji')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 13))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfn')\n",
-      "assert(enigma.peg_positions == ([15], [25], [8]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [18]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'bainslqkcxhfudpogtermwvjzy')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 14))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfo')\n",
-      "assert(enigma.peg_positions == ([15], [25], [7]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [19]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'xemfbdnwjitycgzusvqkprhalo')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 15))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfp')\n",
-      "assert(enigma.peg_positions == ([15], [25], [6]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [20]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qixksmhgbtdvfonrapejwluczy')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 16))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfq')\n",
-      "assert(enigma.peg_positions == ([15], [25], [5]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [21]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cgaulmbskwiefrtzynhodxjvqp')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 17))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfr')\n",
-      "assert(enigma.peg_positions == ([15], [25], [4]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [22]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'iwqfldszaxvenmyrcpgutkbjoh')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 18))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bfs')\n",
-      "assert(enigma.peg_positions == ([15], [25], [3]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [23]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'vxykrjilgfdhqtusmepnoazbcw')\n",
       "\n",
       "enigma.advance()\n",
       "assert(enigma.wheel_positions == (1, 5, 19))\n",
       "assert(cat(enigma.wheel_positions_l) == 'bft')\n",
-      "assert(enigma.peg_positions == ([15], [25], [2]))\n",
+      "assert(enigma.notch_positions == ([11], [1], [24]))\n",
       "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ieysbvkjahgmlpxnwtdrzfqocu')\n",
-      "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (1, 5, 20))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'bfu')\n",
-      "assert(enigma.peg_positions == ([15], [25], [1]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'baihkjvdcfepywsltxoqzgnrmu')\n",
-      "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (1, 5, 21))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'bfv')\n",
-      "assert(enigma.peg_positions == ([15], [25], [0]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'bayjtrilgdshvzuwxfkeompqcn')\n",
-      "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (1, 6, 22))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'bgw')\n",
-      "assert(enigma.peg_positions == ([15], [24], [25]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'rszqohpfxyutvwegdablkmnijc')\n",
-      "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (1, 6, 23))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'bgx')\n",
-      "assert(enigma.peg_positions == ([15], [24], [24]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'nfoxhbzeyrwqpacmljtsvukdig')\n",
-      "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (1, 6, 24))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'bgy')\n",
-      "assert(enigma.peg_positions == ([15], [24], [23]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'czaogmeihtuqfsdxlwnjkyrpvb')\n",
-      "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (1, 6, 25))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'bgz')\n",
-      "assert(enigma.peg_positions == ([15], [24], [22]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'omwgysdjkhizbxarupfvqtcnel')\n",
-      "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (1, 6, 0))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'bga')\n",
-      "assert(enigma.peg_positions == ([15], [24], [21]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cxafmdrzoqutepinjgvlksybwh')\n",
-      "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (1, 6, 1))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'bgb')\n",
-      "assert(enigma.peg_positions == ([15], [24], [20]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'jymvnrxkoahwceiuzftspdlgbq')\n",
-      "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (1, 6, 2))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'bgc')\n",
-      "assert(enigma.peg_positions == ([15], [24], [19]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'uzlyiqwrestcnmxvfhjkapgodb')\n",
-      "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (1, 6, 3))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'bgd')\n",
-      "assert(enigma.peg_positions == ([15], [24], [18]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'veosbuhgpzqynmcikwdxfartlj')\n",
-      "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (1, 6, 4))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'bge')\n",
-      "assert(enigma.peg_positions == ([15], [24], [17]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ydhbmtrclxsiezpougkfqwvjan')\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "\n",
+    "enigma.set_wheels('a', 'd', 't')\n",
     "for i in range(26):\n",
     "    enigma.advance()\n",
     "    print('enigma.advance()')\n",
     "    print(\"assert(enigma.wheel_positions == {})\".format(enigma.wheel_positions))\n",
     "    print(\"assert(cat(enigma.wheel_positions_l) == '{}')\".format(cat(enigma.wheel_positions_l)))\n",
-    "    print(\"assert(enigma.peg_positions == {})\".format(enigma.peg_positions))\n",
+    "    print(\"assert(enigma.notch_positions == {})\".format(enigma.notch_positions))\n",
     "    print(\"assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == '{}')\".format(cat(enigma.lookup(l) for l in string.ascii_lowercase)))\n",
     "    print()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 51,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 184,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'bahxvfrpdc'"
       ]
      },
-     "execution_count": 51,
+     "execution_count": 184,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 52,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 185,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'kvmmwrlqlqsqpeugjrcxzwpfyiyybwloewrouvkpoztceuwtfjzqwpbqldttsr'"
       ]
      },
-     "execution_count": 52,
+     "execution_count": 185,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 53,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 186,
+   "metadata": {},
    "outputs": [
     {
      "data": {
        "'c'"
       ]
      },
-     "execution_count": 53,
+     "execution_count": 186,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 54,
+   "execution_count": 187,
    "metadata": {
     "collapsed": true
    },
     "# Enigma simulation settings are \n",
     "# http://enigma.louisedade.co.uk/enigma.html?m3;b;b153;AFTX;AJFE;AU-BG-EY-FP-HL-IN-JZ-OS-QR-TX\n",
     "w_enigma = Enigma(reflector_b_spec, \n",
-    "                wheel_i_spec, wheel_i_pegs,\n",
-    "                wheel_v_spec, wheel_v_pegs,\n",
-    "                wheel_iii_spec, wheel_iii_pegs,\n",
+    "                wheel_i_spec, wheel_i_notches,\n",
+    "                wheel_v_spec, wheel_v_notches,\n",
+    "                wheel_iii_spec, wheel_iii_notches,\n",
     "                6, 20, 24,\n",
     "                'ua pf rq so ni ey bg hl tx zj')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 55,
+   "execution_count": 188,
    "metadata": {
     "collapsed": true
    },
    "outputs": [],
    "source": [
-    "# Setting sheet line 31 from http://www.codesandciphers.org.uk/enigma/enigma3.htm\n",
-    "# Enigma simulation settings are \n",
-    "# http://enigma.louisedade.co.uk/enigma.html?m3;b;b153;AFTX;AJFE;AU-BG-EY-FP-HL-IN-JZ-OS-QR-TX\n",
-    "enigma = Enigma(reflector_b_spec, \n",
-    "                wheel_i_spec, wheel_i_pegs,\n",
-    "                wheel_v_spec, wheel_v_pegs,\n",
-    "                wheel_iii_spec, wheel_iii_pegs,\n",
-    "                6, 20, 24,\n",
-    "                'ua pf rq so ni ey bg hl tx zj')"
+    "# Setting sheet line 31 from http://www.codesandciphers.org.uk/enigma/enigma3.htm\n",
+    "# Enigma simulation settings are \n",
+    "# http://enigma.louisedade.co.uk/enigma.html?m3;b;b153;AFTX;AJFE;AU-BG-EY-FP-HL-IN-JZ-OS-QR-TX\n",
+    "enigma = Enigma(reflector_b_spec, \n",
+    "#                 wheel_i_spec, wheel_i_notches,\n",
+    "#                 wheel_v_spec, wheel_v_notches,\n",
+    "#                 wheel_iii_spec, wheel_iii_notches,\n",
+    "                6, 20, 24,\n",
+    "                'ua pf rq so ni ey bg hl tx zj')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 56,
+   "execution_count": 189,
    "metadata": {
     "collapsed": true
    },
    "outputs": [],
    "source": [
-    "enigma.set_wheels('j', 'e', 'u')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (4, 11, 24))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'jev')\n",
-    "assert(enigma.peg_positions == ([7], [21], [0]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'mvqjlyowkdieasgzcunxrbhtfp')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (4, 12, 25))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'jfw')\n",
-    "assert(enigma.peg_positions == ([7], [20], [25]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'sjolzuyvrbwdpxcmtiaqfhknge')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (4, 12, 0))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'jfx')\n",
-    "assert(enigma.peg_positions == ([7], [20], [24]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qrxedkoywufmlvgsabpzjnicht')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (4, 12, 1))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'jfy')\n",
-    "assert(enigma.peg_positions == ([7], [20], [23]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'hpsukliagqefwvtbjxcodnmrzy')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (4, 12, 2))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'jfz')\n",
-    "assert(enigma.peg_positions == ([7], [20], [22]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'zevnbpyqowrtxdifhkulscjmga')\n"
+    "w_enigma.set_wheels('j', 'e', 'u')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (4, 11, 24))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'jev')\n",
+    "assert(w_enigma.notch_positions == ([19], [5], [0]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'mvqjlyowkdieasgzcunxrbhtfp')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (4, 12, 25))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'jfw')\n",
+    "assert(w_enigma.notch_positions == ([19], [6], [1]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'sjolzuyvrbwdpxcmtiaqfhknge')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (4, 12, 0))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'jfx')\n",
+    "assert(w_enigma.notch_positions == ([19], [6], [2]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'qrxedkoywufmlvgsabpzjnicht')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (4, 12, 1))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'jfy')\n",
+    "assert(w_enigma.notch_positions == ([19], [6], [3]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'hpsukliagqefwvtbjxcodnmrzy')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (4, 12, 2))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'jfz')\n",
+    "assert(w_enigma.notch_positions == ([19], [6], [4]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'zevnbpyqowrtxdifhkulscjmga')\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 57,
+   "execution_count": 190,
    "metadata": {
     "collapsed": true
    },
    "outputs": [],
    "source": [
-    "enigma.set_wheels('i', 'd', 'z')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 3))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'ida')\n",
-    "assert(enigma.peg_positions == ([8], [22], [21]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ikhpqrvcambzjondefwyxgsutl')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 4))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idb')\n",
-    "assert(enigma.peg_positions == ([8], [22], [20]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cdabskhgzwfmlqvunyexpojtri')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 5))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idc')\n",
-    "assert(enigma.peg_positions == ([8], [22], [19]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'pcbwiqhgemyvjsuaftnroldzkx')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 6))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idd')\n",
-    "assert(enigma.peg_positions == ([8], [22], [18]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'xcbfvdnouptmlghjzwykierasq')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 7))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'ide')\n",
-    "assert(enigma.peg_positions == ([8], [22], [17]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'xfvglbdynuseriwqpmkzjcoaht')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 8))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idf')\n",
-    "assert(enigma.peg_positions == ([8], [22], [16]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'tfpqlbouynsewjgcdxkahzmriv')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 9))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idg')\n",
-    "assert(enigma.peg_positions == ([8], [22], [15]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cjaunvlwtbygzexrspqidfhokm')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 10))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idh')\n",
-    "assert(enigma.peg_positions == ([8], [22], [14]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'yltxkrqvowebzpingfucshjdam')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 11))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idi')\n",
-    "assert(enigma.peg_positions == ([8], [22], [13]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'myktluzrnxceaiqsohpdfwvjbg')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 12))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idj')\n",
-    "assert(enigma.peg_positions == ([8], [22], [12]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'pynjrmiugdqxfcvakewzhoslbt')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 13))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idk')\n",
-    "assert(enigma.peg_positions == ([8], [22], [11]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'mwvedyplnoxhaijgrqtszcbkfu')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 14))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idl')\n",
-    "assert(enigma.peg_positions == ([8], [22], [10]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qcbrfeutvoxpnmjladzhgiykws')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 15))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idm')\n",
-    "assert(enigma.peg_positions == ([8], [22], [9]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'dnoahryetsmukbcvwfjilpqzgx')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 16))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idn')\n",
-    "assert(enigma.peg_positions == ([8], [22], [8]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'nidcfehgbqsovalyjzkxwmutpr')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 17))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'ido')\n",
-    "assert(enigma.peg_positions == ([8], [22], [7]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'joifxdulcarhzpbntkwqgysevm')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 18))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idp')\n",
-    "assert(enigma.peg_positions == ([8], [22], [6]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ptnlsxvozmwdjchayuebrgkfqi')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 19))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idq')\n",
-    "assert(enigma.peg_positions == ([8], [22], [5]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'slwopzqnmxybihdeguavrtcjkf')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 20))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idr')\n",
-    "assert(enigma.peg_positions == ([8], [22], [4]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'hcbedwlamzogixkytsrqvufnpj')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 21))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'ids')\n",
-    "assert(enigma.peg_positions == ([8], [22], [3]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'odxbjwzrmelkisavuhnyqpfctg')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 22))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idt')\n",
-    "assert(enigma.peg_positions == ([8], [22], [2]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'udgbfeclrwnhxksvtioqapjmzy')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 23))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idu')\n",
-    "assert(enigma.peg_positions == ([8], [22], [1]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'nrdczqxmowvshaiufblypkjgte')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 10, 24))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'idv')\n",
-    "assert(enigma.peg_positions == ([8], [22], [0]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'hkifjdoacebqtzgulyvmpsxwrn')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 11, 25))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'iew')\n",
-    "assert(enigma.peg_positions == ([8], [21], [25]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'yptzuhofqvnmlkgbixwcejsrad')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 11, 0))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'iex')\n",
-    "assert(enigma.peg_positions == ([8], [21], [24]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'vkdcwhqfjibzsptngumoraeyxl')\n",
-    "\n",
-    "enigma.advance()\n",
-    "assert(enigma.wheel_positions == (3, 11, 1))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'iey')\n",
-    "assert(enigma.peg_positions == ([8], [21], [23]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'wenpbqrouxlkychdfgzvitajms')\n"
+    "w_enigma.set_wheels('i', 'd', 'z')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 3))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'ida')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [5]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'ikhpqrvcambzjondefwyxgsutl')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 4))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idb')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [6]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'cdabskhgzwfmlqvunyexpojtri')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 5))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idc')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [7]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'pcbwiqhgemyvjsuaftnroldzkx')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 6))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idd')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [8]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'xcbfvdnouptmlghjzwykierasq')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 7))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'ide')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [9]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'xfvglbdynuseriwqpmkzjcoaht')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 8))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idf')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [10]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'tfpqlbouynsewjgcdxkahzmriv')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 9))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idg')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [11]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'cjaunvlwtbygzexrspqidfhokm')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 10))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idh')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [12]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'yltxkrqvowebzpingfucshjdam')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 11))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idi')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [13]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'myktluzrnxceaiqsohpdfwvjbg')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 12))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idj')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [14]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'pynjrmiugdqxfcvakewzhoslbt')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 13))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idk')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [15]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'mwvedyplnoxhaijgrqtszcbkfu')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 14))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idl')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [16]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'qcbrfeutvoxpnmjladzhgiykws')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 15))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idm')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [17]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'dnoahryetsmukbcvwfjilpqzgx')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 16))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idn')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [18]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'nidcfehgbqsovalyjzkxwmutpr')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 17))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'ido')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [19]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'joifxdulcarhzpbntkwqgysevm')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 18))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idp')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [20]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'ptnlsxvozmwdjchayuebrgkfqi')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 19))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idq')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [21]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'slwopzqnmxybihdeguavrtcjkf')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 20))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idr')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [22]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'hcbedwlamzogixkytsrqvufnpj')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 21))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'ids')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [23]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'odxbjwzrmelkisavuhnyqpfctg')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 22))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idt')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [24]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'udgbfeclrwnhxksvtioqapjmzy')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 23))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idu')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [25]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'nrdczqxmowvshaiufblypkjgte')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 10, 24))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'idv')\n",
+    "assert(w_enigma.notch_positions == ([18], [4], [0]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'hkifjdoacebqtzgulyvmpsxwrn')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 11, 25))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'iew')\n",
+    "assert(w_enigma.notch_positions == ([18], [5], [1]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'yptzuhofqvnmlkgbixwcejsrad')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 11, 0))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'iex')\n",
+    "assert(w_enigma.notch_positions == ([18], [5], [2]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'vkdcwhqfjibzsptngumoraeyxl')\n",
+    "\n",
+    "w_enigma.advance()\n",
+    "assert(w_enigma.wheel_positions == (3, 11, 1))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'iey')\n",
+    "assert(w_enigma.notch_positions == ([18], [5], [3]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'wenpbqrouxlkychdfgzvitajms')\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 58,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 191,
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "('verylongtestmessagewithanextrabitofmessageforgoodmeasure',\n",
-       " (3, 12, 6),\n",
-       " ('i', 'f', 'd'),\n",
-       " ([8], [20], [18]),\n",
-       " 'urygzpdmxtwshqvfnbljaokice')"
+       "([18], [5], [3])"
       ]
      },
-     "execution_count": 58,
+     "execution_count": 191,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "enigma.set_wheels('i', 'd', 'z')\n",
-    "ct = enigma.encipher('verylongtestmessagewithanextrabitofmessageforgoodmeasure')\n",
-    "assert(ct == 'gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz')\n",
-    "assert(enigma.wheel_positions == (3, 12, 6))\n",
-    "assert(cat(enigma.wheel_positions_l) == 'ifd')\n",
-    "assert(enigma.peg_positions == ([8], [20], [18]))\n",
-    "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'urygzpdmxtwshqvfnbljaokice')\n",
+    "w_enigma.notch_positions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 192,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('verylongtestmessagewithanextrabitofmessageforgoodmeasure',\n",
+       " (3, 12, 6),\n",
+       " ('i', 'f', 'd'),\n",
+       " ([18], [6], [8]),\n",
+       " 'urygzpdmxtwshqvfnbljaokice')"
+      ]
+     },
+     "execution_count": 192,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "w_enigma.set_wheels('i', 'd', 'z')\n",
+    "ct = w_enigma.encipher('verylongtestmessagewithanextrabitofmessageforgoodmeasure')\n",
+    "assert(ct == 'gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz')\n",
+    "assert(w_enigma.wheel_positions == (3, 12, 6))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'ifd')\n",
+    "assert(w_enigma.notch_positions == ([18], [6], [8]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'urygzpdmxtwshqvfnbljaokice')\n",
     "\n",
-    "enigma.set_wheels('i', 'd', 'z')\n",
-    "pt = enigma.encipher('gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz')\n",
+    "w_enigma.set_wheels('i', 'd', 'z')\n",
+    "pt = w_enigma.encipher('gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz')\n",
     "assert(pt == 'verylongtestmessagewithanextrabitofmessageforgoodmeasure')\n",
     "\n",
-    "pt, enigma.wheel_positions, enigma.wheel_positions_l, enigma.peg_positions, cat(enigma.lookup(l) for l in string.ascii_lowercase)"
+    "pt, w_enigma.wheel_positions, w_enigma.wheel_positions_l, w_enigma.notch_positions, cat(w_enigma.lookup(l) for l in string.ascii_lowercase)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 59,
-   "metadata": {
-    "collapsed": false
-   },
+   "execution_count": 193,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 3))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'ida')\n",
-      "assert(enigma.peg_positions == ([8], [22], [21]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ikhpqrvcambzjondefwyxgsutl')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 3))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'ida')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [5]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'ikhpqrvcambzjondefwyxgsutl')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 4))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idb')\n",
-      "assert(enigma.peg_positions == ([8], [22], [20]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cdabskhgzwfmlqvunyexpojtri')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 4))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idb')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [6]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'cdabskhgzwfmlqvunyexpojtri')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 5))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idc')\n",
-      "assert(enigma.peg_positions == ([8], [22], [19]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'pcbwiqhgemyvjsuaftnroldzkx')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 5))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idc')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [7]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'pcbwiqhgemyvjsuaftnroldzkx')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 6))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idd')\n",
-      "assert(enigma.peg_positions == ([8], [22], [18]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'xcbfvdnouptmlghjzwykierasq')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 6))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idd')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [8]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'xcbfvdnouptmlghjzwykierasq')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 7))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'ide')\n",
-      "assert(enigma.peg_positions == ([8], [22], [17]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'xfvglbdynuseriwqpmkzjcoaht')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 7))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'ide')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [9]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'xfvglbdynuseriwqpmkzjcoaht')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 8))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idf')\n",
-      "assert(enigma.peg_positions == ([8], [22], [16]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'tfpqlbouynsewjgcdxkahzmriv')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 8))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idf')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [10]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'tfpqlbouynsewjgcdxkahzmriv')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 9))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idg')\n",
-      "assert(enigma.peg_positions == ([8], [22], [15]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'cjaunvlwtbygzexrspqidfhokm')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 9))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idg')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [11]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'cjaunvlwtbygzexrspqidfhokm')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 10))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idh')\n",
-      "assert(enigma.peg_positions == ([8], [22], [14]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'yltxkrqvowebzpingfucshjdam')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 10))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idh')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [12]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'yltxkrqvowebzpingfucshjdam')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 11))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idi')\n",
-      "assert(enigma.peg_positions == ([8], [22], [13]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'myktluzrnxceaiqsohpdfwvjbg')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 11))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idi')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [13]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'myktluzrnxceaiqsohpdfwvjbg')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 12))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idj')\n",
-      "assert(enigma.peg_positions == ([8], [22], [12]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'pynjrmiugdqxfcvakewzhoslbt')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 12))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idj')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [14]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'pynjrmiugdqxfcvakewzhoslbt')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 13))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idk')\n",
-      "assert(enigma.peg_positions == ([8], [22], [11]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'mwvedyplnoxhaijgrqtszcbkfu')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 13))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idk')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [15]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'mwvedyplnoxhaijgrqtszcbkfu')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 14))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idl')\n",
-      "assert(enigma.peg_positions == ([8], [22], [10]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'qcbrfeutvoxpnmjladzhgiykws')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 14))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idl')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [16]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'qcbrfeutvoxpnmjladzhgiykws')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 15))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idm')\n",
-      "assert(enigma.peg_positions == ([8], [22], [9]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'dnoahryetsmukbcvwfjilpqzgx')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 15))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idm')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [17]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'dnoahryetsmukbcvwfjilpqzgx')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 16))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idn')\n",
-      "assert(enigma.peg_positions == ([8], [22], [8]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'nidcfehgbqsovalyjzkxwmutpr')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 16))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idn')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [18]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'nidcfehgbqsovalyjzkxwmutpr')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 17))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'ido')\n",
-      "assert(enigma.peg_positions == ([8], [22], [7]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'joifxdulcarhzpbntkwqgysevm')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 17))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'ido')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [19]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'joifxdulcarhzpbntkwqgysevm')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 18))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idp')\n",
-      "assert(enigma.peg_positions == ([8], [22], [6]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'ptnlsxvozmwdjchayuebrgkfqi')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 18))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idp')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [20]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'ptnlsxvozmwdjchayuebrgkfqi')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 19))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idq')\n",
-      "assert(enigma.peg_positions == ([8], [22], [5]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'slwopzqnmxybihdeguavrtcjkf')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 19))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idq')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [21]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'slwopzqnmxybihdeguavrtcjkf')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 20))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idr')\n",
-      "assert(enigma.peg_positions == ([8], [22], [4]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'hcbedwlamzogixkytsrqvufnpj')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 20))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idr')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [22]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'hcbedwlamzogixkytsrqvufnpj')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 21))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'ids')\n",
-      "assert(enigma.peg_positions == ([8], [22], [3]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'odxbjwzrmelkisavuhnyqpfctg')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 21))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'ids')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [23]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'odxbjwzrmelkisavuhnyqpfctg')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 22))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idt')\n",
-      "assert(enigma.peg_positions == ([8], [22], [2]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'udgbfeclrwnhxksvtioqapjmzy')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 22))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idt')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [24]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'udgbfeclrwnhxksvtioqapjmzy')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 23))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idu')\n",
-      "assert(enigma.peg_positions == ([8], [22], [1]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'nrdczqxmowvshaiufblypkjgte')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 23))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idu')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [25]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'nrdczqxmowvshaiufblypkjgte')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 10, 24))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'idv')\n",
-      "assert(enigma.peg_positions == ([8], [22], [0]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'hkifjdoacebqtzgulyvmpsxwrn')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 10, 24))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'idv')\n",
+      "assert(w_enigma.notch_positions == ([18], [4], [0]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'hkifjdoacebqtzgulyvmpsxwrn')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 11, 25))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'iew')\n",
-      "assert(enigma.peg_positions == ([8], [21], [25]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'yptzuhofqvnmlkgbixwcejsrad')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 11, 25))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'iew')\n",
+      "assert(w_enigma.notch_positions == ([18], [5], [1]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'yptzuhofqvnmlkgbixwcejsrad')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 11, 0))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'iex')\n",
-      "assert(enigma.peg_positions == ([8], [21], [24]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'vkdcwhqfjibzsptngumoraeyxl')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 11, 0))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'iex')\n",
+      "assert(w_enigma.notch_positions == ([18], [5], [2]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'vkdcwhqfjibzsptngumoraeyxl')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 11, 1))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'iey')\n",
-      "assert(enigma.peg_positions == ([8], [21], [23]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'wenpbqrouxlkychdfgzvitajms')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 11, 1))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'iey')\n",
+      "assert(w_enigma.notch_positions == ([18], [5], [3]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'wenpbqrouxlkychdfgzvitajms')\n",
       "\n",
-      "enigma.advance()\n",
-      "assert(enigma.wheel_positions == (3, 11, 2))\n",
-      "assert(cat(enigma.wheel_positions_l) == 'iez')\n",
-      "assert(enigma.peg_positions == ([8], [21], [22]))\n",
-      "assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == 'szgyqvclkoihurjwenaxmfptdb')\n",
+      "w_enigma.advance()\n",
+      "assert(w_enigma.wheel_positions == (3, 11, 2))\n",
+      "assert(cat(w_enigma.wheel_positions_l) == 'iez')\n",
+      "assert(w_enigma.notch_positions == ([18], [5], [4]))\n",
+      "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'szgyqvclkoihurjwenaxmfptdb')\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "enigma.set_wheels('i', 'd', 'z')\n",
+    "w_enigma.set_wheels('i', 'd', 'z')\n",
     "\n",
     "for i in range(26):\n",
-    "    enigma.advance()\n",
-    "    print('enigma.advance()')\n",
-    "    print(\"assert(enigma.wheel_positions == {})\".format(enigma.wheel_positions))\n",
-    "    print(\"assert(cat(enigma.wheel_positions_l) == '{}')\".format(cat(enigma.wheel_positions_l)))\n",
-    "    print(\"assert(enigma.peg_positions == {})\".format(enigma.peg_positions))\n",
-    "    print(\"assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == '{}')\".format(cat(enigma.lookup(l) for l in string.ascii_lowercase)))\n",
+    "    w_enigma.advance()\n",
+    "    print('w_enigma.advance()')\n",
+    "    print(\"assert(w_enigma.wheel_positions == {})\".format(w_enigma.wheel_positions))\n",
+    "    print(\"assert(cat(w_enigma.wheel_positions_l) == '{}')\".format(cat(w_enigma.wheel_positions_l)))\n",
+    "    print(\"assert(w_enigma.notch_positions == {})\".format(w_enigma.notch_positions))\n",
+    "    print(\"assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == '{}')\".format(cat(w_enigma.lookup(l) for l in string.ascii_lowercase)))\n",
+    "    print()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 215,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "self.assertEqual(self.enigma31.wheel_positions, (21, 5, 22))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ayt')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([10], [25], [24]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'izhrgtecaslkywvqpdjfxonumb')\n",
+      "\n",
+      "self.enigma31.advance()\n",
+      "self.assertEqual(self.enigma31.wheel_positions, (21, 5, 23))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ayu')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([10], [25], [25]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'dtoavihgflwjnmcsrqpbzekyxu')\n",
+      "\n",
+      "self.enigma31.advance()\n",
+      "self.assertEqual(self.enigma31.wheel_positions, (21, 5, 24))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ayv')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([10], [25], [0]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'xquhtpsdwkjonmlfbvgecriazy')\n",
+      "\n",
+      "self.enigma31.advance()\n",
+      "self.assertEqual(self.enigma31.wheel_positions, (21, 6, 25))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'azw')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([10], [0], [1]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'dofapcluvmngjkbezyxwhitsrq')\n",
+      "\n",
+      "self.enigma31.advance()\n",
+      "self.assertEqual(self.enigma31.wheel_positions, (22, 7, 0))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bax')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([11], [1], [2]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'jdlbmswztapcexrkuofiqygnvh')\n",
+      "\n",
+      "self.enigma31.advance()\n",
+      "self.assertEqual(self.enigma31.wheel_positions, (22, 7, 1))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bay')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([11], [1], [3]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'iydcnuhgawsoxelztvkqfrjmbp')\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "w_enigma.set_wheels('a', 'y', 't')\n",
+    "\n",
+    "print(\"self.assertEqual(self.enigma31.wheel_positions, {})\".format(w_enigma.wheel_positions))\n",
+    "print(\"self.assertEqual(cat(self.enigma31.wheel_positions_l), '{}')\".format(cat(w_enigma.wheel_positions_l)))\n",
+    "print(\"self.assertEqual(self.enigma31.notch_positions, {})\".format(w_enigma.notch_positions))\n",
+    "print(\"assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == '{}')\".format(cat(w_enigma.lookup(l) for l in string.ascii_lowercase)))\n",
+    "print()\n",
+    "\n",
+    "for i in range(5):\n",
+    "    w_enigma.advance()\n",
+    "    print('self.enigma31.advance()')\n",
+    "    print(\"self.assertEqual(self.enigma31.wheel_positions, {})\".format(w_enigma.wheel_positions))\n",
+    "    print(\"self.assertEqual(cat(self.enigma31.wheel_positions_l), '{}')\".format(cat(w_enigma.wheel_positions_l)))\n",
+    "    print(\"self.assertEqual(self.enigma31.notch_positions, {})\".format(w_enigma.notch_positions))\n",
+    "    print(\"assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == '{}')\".format(cat(w_enigma.lookup(l) for l in string.ascii_lowercase)))\n",
     "    print()"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 216,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "self.assertEqual(self.enigma31.wheel_positions, (21, 6, 22))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'azt')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([10], [0], [24]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'idjbptqwacsvnmregokfzlhyxu')\n",
+      "\n",
+      "self.enigma31.advance()\n",
+      "self.assertEqual(self.enigma31.wheel_positions, (22, 7, 23))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bau')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([11], [1], [25]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'rniszouwcxtvqbfymadkglhjpe')\n",
+      "\n",
+      "self.enigma31.advance()\n",
+      "self.assertEqual(self.enigma31.wheel_positions, (22, 7, 24))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bav')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([11], [1], [0]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'qijfsdmkbchugxtwazeolypnvr')\n",
+      "\n",
+      "self.enigma31.advance()\n",
+      "self.assertEqual(self.enigma31.wheel_positions, (22, 8, 25))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bbw')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([11], [2], [1]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'xprtlozyskjewqfbncidvumahg')\n",
+      "\n",
+      "self.enigma31.advance()\n",
+      "self.assertEqual(self.enigma31.wheel_positions, (22, 8, 0))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bbx')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([11], [2], [2]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'vtfuyczoqxmpkwhlisrbdanjeg')\n",
+      "\n",
+      "self.enigma31.advance()\n",
+      "self.assertEqual(self.enigma31.wheel_positions, (22, 8, 1))\n",
+      "self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bby')\n",
+      "self.assertEqual(self.enigma31.notch_positions, ([11], [2], [3]))\n",
+      "assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'tjrhzpqdobwxyuifgcvansklme')\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "w_enigma.set_wheels('a', 'z', 't')\n",
+    "\n",
+    "print(\"self.assertEqual(self.enigma31.wheel_positions, {})\".format(w_enigma.wheel_positions))\n",
+    "print(\"self.assertEqual(cat(self.enigma31.wheel_positions_l), '{}')\".format(cat(w_enigma.wheel_positions_l)))\n",
+    "print(\"self.assertEqual(self.enigma31.notch_positions, {})\".format(w_enigma.notch_positions))\n",
+    "print(\"assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == '{}')\".format(cat(w_enigma.lookup(l) for l in string.ascii_lowercase)))\n",
+    "print()\n",
+    "\n",
+    "for i in range(5):\n",
+    "    w_enigma.advance()\n",
+    "    print('self.enigma31.advance()')\n",
+    "    print(\"self.assertEqual(self.enigma31.wheel_positions, {})\".format(w_enigma.wheel_positions))\n",
+    "    print(\"self.assertEqual(cat(self.enigma31.wheel_positions_l), '{}')\".format(cat(w_enigma.wheel_positions_l)))\n",
+    "    print(\"self.assertEqual(self.enigma31.notch_positions, {})\".format(w_enigma.notch_positions))\n",
+    "    print(\"assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == '{}')\".format(cat(w_enigma.lookup(l) for l in string.ascii_lowercase)))\n",
+    "    print()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 221,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "w_enigma.set_wheels('i', 'z', 'd')\n",
+    "ct = w_enigma.encipher('verylongtestmessagewithanextrabitofmessageforgoodmeasure')\n",
+    "assert(ct == 'apocwtjuikurcfivlozvhffkoacxufcekthcvodfqpxdjqyckdozlqki')\n",
+    "assert(w_enigma.wheel_positions == (4, 9, 10))\n",
+    "assert(cat(w_enigma.wheel_positions_l) == 'jch')\n",
+    "assert(w_enigma.notch_positions == ([19], [3], [12]))\n",
+    "assert(cat(w_enigma.lookup(l) for l in string.ascii_lowercase) == 'mopnigfuesqwadbcktjrhylzvx')\n",
+    "\n",
+    "w_enigma.set_wheels('i', 'z', 'd')\n",
+    "pt = w_enigma.decipher('apocwtjuikurcfivlozvhffkoacxufcekthcvodfqpxdjqyckdozlqki')\n",
+    "assert(pt == 'verylongtestmessagewithanextrabitofmessageforgoodmeasure')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 218,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "([19], [3], [12])"
+      ]
+     },
+     "execution_count": 218,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "w_enigma.notch_positions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 194,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "# Reflector B\n",
+    "# Rotors III, I, II with rings 17, 11, 19\n",
+    "# Plugboard pairs GU FZ BD LK TC PS HV WN JE AM\n",
+    "\n",
+    "tbt_enigma = Enigma(reflector_b_spec, \n",
+    "                wheel_iii_spec, wheel_iii_notches,\n",
+    "                wheel_i_spec, wheel_i_notches,\n",
+    "                wheel_ii_spec, wheel_ii_notches,\n",
+    "                17, 11, 19,\n",
+    "                'GU FZ BD LK TC PS HV WN JE AM'.lower())"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 195,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'jbvbwwzfslhxnhzzccsngebmrnswgjonwbjnzcfgadeuoyameylmpvny'"
+      ]
+     },
+     "execution_count": 195,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'q', 'v')\n",
+    "ct = tbt_enigma.encipher('very long test message with an extra bit of message for good measure')\n",
+    "ct"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 196,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'SLGNC SZXLT KZEBG HSTGY WDMPR'"
+      ]
+     },
+     "execution_count": 196,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "target_ct = 'SLGNC SZXLT KZEBG HSTGY WDMPR'\n",
+    "target_ct"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 197,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'Theyw erede tecte d byBri tishs hipsi nclud'"
+      ]
+     },
+     "execution_count": 197,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "target_pt = 'Theyw erede tecte d byBri tishs hipsi nclud'\n",
+    "target_pt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 198,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(29, 43)"
+      ]
+     },
+     "execution_count": 198,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "len(target_ct), len(target_pt)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 199,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "SLGNC SZXLT KZEBG HSTGY WDMPR\n",
+      "Theyw erede tecte d byBri tishs hipsi nclud\n"
+     ]
+    }
+   ],
+   "source": [
+    "print('{}\\n{}'.format(target_ct, target_pt))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 200,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Theyw erede tecte d byBri tishs hipsi nclud\n",
+      "SLGNC SZXLT KZEBG HSTGY WDMPR\n",
+      "slgncszxltkzebghstgywdmprucuzqdqzpve\n",
+      "theyweredetectedbybritish\n"
+     ]
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "this_pt = tbt_enigma.encipher(target_ct)\n",
+    "\n",
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "this_ct = tbt_enigma.encipher(target_pt)\n",
+    "\n",
+    "\n",
+    "print('{}\\n{}\\n{}\\n{}'.format(target_pt, target_ct, this_ct, this_pt))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 201,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "import itertools"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 202,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "def str_ham(s1, s2):\n",
+    "    \"\"\"Hamming distance for strings\"\"\"\n",
+    "    return sum(1 for c1, c2 in zip(s1, s2) if c1 != c2)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 203,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "0"
+      ]
+     },
+     "execution_count": 203,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "str_ham('hello', 'hello')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "A brute-force check of all message settings, looking for the one that generates the target text."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 204,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "best ('a', 'a', 'a') 29\n",
+      "best ('a', 'a', 'a') 29\n",
+      "best ('a', 'a', 'a') 29\n",
+      "best ('a', 'a', 'a') 29\n",
+      "1 loop, best of 3: 20.6 s per loop\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit\n",
+    "best = ('a', 'a', 'a')\n",
+    "best_hd = 10000\n",
+    "for w1, w2, w3 in itertools.product(string.ascii_lowercase, repeat=3):\n",
+    "    tbt_enigma.set_wheels(w1, w2, w3)\n",
+    "    this_ct = tbt_enigma.encipher(target_pt)\n",
+    "    if this_ct == target_ct:\n",
+    "        print(w1, w2, w3)\n",
+    "    if str_ham(this_ct, target_ct) < best_hd:\n",
+    "        best = (w1, w2, w3)\n",
+    "        best_hd = str_ham(this_ct, target_ct)\n",
+    "print('best', best, best_hd)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 205,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Wheels now ['a', 'a', 'b'] enciphering t -> s\n",
+      "Wheels now ['a', 'a', 'c'] enciphering h -> l\n",
+      "Wheels now ['a', 'a', 'd'] enciphering e -> g\n",
+      "Wheels now ['a', 'a', 'e'] enciphering y -> n\n",
+      "Wheels now ['a', 'b', 'f'] enciphering w -> c\n",
+      "Wheels now ['a', 'b', 'g'] enciphering e -> s\n",
+      "Wheels now ['a', 'b', 'h'] enciphering r -> z\n",
+      "Wheels now ['a', 'b', 'i'] enciphering e -> x\n",
+      "Wheels now ['a', 'b', 'j'] enciphering d -> l\n",
+      "Wheels now ['a', 'b', 'k'] enciphering e -> t\n",
+      "Wheels now ['a', 'b', 'l'] enciphering t -> k\n",
+      "Wheels now ['a', 'b', 'm'] enciphering e -> z\n",
+      "Wheels now ['a', 'b', 'n'] enciphering c -> e\n",
+      "Wheels now ['a', 'b', 'o'] enciphering t -> b\n",
+      "Wheels now ['a', 'b', 'p'] enciphering e -> g\n",
+      "Wheels now ['a', 'b', 'q'] enciphering d -> h\n",
+      "Wheels now ['a', 'b', 'r'] enciphering b -> s\n",
+      "Wheels now ['a', 'b', 's'] enciphering y -> t\n",
+      "Wheels now ['a', 'b', 't'] enciphering b -> g\n",
+      "Wheels now ['a', 'b', 'u'] enciphering r -> y\n",
+      "Wheels now ['a', 'b', 'v'] enciphering i -> w\n",
+      "Wheels now ['a', 'b', 'w'] enciphering t -> d\n",
+      "Wheels now ['a', 'b', 'x'] enciphering i -> m\n",
+      "Wheels now ['a', 'b', 'y'] enciphering s -> p\n",
+      "Wheels now ['a', 'b', 'z'] enciphering h -> r\n",
+      "Wheels now ['a', 'b', 'a'] enciphering s -> u\n",
+      "Wheels now ['a', 'b', 'b'] enciphering h -> c\n",
+      "Wheels now ['a', 'b', 'c'] enciphering i -> u\n",
+      "Wheels now ['a', 'b', 'd'] enciphering p -> z\n",
+      "Wheels now ['a', 'b', 'e'] enciphering s -> q\n",
+      "Wheels now ['a', 'c', 'f'] enciphering i -> d\n",
+      "Wheels now ['a', 'c', 'g'] enciphering n -> q\n",
+      "Wheels now ['a', 'c', 'h'] enciphering c -> z\n",
+      "Wheels now ['a', 'c', 'i'] enciphering l -> p\n",
+      "Wheels now ['a', 'c', 'j'] enciphering u -> v\n",
+      "Wheels now ['a', 'c', 'k'] enciphering d -> e\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "('slgncszxltkzebghstgywdmprucuzqdqzpve', 'SLGNC SZXLT KZEBG HSTGY WDMPR')"
+      ]
+     },
+     "execution_count": 205,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "tbt_enigma.encipher(target_pt, debug=True), target_ct"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 206,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Wheels now ['a', 'a', 'b'] enciphering s -> t\n",
+      "Wheels now ['a', 'a', 'c'] enciphering l -> h\n",
+      "Wheels now ['a', 'a', 'd'] enciphering g -> e\n",
+      "Wheels now ['a', 'a', 'e'] enciphering n -> y\n",
+      "Wheels now ['a', 'b', 'f'] enciphering c -> w\n",
+      "Wheels now ['a', 'b', 'g'] enciphering s -> e\n",
+      "Wheels now ['a', 'b', 'h'] enciphering z -> r\n",
+      "Wheels now ['a', 'b', 'i'] enciphering x -> e\n",
+      "Wheels now ['a', 'b', 'j'] enciphering l -> d\n",
+      "Wheels now ['a', 'b', 'k'] enciphering t -> e\n",
+      "Wheels now ['a', 'b', 'l'] enciphering k -> t\n",
+      "Wheels now ['a', 'b', 'm'] enciphering z -> e\n",
+      "Wheels now ['a', 'b', 'n'] enciphering e -> c\n",
+      "Wheels now ['a', 'b', 'o'] enciphering b -> t\n",
+      "Wheels now ['a', 'b', 'p'] enciphering g -> e\n",
+      "Wheels now ['a', 'b', 'q'] enciphering h -> d\n",
+      "Wheels now ['a', 'b', 'r'] enciphering s -> b\n",
+      "Wheels now ['a', 'b', 's'] enciphering t -> y\n",
+      "Wheels now ['a', 'b', 't'] enciphering g -> b\n",
+      "Wheels now ['a', 'b', 'u'] enciphering y -> r\n",
+      "Wheels now ['a', 'b', 'v'] enciphering w -> i\n",
+      "Wheels now ['a', 'b', 'w'] enciphering d -> t\n",
+      "Wheels now ['a', 'b', 'x'] enciphering m -> i\n",
+      "Wheels now ['a', 'b', 'y'] enciphering p -> s\n",
+      "Wheels now ['a', 'b', 'z'] enciphering r -> h\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "('theyweredetectedbybritish', 'Theyw erede tecte d byBri tishs hipsi nclud')"
+      ]
+     },
+     "execution_count": 206,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "tbt_enigma.encipher(target_ct, debug=True), target_pt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 207,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('theyweredetectedbybritish', 'Theyw erede tecte d byBri tishs hipsi nclud')"
+      ]
+     },
+     "execution_count": 207,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "tbt_enigma.encipher(target_ct), target_pt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 208,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'mdtbjzuvielkawosqrpcghnxyf'"
+      ]
+     },
+     "execution_count": 208,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(tbt_enigma.plugboard.forward(l) for l in string.ascii_lowercase)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 209,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "10"
+      ]
+     },
+     "execution_count": 209,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "tbt_enigma.set_wheels('a', 'a', 'a')\n",
+    "tbt_enigma.left_wheel.position"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.4.3+"
+   "version": "3.5.3"
   }
  },
  "nbformat": 4,
- "nbformat_minor": 0
+ "nbformat_minor": 1
 }
index a26d1d3041dc6509049624870e2e5afd123b9923..89758f532087985078041473006ce3d6510e3933 100644 (file)
--- a/enigma.py
+++ b/enigma.py
@@ -60,14 +60,14 @@ wheel_viii_spec = 'fkqhtlxocbjspdzramewniuygv'
 beta_wheel_spec = 'leyjvcnixwpbqmdrtakzgfuhos'
 gamma_wheel_spec = 'fsokanuerhmbtiycwlqpzxvgjd'
 
-wheel_i_pegs = ['q']
-wheel_ii_pegs = ['e']
-wheel_iii_pegs = ['v']
-wheel_iv_pegs = ['j']
-wheel_v_pegs = ['z']
-wheel_vi_pegs = ['z', 'm']
-wheel_vii_pegs = ['z', 'm']
-wheel_viii_pegs = ['z', 'm']
+wheel_i_notches = ['q']
+wheel_ii_notches = ['e']
+wheel_iii_notches = ['v']
+wheel_iv_notches = ['j']
+wheel_v_notches = ['z']
+wheel_vi_notches = ['z', 'm']
+wheel_vii_notches = ['z', 'm']
+wheel_viii_notches = ['z', 'm']
 
 reflector_b_spec = 'ay br cu dh eq fs gl ip jx kn mo tz vw'
 reflector_c_spec = 'af bv cp dj ei go hy kr lz mx nw tq su'
@@ -223,7 +223,7 @@ class SimpleWheel(LetterTransformer):
 class Wheel(SimpleWheel):
     """A wheel with a movable ring.
 
-    The ring holds the letters and the pegs that turn other wheels. The core
+    The ring holds the letters and the notches that turn other wheels. The core
     holds the wiring that does the transformation.
 
     The ring position is how many steps the core is turned relative to the ring.
@@ -236,12 +236,12 @@ class Wheel(SimpleWheel):
     The position_l is the position of the ring, or what would be observed
     by the user of the Enigma machine. 
 
-    The peg_positions are the number of advances of this wheel before it will 
+    The notch_positions are the number of advances of this wheel before it will 
     advance the next wheel.
 
     """
-    def __init__(self, transform, ring_peg_letters, ring_setting=1, position='a', raw_transform=False):
-        self.ring_peg_letters = ring_peg_letters
+    def __init__(self, transform, ring_notch_letters, ring_setting=1, position='a', raw_transform=False):
+        self.ring_notch_letters = ring_notch_letters
         self.ring_setting = ring_setting
         super(Wheel, self).__init__(transform, position=position, raw_transform=raw_transform)
         self.set_position(position)
@@ -257,12 +257,14 @@ class Wheel(SimpleWheel):
             self.position = (pos(position) - self.ring_setting + 1) % 26
         else:
             self.position = (position - self.ring_setting) % 26
-        # self.peg_positions = [(pos(p) - pos(position)) % 26  for p in self.ring_peg_letters]
-        self.peg_positions = [(pos(p) - (self.position + self.ring_setting - 1)) % 26  for p in self.ring_peg_letters]
+        # # self.notch_positions = [(pos(p) - pos(position)) % 26  for p in self.ring_notch_letters]
+        # self.notch_positions = [(pos(p) - (self.position + self.ring_setting - 1)) % 26  for p in self.ring_notch_letters]
+        self.notch_positions = [(self.position + self.ring_setting - 1 - pos(p)) % 26  for p in self.ring_notch_letters]
         
     def advance(self):
         super(Wheel, self).advance()
-        self.peg_positions = [(p - 1) % 26 for p in self.peg_positions]
+        self.notch_positions = [(p + 1) % 26 for p in self.notch_positions]
+        return self.position
 
 
 class Enigma(object):
@@ -271,15 +273,15 @@ class Enigma(object):
 
     """
     def __init__(self, reflector_spec,
-                 left_wheel_spec, left_wheel_pegs,
-                 middle_wheel_spec, middle_wheel_pegs,
-                 right_wheel_spec, right_wheel_pegs,
+                 left_wheel_spec, left_wheel_notches,
+                 middle_wheel_spec, middle_wheel_notches,
+                 right_wheel_spec, right_wheel_notches,
                  left_ring_setting, middle_ring_setting, right_ring_setting,
                  plugboard_setting):
         self.reflector = Reflector(reflector_spec)
-        self.left_wheel = Wheel(left_wheel_spec, left_wheel_pegs, ring_setting=left_ring_setting)
-        self.middle_wheel = Wheel(middle_wheel_spec, middle_wheel_pegs, ring_setting=middle_ring_setting)
-        self.right_wheel = Wheel(right_wheel_spec, right_wheel_pegs, ring_setting=right_ring_setting)
+        self.left_wheel = Wheel(left_wheel_spec, left_wheel_notches, ring_setting=left_ring_setting)
+        self.middle_wheel = Wheel(middle_wheel_spec, middle_wheel_notches, ring_setting=middle_ring_setting)
+        self.right_wheel = Wheel(right_wheel_spec, right_wheel_notches, ring_setting=right_ring_setting)
         self.plugboard = Plugboard(plugboard_setting)
         
     def __getattribute__(self,name):
@@ -287,8 +289,8 @@ class Enigma(object):
             return self.left_wheel.position, self.middle_wheel.position, self.right_wheel.position 
         elif name=='wheel_positions_l':
             return self.left_wheel.position_l, self.middle_wheel.position_l, self.right_wheel.position_l 
-        elif name=='peg_positions':
-            return self.left_wheel.peg_positions, self.middle_wheel.peg_positions, self.right_wheel.peg_positions
+        elif name=='notch_positions':
+            return self.left_wheel.notch_positions, self.middle_wheel.notch_positions, self.right_wheel.notch_positions
         else:
             return object.__getattribute__(self, name)
 
@@ -312,9 +314,9 @@ class Enigma(object):
     def advance(self):
         advance_middle = False
         advance_left = False
-        if 0 in self.right_wheel.peg_positions:
+        if 0 in self.right_wheel.notch_positions:
             advance_middle = True
-        if 0 in self.middle_wheel.peg_positions:
+        if 0 in self.middle_wheel.notch_positions:
             advance_left = True
             advance_middle = True
         self.right_wheel.advance()
@@ -339,7 +341,7 @@ class Enigma(object):
 #     print('enigma.advance()')
 #     print("assert(enigma.wheel_positions == {})".format(enigma.wheel_positions))
 #     print("assert(cat(enigma.wheel_positions_l) == '{}')".format(cat(enigma.wheel_positions_l)))
-#     print("assert(enigma.peg_positions == {})".format(enigma.peg_positions))
+#     print("assert(enigma.notch_positions == {})".format(enigma.notch_positions))
 #     print("assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == '{}')".format(cat(enigma.lookup(l) for l in string.ascii_lowercase)))
 #     print()
 
index b30be760a2aadff3bd5df88da25c616bfdb8768c..5ff8d9f1090b63e24aee7fb0cec48a6a234b3994 100644 (file)
@@ -157,23 +157,23 @@ class SimpleWheelTest(unittest.TestCase):
 
 class WheelTest(unittest.TestCase):
     def test_init1(self):
-        wheel = Wheel(wheel_iii_spec, wheel_iii_pegs, position='b', 
+        wheel = Wheel(wheel_iii_spec, wheel_iii_notches, position='b', 
             ring_setting=1)
         self.assertEqual(wheel.position, 1)
-        self.assertEqual(wheel.peg_positions, [20])
+        self.assertEqual(wheel.notch_positions, [6])
         self.assertEqual(wheel.position_l, 'b')
 
         wheel.advance()
         self.assertEqual(wheel.position, 2)
-        self.assertEqual(wheel.peg_positions, [19])
+        self.assertEqual(wheel.notch_positions, [7])
         self.assertEqual(wheel.position_l, 'c')
 
     def test_init2(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', 
+        wheel = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', 
             ring_setting=3)
         self.assertEqual(wheel.position, 25)
-        self.assertIn(11, wheel.peg_positions)
-        self.assertIn(24, wheel.peg_positions)
+        self.assertIn(2, wheel.notch_positions)
+        self.assertIn(15, wheel.notch_positions)
         self.assertEqual(wheel.position_l, 'b')
         self.assertEqual(cat(wheel.forward(l) 
                 for l in string.ascii_lowercase),
@@ -183,13 +183,13 @@ class WheelTest(unittest.TestCase):
             'ptlyrmidoxbswhnfckquzgeavj')
 
     def test_advance(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', 
+        wheel = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', 
             ring_setting=3)
         wheel.advance()
 
         self.assertEqual(wheel.position, 0)
-        self.assertIn(10, wheel.peg_positions)
-        self.assertIn(23, wheel.peg_positions)
+        self.assertIn(3, wheel.notch_positions)
+        self.assertIn(16, wheel.notch_positions)
         self.assertEqual(wheel.position_l, 'c')
         self.assertEqual(cat(wheel.forward(l) 
                 for l in string.ascii_lowercase),
@@ -199,14 +199,14 @@ class WheelTest(unittest.TestCase):
             'skxqlhcnwarvgmebjptyfdzuio')
 
     def test_advance_23(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', 
+        wheel = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', 
             ring_setting=3)
         for _ in range(23):
             wheel.advance()
 
         self.assertEqual(wheel.position, 22)
-        self.assertIn(1, wheel.peg_positions)
-        self.assertIn(14, wheel.peg_positions)
+        self.assertIn(12, wheel.notch_positions)
+        self.assertIn(25, wheel.notch_positions)
         self.assertEqual(wheel.position_l, 'y')
         self.assertEqual(cat(wheel.forward(l) 
                 for l in string.ascii_lowercase),
@@ -216,14 +216,14 @@ class WheelTest(unittest.TestCase):
             'dymswobuplgraevzkqifntxcjh')
 
     def test_advance_24(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', 
+        wheel = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', 
             ring_setting=3)
         for _ in range(24):
             wheel.advance()
 
         self.assertEqual(wheel.position, 23)
-        self.assertIn(0, wheel.peg_positions)
-        self.assertIn(13, wheel.peg_positions)
+        self.assertIn(0, wheel.notch_positions)
+        self.assertIn(13, wheel.notch_positions)
         self.assertEqual(wheel.position_l, 'z')
         self.assertEqual(cat(wheel.forward(l) 
                 for l in string.ascii_lowercase),
@@ -233,14 +233,14 @@ class WheelTest(unittest.TestCase):
             'xlrvnatokfqzduyjphemswbigc')
 
     def test_advance_25(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', 
+        wheel = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', 
             ring_setting=3)
         for _ in range(25):
             wheel.advance()
 
         self.assertEqual(wheel.position, 24)
-        self.assertIn(25, wheel.peg_positions)
-        self.assertIn(12, wheel.peg_positions)
+        self.assertIn(1, wheel.notch_positions)
+        self.assertIn(14, wheel.notch_positions)
         self.assertEqual(wheel.position_l, 'a')
         self.assertEqual(cat(wheel.forward(l) 
                 for l in string.ascii_lowercase),
@@ -250,14 +250,14 @@ class WheelTest(unittest.TestCase):
             'kqumzsnjepyctxiogdlrvahfbw')
 
     def test_advance_26(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', 
+        wheel = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', 
             ring_setting=3)
         for _ in range(26):
             wheel.advance()
 
         self.assertEqual(wheel.position, 25)
-        self.assertIn(24, wheel.peg_positions)
-        self.assertIn(11, wheel.peg_positions)
+        self.assertIn(2, wheel.notch_positions)
+        self.assertIn(15, wheel.notch_positions)
         self.assertEqual(wheel.position_l, 'b')
         self.assertEqual(cat(wheel.forward(l) 
                 for l in string.ascii_lowercase),
@@ -267,14 +267,14 @@ class WheelTest(unittest.TestCase):
             'ptlyrmidoxbswhnfckquzgeavj')
 
     def test_advance_27(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', 
+        wheel = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', 
             ring_setting=3)
         for _ in range(27):
             wheel.advance()
 
         self.assertEqual(wheel.position, 0)
-        self.assertIn(23, wheel.peg_positions)
-        self.assertIn(10, wheel.peg_positions)
+        self.assertIn(3, wheel.notch_positions)
+        self.assertIn(16, wheel.notch_positions)
         self.assertEqual(wheel.position_l, 'c')
         self.assertEqual(cat(wheel.forward(l) 
                 for l in string.ascii_lowercase),
@@ -284,60 +284,60 @@ class WheelTest(unittest.TestCase):
             'skxqlhcnwarvgmebjptyfdzuio')
 
     def test_set_position(self):
-        wheel_3 = Wheel(wheel_iii_spec, wheel_iii_pegs, ring_setting=3)
+        wheel_3 = Wheel(wheel_iii_spec, wheel_iii_notches, ring_setting=3)
         wheel_3.set_position('a')
         self.assertEqual(wheel_3.position, 24)
         self.assertEqual(wheel_3.position_l, 'a')
-        self.assertEqual(wheel_3.peg_positions, [21])
+        self.assertEqual(wheel_3.notch_positions, [5])
 
         wheel_3.set_position('z')
         self.assertEqual(wheel_3.position, 23)
         self.assertEqual(wheel_3.position_l, 'z')
-        self.assertEqual(wheel_3.peg_positions, [22])
+        self.assertEqual(wheel_3.notch_positions, [4])
 
         wheel_3.set_position(26)
         self.assertEqual(wheel_3.position, 23)
         self.assertEqual(wheel_3.position_l, 'z')
-        self.assertEqual(wheel_3.peg_positions, [22])
+        self.assertEqual(wheel_3.notch_positions, [4])
 
         wheel_3.set_position(27)
         self.assertEqual(wheel_3.position, 24)
         self.assertEqual(wheel_3.position_l, 'a')
-        self.assertEqual(wheel_3.peg_positions, [21])
+        self.assertEqual(wheel_3.notch_positions, [5])
 
         wheel_3.set_position('f')
         self.assertEqual(wheel_3.position, 3)
         self.assertEqual(wheel_3.position_l, 'f')
-        self.assertEqual(wheel_3.peg_positions, [16])
+        self.assertEqual(wheel_3.notch_positions, [10])
 
         wheel_3.set_position(6)
         self.assertEqual(wheel_3.position, 3)
         self.assertEqual(wheel_3.position_l, 'f')
-        self.assertEqual(wheel_3.peg_positions, [16])
+        self.assertEqual(wheel_3.notch_positions, [10])
 
         wheel_3.advance()
         self.assertEqual(wheel_3.position, 4)
         self.assertEqual(wheel_3.position_l, 'g')
-        self.assertEqual(wheel_3.peg_positions, [15])
+        self.assertEqual(wheel_3.notch_positions, [11])
 
         wheel_3.set_position(12)
         self.assertEqual(wheel_3.position, 9)
         self.assertEqual(wheel_3.position_l, 'l')
-        self.assertEqual(wheel_3.peg_positions, [10])
+        self.assertEqual(wheel_3.notch_positions, [16])
 
         wheel_3.advance()
         self.assertEqual(wheel_3.position, 10)
         self.assertEqual(wheel_3.position_l, 'm')
-        self.assertEqual(wheel_3.peg_positions, [9])
+        self.assertEqual(wheel_3.notch_positions, [17])
 
 
 class EnigmaTest(unittest.TestCase):
 
     def setUp(self):
         self.enigma = Enigma(reflector_b_spec, 
-                wheel_i_spec, wheel_i_pegs, 
-                wheel_ii_spec, wheel_ii_pegs, 
-                wheel_iii_spec, wheel_iii_pegs, 
+                wheel_i_spec, wheel_i_notches, 
+                wheel_ii_spec, wheel_ii_notches, 
+                wheel_iii_spec, wheel_iii_notches, 
                 1, 1, 1, 
                 '')
 
@@ -345,17 +345,17 @@ class EnigmaTest(unittest.TestCase):
         # Enigma simulation settings are 
         # http://enigma.louisedade.co.uk/enigma.html?m3;b;b153;AFTX;AJEU;AU-BG-EY-FP-HL-IN-JZ-OS-QR-TX
         self.enigma31 = Enigma(reflector_b_spec, 
-                wheel_i_spec, wheel_i_pegs, 
-                wheel_v_spec, wheel_v_pegs, 
-                wheel_iii_spec, wheel_iii_pegs, 
+                wheel_i_spec, wheel_i_notches, 
+                wheel_v_spec, wheel_v_notches, 
+                wheel_iii_spec, wheel_iii_notches, 
                 6, 20, 24, 
                 'ua pf rq so ni ey bg hl tx zj')
 
         # Settings for Bletchley Park outreach department's Enigma
         self.enigma_bp = Enigma(reflector_b_spec, 
-                 wheel_i_spec, wheel_i_pegs,
-                 wheel_iii_spec, wheel_iii_pegs,
-                 wheel_ii_spec, wheel_ii_pegs,
+                 wheel_i_spec, wheel_i_notches,
+                 wheel_iii_spec, wheel_iii_notches,
+                 wheel_ii_spec, wheel_ii_notches,
                  1, 26, 26, 
                  'qm we ro tu zj ps dl fg')
 
@@ -364,42 +364,42 @@ class EnigmaTest(unittest.TestCase):
         self.enigma.set_wheels('a', 'a', 't')
         self.assertEqual(self.enigma.wheel_positions, (0, 0, 19))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'aat')
-        self.assertEqual(self.enigma.peg_positions, ([16], [4], [2]))
+        self.assertEqual(self.enigma.notch_positions, ([10], [22], [24]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase), 
             'puvioztjdhxmlyeawsrgbcqknf')
 
         self.enigma.advance()
         self.assertEqual(self.enigma.wheel_positions, (0, 0, 20))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'aau')
-        self.assertEqual(self.enigma.peg_positions, ([16], [4], [1]))
+        self.assertEqual(self.enigma.notch_positions, ([10], [22], [25]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
             'baigpldqcowfyzjehvtsxrkumn')
 
         self.enigma.advance()
         self.assertEqual(self.enigma.wheel_positions, (0, 0, 21))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'aav')
-        self.assertEqual(self.enigma.peg_positions, ([16], [4], [0]))
+        self.assertEqual(self.enigma.notch_positions, ([10], [22], [0]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
             'mnvfydiwgzsoablrxpkutchqej')
 
         self.enigma.advance()
         self.assertEqual(self.enigma.wheel_positions, (0, 1, 22))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'abw')
-        self.assertEqual(self.enigma.peg_positions, ([16], [3], [25]))
+        self.assertEqual(self.enigma.notch_positions, ([10], [23], [1]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
             'ulfopcykswhbzvderqixanjtgm')
 
         self.enigma.advance()
         self.assertEqual(self.enigma.wheel_positions, (0, 1, 23))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'abx')
-        self.assertEqual(self.enigma.peg_positions, ([16], [3], [24]))
+        self.assertEqual(self.enigma.notch_positions, ([10], [23], [2]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
             'qmwftdyovursbzhxaklejicpgn')
 
         self.enigma.advance()
         self.assertEqual(self.enigma.wheel_positions, (0, 1, 24))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'aby')
-        self.assertEqual(self.enigma.peg_positions, ([16], [3], [23]))
+        self.assertEqual(self.enigma.notch_positions, ([10], [23], [3]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
             'oljmzxrvucybdqasngpwihtfke')
 
@@ -408,42 +408,42 @@ class EnigmaTest(unittest.TestCase):
         self.enigma.set_wheels('a', 'd', 't')
         self.assertEqual(self.enigma.wheel_positions, (0, 3, 19))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'adt')
-        self.assertEqual(self.enigma.peg_positions, ([16], [1], [2]))
+        self.assertEqual(self.enigma.notch_positions, ([10], [25], [24]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
             'zcbpqxwsjiuonmldethrkygfva')
 
         self.enigma.advance()
         self.assertEqual(self.enigma.wheel_positions, (0, 3, 20))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'adu')
-        self.assertEqual(self.enigma.peg_positions, ([16], [1], [1]))
+        self.assertEqual(self.enigma.notch_positions, ([10], [25], [25]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
             'ehprawjbngotxikcsdqlzyfmvu')
 
         self.enigma.advance()
         self.assertEqual(self.enigma.wheel_positions, (0, 3, 21))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'adv')
-        self.assertEqual(self.enigma.peg_positions, ([16], [1], [0]))
+        self.assertEqual(self.enigma.notch_positions, ([10], [25], [0]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
             'eqzxarpihmnvjkwgbfuyslodtc')
 
         self.enigma.advance()
         self.assertEqual(self.enigma.wheel_positions, (0, 4, 22))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'aew')
-        self.assertEqual(self.enigma.peg_positions, ([16], [0], [25]))
+        self.assertEqual(self.enigma.notch_positions, ([10], [0], [1]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
             'qedcbtpluzmhkongavwfirsyxj')
 
         self.enigma.advance()
         self.assertEqual(self.enigma.wheel_positions, (1, 5, 23))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'bfx')
-        self.assertEqual(self.enigma.peg_positions, ([15], [25], [24]))
+        self.assertEqual(self.enigma.notch_positions, ([11], [1], [2]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
             'iwuedhsfazqxytvrkpgncoblmj')
 
         self.enigma.advance()
         self.assertEqual(self.enigma.wheel_positions, (1, 5, 24))
         self.assertEqual(cat(self.enigma.wheel_positions_l), 'bfy')
-        self.assertEqual(self.enigma.peg_positions, ([15], [25], [23]))
+        self.assertEqual(self.enigma.notch_positions, ([11], [1], [3]))
         self.assertEqual(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
             'baknstqzrmcxjdvygiefwoulph')
 
@@ -470,35 +470,35 @@ class EnigmaTest(unittest.TestCase):
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (4, 11, 24))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'jev')
-        self.assertEqual(self.enigma31.peg_positions, ([7], [21], [0]))
+        self.assertEqual(self.enigma31.notch_positions, ([19], [5], [0]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'mvqjlyowkdieasgzcunxrbhtfp')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (4, 12, 25))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'jfw')
-        self.assertEqual(self.enigma31.peg_positions, ([7], [20], [25]))
+        self.assertEqual(self.enigma31.notch_positions, ([19], [6], [1]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'sjolzuyvrbwdpxcmtiaqfhknge')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (4, 12, 0))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'jfx')
-        self.assertEqual(self.enigma31.peg_positions, ([7], [20], [24]))
+        self.assertEqual(self.enigma31.notch_positions, ([19], [6], [2]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'qrxedkoywufmlvgsabpzjnicht')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (4, 12, 1))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'jfy')
-        self.assertEqual(self.enigma31.peg_positions, ([7], [20], [23]))
+        self.assertEqual(self.enigma31.notch_positions, ([19], [6], [3]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'hpsukliagqefwvtbjxcodnmrzy')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (4, 12, 2))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'jfz')
-        self.assertEqual(self.enigma31.peg_positions, ([7], [20], [22]))
+        self.assertEqual(self.enigma31.notch_positions, ([19], [6], [4]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'zevnbpyqowrtxdifhkulscjmga')
 
@@ -509,238 +509,309 @@ class EnigmaTest(unittest.TestCase):
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 3))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ida')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [21]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [5]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'ikhpqrvcambzjondefwyxgsutl')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 4))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idb')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [20]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [6]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'cdabskhgzwfmlqvunyexpojtri')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 5))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idc')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [19]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [7]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'pcbwiqhgemyvjsuaftnroldzkx')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 6))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idd')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [18]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [8]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'xcbfvdnouptmlghjzwykierasq')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 7))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ide')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [17]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [9]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'xfvglbdynuseriwqpmkzjcoaht')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 8))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idf')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [16]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [10]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'tfpqlbouynsewjgcdxkahzmriv')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 9))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idg')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [15]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [11]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'cjaunvlwtbygzexrspqidfhokm')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 10))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idh')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [14]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [12]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'yltxkrqvowebzpingfucshjdam')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 11))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idi')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [13]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [13]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'myktluzrnxceaiqsohpdfwvjbg')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 12))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idj')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [12]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [14]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'pynjrmiugdqxfcvakewzhoslbt')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 13))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idk')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [11]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [15]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'mwvedyplnoxhaijgrqtszcbkfu')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 14))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idl')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [10]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [16]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'qcbrfeutvoxpnmjladzhgiykws')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 15))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idm')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [9]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [17]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'dnoahryetsmukbcvwfjilpqzgx')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 16))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idn')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [8]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [18]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'nidcfehgbqsovalyjzkxwmutpr')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 17))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ido')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [7]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [19]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'joifxdulcarhzpbntkwqgysevm')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 18))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idp')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [6]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [20]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'ptnlsxvozmwdjchayuebrgkfqi')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 19))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idq')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [5]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [21]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'slwopzqnmxybihdeguavrtcjkf')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 20))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idr')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [4]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [22]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'hcbedwlamzogixkytsrqvufnpj')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 21))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ids')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [3]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [23]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'odxbjwzrmelkisavuhnyqpfctg')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 22))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idt')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [2]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [24]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'udgbfeclrwnhxksvtioqapjmzy')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 23))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idu')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [1]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [25]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'nrdczqxmowvshaiufblypkjgte')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 10, 24))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'idv')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [22], [0]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [4], [0]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'hkifjdoacebqtzgulyvmpsxwrn')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 11, 25))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'iew')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [21], [25]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [5], [1]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'yptzuhofqvnmlkgbixwcejsrad')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 11, 0))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'iex')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [21], [24]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [5], [2]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'vkdcwhqfjibzsptngumoraeyxl')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (3, 11, 1))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'iey')
-        self.assertEqual(self.enigma31.peg_positions, ([8], [21], [23]))
+        self.assertEqual(self.enigma31.notch_positions, ([18], [5], [3]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'wenpbqrouxlkychdfgzvitajms')
 
     def test_double_advance_with_ring_settings_2(self):
         self.enigma31.set_wheels('a', 'y', 't')
+        # self.assertEqual(self.enigma31.wheel_positions, (21, 5, 22))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ayt')
+        # self.assertEqual(self.enigma31.notch_positions, ([16], [1], [2]))
+
+        # self.enigma31.advance()
+        # self.assertEqual(self.enigma31.wheel_positions, (21, 5, 23))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ayu')
+        # self.assertEqual(self.enigma31.notch_positions, ([16], [1], [1]))
+
+        # self.enigma31.advance()
+        # self.assertEqual(self.enigma31.wheel_positions, (21, 5, 24))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ayv')
+        # self.assertEqual(self.enigma31.notch_positions, ([16], [1], [0]))
+
+        # self.enigma31.advance()
+        # self.assertEqual(self.enigma31.wheel_positions, (21, 6, 25))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'azw')
+        # self.assertEqual(self.enigma31.notch_positions, ([16], [0], [25]))
+
+        # self.enigma31.advance()
+        # self.assertEqual(self.enigma31.wheel_positions, (22, 7, 0))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bax')
+        # self.assertEqual(self.enigma31.notch_positions, ([15], [25], [24]))
+
+        # self.enigma31.advance()
+        # self.assertEqual(self.enigma31.wheel_positions, (22, 7, 1))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bay')
+        # self.assertEqual(self.enigma31.notch_positions, ([15], [25], [23]))  
+
         self.assertEqual(self.enigma31.wheel_positions, (21, 5, 22))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ayt')
-        self.assertEqual(self.enigma31.peg_positions, ([16], [1], [2]))
+        self.assertEqual(self.enigma31.notch_positions, ([10], [25], [24]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'izhrgtecaslkywvqpdjfxonumb')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (21, 5, 23))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ayu')
-        self.assertEqual(self.enigma31.peg_positions, ([16], [1], [1]))
+        self.assertEqual(self.enigma31.notch_positions, ([10], [25], [25]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'dtoavihgflwjnmcsrqpbzekyxu')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (21, 5, 24))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'ayv')
-        self.assertEqual(self.enigma31.peg_positions, ([16], [1], [0]))
+        self.assertEqual(self.enigma31.notch_positions, ([10], [25], [0]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'xquhtpsdwkjonmlfbvgecriazy')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (21, 6, 25))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'azw')
-        self.assertEqual(self.enigma31.peg_positions, ([16], [0], [25]))
+        self.assertEqual(self.enigma31.notch_positions, ([10], [0], [1]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'dofapcluvmngjkbezyxwhitsrq')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (22, 7, 0))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bax')
-        self.assertEqual(self.enigma31.peg_positions, ([15], [25], [24]))
+        self.assertEqual(self.enigma31.notch_positions, ([11], [1], [2]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'jdlbmswztapcexrkuofiqygnvh')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (22, 7, 1))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bay')
-        self.assertEqual(self.enigma31.peg_positions, ([15], [25], [23]))  
+        self.assertEqual(self.enigma31.notch_positions, ([11], [1], [3]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'iydcnuhgawsoxelztvkqfrjmbp')
+
 
         self.enigma31.set_wheels('a', 'z', 't')
+        # self.assertEqual(self.enigma31.wheel_positions, (21, 6, 22))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'azt')
+        # self.assertEqual(self.enigma31.notch_positions, ([16], [0], [2]))
+
+        # self.enigma31.advance()
+        # self.assertEqual(self.enigma31.wheel_positions, (22, 7, 23))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bau')
+        # self.assertEqual(self.enigma31.notch_positions, ([15], [25], [1]))
+
+        # self.enigma31.advance()
+        # self.assertEqual(self.enigma31.wheel_positions, (22, 7, 24))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bav')
+        # self.assertEqual(self.enigma31.notch_positions, ([15], [25], [0]))
+
+        # self.enigma31.advance()
+        # self.assertEqual(self.enigma31.wheel_positions, (22, 8, 25))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bbw')
+        # self.assertEqual(self.enigma31.notch_positions, ([15], [24], [25]))
+
+        # self.enigma31.advance()
+        # self.assertEqual(self.enigma31.wheel_positions, (22, 8, 0))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bbx')
+        # self.assertEqual(self.enigma31.notch_positions, ([15], [24], [24]))
+
+        # self.enigma31.advance()
+        # self.assertEqual(self.enigma31.wheel_positions, (22, 8, 1))
+        # self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bby')
+        # self.assertEqual(self.enigma31.notch_positions, ([15], [24], [23]))
+
         self.assertEqual(self.enigma31.wheel_positions, (21, 6, 22))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'azt')
-        self.assertEqual(self.enigma31.peg_positions, ([16], [0], [2]))
+        self.assertEqual(self.enigma31.notch_positions, ([10], [0], [24]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'idjbptqwacsvnmregokfzlhyxu')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (22, 7, 23))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bau')
-        self.assertEqual(self.enigma31.peg_positions, ([15], [25], [1]))
+        self.assertEqual(self.enigma31.notch_positions, ([11], [1], [25]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'rniszouwcxtvqbfymadkglhjpe')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (22, 7, 24))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bav')
-        self.assertEqual(self.enigma31.peg_positions, ([15], [25], [0]))
+        self.assertEqual(self.enigma31.notch_positions, ([11], [1], [0]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'qijfsdmkbchugxtwazeolypnvr')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (22, 8, 25))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bbw')
-        self.assertEqual(self.enigma31.peg_positions, ([15], [24], [25]))
+        self.assertEqual(self.enigma31.notch_positions, ([11], [2], [1]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'xprtlozyskjewqfbncidvumahg')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (22, 8, 0))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bbx')
-        self.assertEqual(self.enigma31.peg_positions, ([15], [24], [24]))
+        self.assertEqual(self.enigma31.notch_positions, ([11], [2], [2]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'vtfuyczoqxmpkwhlisrbdanjeg')
 
         self.enigma31.advance()
         self.assertEqual(self.enigma31.wheel_positions, (22, 8, 1))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'bby')
-        self.assertEqual(self.enigma31.peg_positions, ([15], [24], [23]))
+        self.assertEqual(self.enigma31.notch_positions, ([11], [2], [3]))
+        assert(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase) == 'tjrhzpqdobwxyuifgcvansklme')
 
 
     def test_encipher_with_ring(self):
@@ -751,7 +822,7 @@ class EnigmaTest(unittest.TestCase):
             'apocwtjuikurcfivlozvhffkoacxufcekthcvodfqpxdjqyckdozlqki')
         self.assertEqual(self.enigma31.wheel_positions, (4, 9, 10))
         self.assertEqual(cat(self.enigma31.wheel_positions_l), 'jch')
-        self.assertEqual(self.enigma31.peg_positions, ([7], [23], [14]))
+        self.assertEqual(self.enigma31.notch_positions, ([19], [3], [12]))
         self.assertEqual(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
             'mopnigfuesqwadbcktjrhylzvx')