Updated engima machine, refactored, added tests
authorNeil Smith <neil.git@njae.me.uk>
Sun, 15 May 2016 22:45:52 +0000 (23:45 +0100)
committerNeil Smith <neil.git@njae.me.uk>
Wed, 4 Oct 2017 08:20:57 +0000 (09:20 +0100)
enigma.ipynb

index bfd33028cddac9c28065975b3ed4fecc1673db25..7c9906cbc618ee32b8dd1a5b035545263001dae8 100644 (file)
   },
   {
    "cell_type": "code",
-   "execution_count": 333,
+   "execution_count": 733,
    "metadata": {
     "collapsed": true
    },
    "outputs": [],
    "source": [
     "import string\n",
+    "import collections\n",
     "\n",
-    "cat = ''.join"
+    "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": 334,
+   "execution_count": 734,
    "metadata": {
     "collapsed": true
    },
     "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)'"
+    "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": 335,
+   "execution_count": 735,
    "metadata": {
     "collapsed": false
    },
@@ -75,7 +88,8 @@
     "        self.make_transform_map(transform)\n",
     "    \n",
     "    def parse_specification(self, specification):\n",
-    "        return 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",
     "    def make_transform_map(self, transform):\n",
     "        self.make_empty_transform()\n",
     "        for p in transform:\n",
-    "            self.forward_map[ord(p[0]) - ord('a')] = ord(p[1]) - ord('a')\n",
-    "            self.backward_map[ord(p[1]) - ord('a')] = ord(p[0]) - ord('a')\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 chr(\n",
-    "                (self.forward_map[(ord(letter) - ord('a')) % 26] + ord('a')))\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 chr(\n",
-    "                (self.backward_map[(ord(letter) - ord('a')) % 26] + ord('a')))\n",
+    "            return unpos(self.backward_map[pos(letter)])\n",
     "        else:\n",
     "            return ''"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 336,
+   "execution_count": 736,
    "metadata": {
     "collapsed": false
    },
        " ('y', 'z')]"
       ]
      },
-     "execution_count": 336,
+     "execution_count": 736,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 337,
+   "execution_count": 737,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'zyxwcabdefghijklmnopqrstuv'"
+      ]
+     },
+     "execution_count": 737,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cat(collections.OrderedDict.fromkeys('zyxwc' + string.ascii_lowercase))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 738,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "tmap2 = list(zip(string.ascii_lowercase, cat(collections.OrderedDict.fromkeys('zyxwc' + string.ascii_lowercase))))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 739,
    "metadata": {
     "collapsed": false,
     "scrolled": true
        "  24])"
       ]
      },
-     "execution_count": 337,
+     "execution_count": 739,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "lt = LetterTransformer(tmap)\n",
+    "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": 338,
+   "execution_count": 740,
+   "metadata": {
+    "collapsed": false,
+    "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": 740,
+     "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": 741,
    "metadata": {
     "collapsed": false
    },
     {
      "data": {
       "text/plain": [
-       "'bcdefghijklmnopqrstuvwxyza'"
+       "'zyxwcabdefghijklmnopqrstuv'"
       ]
      },
-     "execution_count": 338,
+     "execution_count": 741,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 339,
+   "execution_count": 742,
    "metadata": {
     "collapsed": false
    },
     {
      "data": {
       "text/plain": [
-       "'zabcdefghijklmnopqrstuvwxy'"
+       "'fgehijklmnopqrstuvwxyzdcba'"
       ]
      },
-     "execution_count": 339,
+     "execution_count": 742,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 340,
+   "execution_count": 743,
    "metadata": {
     "collapsed": true
    },
    "source": [
     "class Plugboard(LetterTransformer):\n",
     "    def parse_specification(self, specification):\n",
-    "        return [tuple(p) for p in specification.split()]\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",
   },
   {
    "cell_type": "code",
-   "execution_count": 341,
+   "execution_count": 744,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 342,
+   "execution_count": 745,
    "metadata": {
     "collapsed": false
    },
        "'zycdefghijklmnopqrstuvwxba'"
       ]
      },
-     "execution_count": 342,
+     "execution_count": 745,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 343,
+   "execution_count": 746,
    "metadata": {
     "collapsed": false
    },
        "'zycdefghijklmnopqrstuvwxba'"
       ]
      },
-     "execution_count": 343,
+     "execution_count": 746,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 344,
+   "execution_count": 747,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 345,
+   "execution_count": 748,
    "metadata": {
     "collapsed": false
    },
        "('zycdefghijklmnopqrstuvwxba', 'zycdefghijklmnopqrstuvwxba')"
       ]
      },
-     "execution_count": 345,
+     "execution_count": 748,
+     "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": 749,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('ugcdypblnzkhmisfrqoxavwtej', 'ugcdypblnzkhmisfrqoxavwtej')"
+      ]
+     },
+     "execution_count": 749,
      "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": 346,
+   "execution_count": 750,
    "metadata": {
     "collapsed": true
    },
    "outputs": [],
    "source": [
     "class Reflector(Plugboard):\n",
-    "    def parse_specification(self, specification):\n",
-    "        return [tuple(p) for p in specification[1:-1].split(') (')]\n",
-    "    \n",
     "    def validate_transform(self, transform):\n",
     "        if len(transform) != 13:\n",
     "            raise ValueError(\"Reflector specification has {} pairs, requires 13\".\n",
   },
   {
    "cell_type": "code",
-   "execution_count": 347,
+   "execution_count": 751,
    "metadata": {
     "collapsed": false
    },
        " ('v', 'w')]"
       ]
      },
-     "execution_count": 347,
+     "execution_count": 751,
      "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(p) for p in reflector_b_spec.lower()[1:-1].split(') (')]\n",
+    "reflector_b_l = [tuple(clean(p)) for p in reflector_b_spec.split()]\n",
     "reflector_b_l"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 348,
+   "execution_count": 752,
    "metadata": {
     "collapsed": false
    },
    "outputs": [],
    "source": [
-    "reflector_b = Reflector(reflector_b_spec, raw_transform=False)"
+    "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": 349,
+   "execution_count": 753,
    "metadata": {
     "collapsed": false
    },
        "'yruhqsldpxngokmiebfzcwvjat'"
       ]
      },
-     "execution_count": 349,
+     "execution_count": 753,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 350,
+   "execution_count": 754,
    "metadata": {
-    "collapsed": true
+    "collapsed": false
    },
    "outputs": [],
    "source": [
   },
   {
    "cell_type": "code",
-   "execution_count": 351,
+   "execution_count": 755,
    "metadata": {
     "collapsed": false
    },
        "'fvpjiaoyedrzxwgctkuqsbnmhl'"
       ]
      },
-     "execution_count": 351,
+     "execution_count": 755,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 352,
+   "execution_count": 756,
    "metadata": {
     "collapsed": true
    },
     "        super(SimpleWheel, self).__init__(transform, raw_transform)\n",
     "        self.set_position(position)\n",
     "        \n",
-    "    def parse_specification(self, specification):\n",
-    "        return list(zip(string.ascii_lowercase, specification))\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",
-    "        self.position_l = position\n",
     "    \n",
     "    def forward(self, letter):\n",
     "        if letter in string.ascii_lowercase:\n",
-    "            return chr(\n",
-    "                (self.forward_map[(ord(letter) - ord('a') + self.position) % 26] - \n",
-    "                    self.position) % 26 + \n",
-    "                ord('a'))\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 chr(\n",
-    "                (self.backward_map[(ord(letter) - ord('a') + self.position) % 26] - \n",
-    "                    self.position) % 26 + \n",
-    "                ord('a'))\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",
-    "        self.position_l = chr(self.position + ord('a'))\n",
     "        return self.position"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 353,
+   "execution_count": 757,
    "metadata": {
     "collapsed": false,
     "scrolled": true
        " ('z', 'j')]"
       ]
      },
-     "execution_count": 353,
+     "execution_count": 757,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 354,
+   "execution_count": 758,
    "metadata": {
     "collapsed": false
    },
    "outputs": [],
    "source": [
-    "wheel_1 = SimpleWheel(rotor_1_transform, raw_transform=True)"
+    "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": 355,
+   "execution_count": 759,
    "metadata": {
     "collapsed": false
    },
        "('ekmflgdqvzntowyhxuspaibrcj', 'uwygadfpvzbeckmthxslrinqoj')"
       ]
      },
-     "execution_count": 355,
+     "execution_count": 759,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 356,
+   "execution_count": 760,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'a'"
+      ]
+     },
+     "execution_count": 760,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel_1.position_l"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 761,
    "metadata": {
     "collapsed": false
    },
    "outputs": [],
    "source": [
-    "wheel_2 = SimpleWheel(wheel_ii_spec)"
+    "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": 357,
+   "execution_count": 762,
    "metadata": {
     "collapsed": false
    },
        "('ajdksiruxblhwtmcqgznpyfvoe', 'ajpczwrlfbdkotyuqgenhxmivs')"
       ]
      },
-     "execution_count": 357,
+     "execution_count": 762,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 358,
+   "execution_count": 763,
    "metadata": {
     "collapsed": false
    },
     {
      "data": {
       "text/plain": [
-       "'cegikboqswuymxdhvfzjltrpna'"
+       "('bdfhjlcprtxvznyeiwgakmusqo', 'tagbpcsdqeufvnzhyixjwlrkom')"
       ]
      },
-     "execution_count": 358,
+     "execution_count": 763,
      "metadata": {},
      "output_type": "execute_result"
     }
     "wheel_3 = SimpleWheel(wheel_iii_spec)\n",
     "wheel_3.set_position('a')\n",
     "wheel_3.advance()\n",
-    "cat(wheel_3.forward(l) for l in string.ascii_lowercase)"
+    "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": 524,
+   "execution_count": 764,
    "metadata": {
     "collapsed": false
    },
     "        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",
-    "        super(Wheel, self).set_position(position)\n",
-    "        self.peg_positions = [(ord(p) - ord(position)) % 26  for p in self.ring_peg_letters]\n",
-    "        self.position = (self.position - self.ring_setting + 1) % 26\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": 525,
+   "execution_count": 765,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 526,
+   "execution_count": 766,
    "metadata": {
     "collapsed": false
    },
        "(1, [20])"
       ]
      },
-     "execution_count": 526,
+     "execution_count": 766,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 527,
+   "execution_count": 767,
    "metadata": {
     "collapsed": false
    },
        "(25, [24, 11])"
       ]
      },
-     "execution_count": 527,
+     "execution_count": 767,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 528,
+   "execution_count": 768,
    "metadata": {
     "collapsed": false,
     "scrolled": true
   },
   {
    "cell_type": "code",
-   "execution_count": 529,
+   "execution_count": 769,
+   "metadata": {
+    "collapsed": false
+   },
+   "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": 770,
+   "metadata": {
+    "collapsed": false
+   },
+   "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": 771,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(0, 'c', [23, 10])"
+      ]
+     },
+     "execution_count": 771,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wheel_6.position, wheel_6.position_l, wheel_6.peg_positions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 772,
    "metadata": {
     "collapsed": true
    },
     "        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",
     "            advance_middle = True\n",
     "        if 0 in self.middle_wheel.peg_positions:\n",
     "            advance_left = True\n",
-    "        if 0 in self.middle_wheel.peg_positions and 25 in self.right_wheel.peg_positions:\n",
     "            advance_middle = True\n",
-    "            advance_left = 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(self, message):\n",
     "        enciphered = ''\n",
-    "        for letter in message:\n",
+    "        for letter in clean(message):\n",
     "            enciphered += self.encipher_letter(letter)\n",
     "        return enciphered"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 548,
+   "execution_count": 773,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 549,
+   "execution_count": 774,
    "metadata": {
     "collapsed": false
    },
        "'u'"
       ]
      },
-     "execution_count": 549,
+     "execution_count": 774,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 550,
+   "execution_count": 775,
    "metadata": {
     "collapsed": false
    },
        "'a'"
       ]
      },
-     "execution_count": 550,
+     "execution_count": 775,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 551,
+   "execution_count": 776,
    "metadata": {
-    "collapsed": false,
-    "scrolled": true
+    "collapsed": false
    },
    "outputs": [
     {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "a a a ; [16] [4] [21]\n",
-      "a a b ; [16] [4] [20]\n",
-      "a a c ; [16] [4] [19]\n",
-      "a a d ; [16] [4] [18]\n",
-      "a a e ; [16] [4] [17]\n",
-      "a a f ; [16] [4] [16]\n",
-      "a a g ; [16] [4] [15]\n",
-      "a a h ; [16] [4] [14]\n",
-      "a a i ; [16] [4] [13]\n",
-      "a a j ; [16] [4] [12]\n",
-      "a a k ; [16] [4] [11]\n",
-      "a a l ; [16] [4] [10]\n",
-      "a a m ; [16] [4] [9]\n",
-      "a a n ; [16] [4] [8]\n",
-      "a a o ; [16] [4] [7]\n",
-      "a a p ; [16] [4] [6]\n",
-      "a a q ; [16] [4] [5]\n",
-      "a a r ; [16] [4] [4]\n",
-      "a a s ; [16] [4] [3]\n",
-      "a a t ; [16] [4] [2]\n",
-      "a a u ; [16] [4] [1]\n",
-      "a a v ; [16] [4] [0]\n",
-      "a b w ; [16] [3] [25]\n",
-      "a b x ; [16] [3] [24]\n",
-      "a b y ; [16] [3] [23]\n",
-      "a b z ; [16] [3] [22]\n"
-     ]
+     "data": {
+      "text/plain": [
+       "'uejobtpzwcnsrkdgvmlfaqiyxh'"
+      ]
+     },
+     "execution_count": 776,
+     "metadata": {},
+     "output_type": "execute_result"
     }
    ],
    "source": [
-    "enigma.set_wheels('a', 'a', 'a')\n",
-    "for _ in range(26):\n",
-    "    print(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.advance()"
+    "cat(enigma.lookup(l) for l in string.ascii_lowercase)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 552,
+   "execution_count": 777,
    "metadata": {
     "collapsed": false,
     "scrolled": true
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "a d t ; [16] [1] [2]\n",
-      "a d u ; [16] [1] [1]\n",
-      "a d v ; [16] [1] [0]\n",
-      "a e w ; [16] [0] [25]\n",
-      "b f x ; [15] [25] [24]\n",
-      "b f y ; [15] [25] [23]\n",
-      "b f z ; [15] [25] [22]\n",
-      "b f a ; [15] [25] [21]\n",
-      "b f b ; [15] [25] [20]\n",
-      "b f c ; [15] [25] [19]\n",
-      "b f d ; [15] [25] [18]\n",
-      "b f e ; [15] [25] [17]\n",
-      "b f f ; [15] [25] [16]\n",
-      "b f g ; [15] [25] [15]\n",
-      "b f h ; [15] [25] [14]\n",
-      "b f i ; [15] [25] [13]\n",
-      "b f j ; [15] [25] [12]\n",
-      "b f k ; [15] [25] [11]\n",
-      "b f l ; [15] [25] [10]\n",
-      "b f m ; [15] [25] [9]\n",
-      "b f n ; [15] [25] [8]\n",
-      "b f o ; [15] [25] [7]\n",
-      "b f p ; [15] [25] [6]\n",
-      "b f q ; [15] [25] [5]\n",
-      "b f r ; [15] [25] [4]\n",
-      "b f s ; [15] [25] [3]\n"
+      "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', 'd', 't')\n",
-    "for _ in range(26):\n",
-    "    print(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.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": 553,
+   "execution_count": 778,
    "metadata": {
-    "collapsed": false
+    "collapsed": false,
+    "scrolled": true
    },
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "'i'"
-      ]
-     },
-     "execution_count": 553,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
+   "outputs": [],
    "source": [
-    "enigma.set_wheels('a', 'a', 'a')\n",
+    "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",
-    "enigma.lookup('h')"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 560,
-   "metadata": {
-    "collapsed": false
-   },
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "'ilbdartydc'"
-      ]
-     },
-     "execution_count": 560,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "enigma.set_wheels('a', 'a', 'a')\n",
-    "ct = enigma.encipher('hellothere')\n",
-    "assert(ct == 'ilbdartydc')\n",
-    "ct"
+    "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": 555,
+   "execution_count": 779,
    "metadata": {
-    "collapsed": false
+    "collapsed": false,
+    "scrolled": true
    },
    "outputs": [
     {
-     "data": {
-      "text/plain": [
-       "'hellothere'"
-      ]
-     },
-     "execution_count": 555,
-     "metadata": {},
-     "output_type": "execute_result"
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "(1, 5, 24) ('b', 'f', 'y') ([15], [25], [23]) baknstqzrmcxjdvygiefwoulph\n"
+     ]
     }
    ],
    "source": [
-    "enigma.set_wheels('a', 'a', 'a')\n",
-    "pt = enigma.encipher(ct)\n",
-    "pt"
+    "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": 556,
+   "execution_count": 780,
    "metadata": {
     "collapsed": false
    },
     {
      "data": {
       "text/plain": [
-       "'i'"
+       "'olpfhnvflyn'"
       ]
      },
-     "execution_count": 556,
+     "execution_count": 780,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
     "enigma.set_wheels('a', 'a', 'a')\n",
-    "enigma.advance()\n",
-    "enigma.lookup('h')"
+    "ct = enigma.encipher('testmessage')\n",
+    "assert(ct == 'olpfhnvflyn')\n",
+    "ct"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 557,
+   "execution_count": 781,
    "metadata": {
     "collapsed": false
    },
     {
      "data": {
       "text/plain": [
-       "('h', 'q', 'q', 'x', 'j', 'z', 's', 'i', 'i')"
+       "'lawnjgpwjik'"
       ]
      },
-     "execution_count": 557,
+     "execution_count": 781,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "enigma.set_wheels('a', 'a', 'a')\n",
-    "enigma.advance()\n",
-    "a = enigma.plugboard.forward('h')\n",
-    "b = enigma.right_wheel.forward(a)\n",
-    "c = enigma.middle_wheel.forward(b)\n",
-    "d = enigma.left_wheel.forward(c)\n",
-    "e = enigma.reflector.forward(d)\n",
-    "f = enigma.left_wheel.backward(e)\n",
-    "g = enigma.middle_wheel.backward(f)\n",
-    "h = enigma.right_wheel.backward(g)\n",
-    "i = enigma.plugboard.backward(h)\n",
-    "a, b, c, d, e, f, g, h, i"
+    "enigma.set_wheels('a', 'd', 't')\n",
+    "ct = enigma.encipher('testmessage')\n",
+    "assert(ct == 'lawnjgpwjik')\n",
+    "ct"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 558,
+   "execution_count": 782,
    "metadata": {
     "collapsed": false
    },
    "outputs": [
     {
-     "data": {
-      "text/plain": [
-       "'h'"
-      ]
-     },
-     "execution_count": 558,
-     "metadata": {},
-     "output_type": "execute_result"
+     "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": [
-    "pb = Plugboard('')\n",
-    "pb.forward('h')"
+    "\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": 561,
+   "execution_count": 783,
    "metadata": {
     "collapsed": false
    },
        "'bahxvfrpdc'"
       ]
      },
-     "execution_count": 561,
+     "execution_count": 783,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 562,
+   "execution_count": 784,
    "metadata": {
     "collapsed": false
    },
        "'kvmmwrlqlqsqpeugjrcxzwpfyiyybwloewrouvkpoztceuwtfjzqwpbqldttsr'"
       ]
      },
-     "execution_count": 562,
+     "execution_count": 784,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 563,
+   "execution_count": 785,
    "metadata": {
     "collapsed": false
    },
        "'c'"
       ]
      },
-     "execution_count": 563,
+     "execution_count": 785,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 564,
+   "execution_count": 786,
    "metadata": {
     "collapsed": true
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 566,
+   "execution_count": 787,
+   "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": 788,
+   "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": 789,
+   "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": 790,
    "metadata": {
     "collapsed": false
    },
    "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "wzmfiwuntn\n"
-     ]
-    },
     {
      "data": {
       "text/plain": [
-       "'wzmfiwuntn'"
+       "('verylongtestmessagewithanextrabitofmessageforgoodmeasure',\n",
+       " (3, 12, 6),\n",
+       " ('i', 'f', 'd'),\n",
+       " ([8], [20], [18]),\n",
+       " 'urygzpdmxtwshqvfnbljaokice')"
       ]
      },
-     "execution_count": 566,
+     "execution_count": 790,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "w_enigma.set_wheels('j', 'e', 'u')\n",
-    "ct = w_enigma.encipher('hellothere')\n",
-    "print(ct)\n",
-    "assert(ct == 'wzmfiwuntn')\n",
-    "ct"
+    "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": 568,
+   "execution_count": 791,
    "metadata": {
     "collapsed": false
    },
    "outputs": [
     {
-     "data": {
-      "text/plain": [
-       "'ayraqvsfkhflwsmkqicvfwawswmiwvvlteb'"
-      ]
-     },
-     "execution_count": 568,
-     "metadata": {},
-     "output_type": "execute_result"
+     "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": [
-    "w_enigma.set_wheels('n', 'y', 'q')\n",
-    "ct = w_enigma.encipher('hellotomweshouldgetbeerorcoffeesoon')\n",
-    "ct"
+    "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()"
    ]
   },
   {