Added tests for bombe
authorNeil Smith <neil.git@njae.me.uk>
Fri, 10 Jun 2016 09:41:30 +0000 (10:41 +0100)
committerNeil Smith <neil.git@njae.me.uk>
Wed, 4 Oct 2017 08:22:06 +0000 (09:22 +0100)
bombe.ipynb
bombe.py
test_bombe.py

index 59442e4f4da5bf6481be0f923171939245d2c938..93e0ccbfcd5ac4996251c5eb08db7e6deadd969b 100644 (file)
@@ -75,7 +75,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 31,
    "metadata": {
     "collapsed": false
    },
     "        self.connections += [Connection([bank_before, bank_after], scrambler)]\n",
     "        \n",
     "    def read_menu(self, menu):\n",
+    "        self.connections = []\n",
     "        for item in menu:\n",
     "            scrambler = Scrambler(self.wheel1_spec, self.wheel2_spec, self.wheel3_spec,\n",
     "                                  self.reflector_spec,\n",
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 32,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 33,
    "metadata": {
     "collapsed": true
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 34,
    "metadata": {
     "collapsed": false
    },
        "'opgndxcrwomnlnecjz'"
       ]
      },
-     "execution_count": 7,
+     "execution_count": 34,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 35,
    "metadata": {
     "collapsed": false
    },
        "'aas'"
       ]
      },
-     "execution_count": 8,
+     "execution_count": 35,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 36,
    "metadata": {
     "collapsed": false
    },
        " MenuIem(before='e', after='z', number=18)]"
       ]
      },
-     "execution_count": 9,
+     "execution_count": 36,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 37,
    "metadata": {
     "collapsed": true
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 38,
    "metadata": {
     "collapsed": false
    },
        " MenuIem(before='e', after='z', number=18)]"
       ]
      },
-     "execution_count": 11,
+     "execution_count": 38,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 39,
    "metadata": {
     "collapsed": false
    },
        "'s'"
       ]
      },
-     "execution_count": 12,
+     "execution_count": 39,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 42,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 43,
    "metadata": {
     "collapsed": false
    },
        "18"
       ]
      },
-     "execution_count": 14,
+     "execution_count": 43,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 27,
    "metadata": {
     "collapsed": false
    },
      "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": 16,
+   "execution_count": 44,
    "metadata": {
     "collapsed": false
    },
     {
      "data": {
       "text/plain": [
-       "False"
+       "'ot:hp:gi:ns:di:sx:ac:rt:ew:os:mt:mn:el:ns:es:ac:gj:ez'"
       ]
      },
-     "execution_count": 16,
+     "execution_count": 44,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "':'.join(cat(sorted(c.banks)) for c in bombe.connections)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 45,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'aaa:aab:aac:aad:aae:aaf:aag:aah:aai:aaj:aak:aal:aam:aan:aao:aap:aaq:aar'"
+      ]
+     },
+     "execution_count": 45,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "':'.join(cat(c.scrambler.wheel_positions_l) for c in bombe.connections)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 56,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 56,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 57,
    "metadata": {
     "collapsed": false
    },
     {
      "data": {
       "text/plain": [
-       "{'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",
+       "{'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",
        " 't': True,\n",
-       " 'u': True,\n",
-       " 'v': True,\n",
-       " 'w': True,\n",
-       " 'x': True,\n",
-       " 'y': True,\n",
-       " 'z': True}"
+       " 'u': False,\n",
+       " 'v': False,\n",
+       " 'w': False,\n",
+       " 'x': False,\n",
+       " 'y': False,\n",
+       " 'z': False}"
       ]
      },
-     "execution_count": 17,
+     "execution_count": 57,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 48,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 49,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "a : abcdefghijklmnopqrstuvwxyz\n",
+      "b : a.cde.ghij.lmnop.rst..wx.z\n",
+      "c : abcdefghijklmnopqrstuvwxyz\n",
+      "d : abcdefghijklmnopqrstuvwx.z\n",
+      "e : abcdefghijklmnopqrstuvwxyz\n",
+      "f : a.cde.ghij.lmnop.rst..wx.z\n",
+      "g : abcdefghijklmnopqrst.vwxyz\n",
+      "h : abcdefghijklmnopqrstuvwxyz\n",
+      "i : abcdefghijklmnopqrstu.wxyz\n",
+      "j : abcdefghi.klmnopqrstuvwxyz\n",
+      "k : a.cde.ghij.lmnop.rst..wx.z\n",
+      "l : abcdefghijklmnopqrstuvwxyz\n",
+      "m : abcdefghijklmnopqrstuvwxyz\n",
+      "n : abcdefghijklmnopqrstuvwxyz\n",
+      "o : abcdefghijklmnopqrstuvwxyz\n",
+      "p : abcdefghijklmnopqrstuvwxyz\n",
+      "q : a.cde.ghij.lmnop.rst..wx.z\n",
+      "r : abcdefghijklmnopqrstuvwxyz\n",
+      "s : abcdefghijklmnopqrstuvwxyz\n",
+      "t : abcdefghijklmnopqrstuvwxyz\n",
+      "u : a.cde..hij.lmnop.rst..wx.z\n",
+      "v : a.cde.gh.j.lmnop.rst..wx.z\n",
+      "w : abcdefghijklmnopqrstuvwxyz\n",
+      "x : abcdefghijklmnopqrstuvwxyz\n",
+      "y : a.c.e.ghij.lmnop.rst..wx.z\n",
+      "z : abcdefghijklmnopqrstuvwxyz\n"
+     ]
+    }
+   ],
+   "source": [
+    "for b in sorted(bombe.banks):\n",
+    "    print(b, ': ', end='')\n",
+    "    for w in sorted(bombe.banks[b]):\n",
+    "        if bombe.banks[b][w]:\n",
+    "            print(w, end='')\n",
+    "        else:\n",
+    "            print('.', end='')\n",
+    "    print('')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 50,
    "metadata": {
     "collapsed": false
    },
        "('a', 'a', 'a')"
       ]
      },
-     "execution_count": 19,
+     "execution_count": 50,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 51,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": 52,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 53,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "execution_count": 54,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
+   "execution_count": 55,
    "metadata": {
     "collapsed": false
    },
        "1"
       ]
      },
-     "execution_count": 24,
+     "execution_count": 55,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 37,
+   "execution_count": 58,
    "metadata": {
     "collapsed": false
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 38,
+   "execution_count": 59,
    "metadata": {
     "collapsed": false
    },
        "('e', 'l', 'e')"
       ]
      },
-     "execution_count": 38,
+     "execution_count": 59,
      "metadata": {},
      "output_type": "execute_result"
     }
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.4.3+"
+   "version": "3.5.1+"
   }
  },
  "nbformat": 4,
index acaad0eed11c24dbfe7eec834b028d91895083e2..b8971a74b98a5097ba0280e6b472198b11bf5e9b 100644 (file)
--- a/bombe.py
+++ b/bombe.py
@@ -9,6 +9,10 @@ Signal = collections.namedtuple('Signal', ['bank', 'wire'])
 Connection = collections.namedtuple('Connection', ['banks', 'scrambler'])
 MenuItem = collections.namedtuple('MenuIem', ['before', 'after', 'number'])
 
+def make_menu(plaintext, ciphertext):
+    return [MenuItem(p, c, i+1) 
+            for i, (p, c) in enumerate(zip(plaintext, ciphertext))]
+
 
 class Scrambler(object):
     def __init__(self, wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec,
@@ -82,6 +86,7 @@ class Bombe(object):
         self.connections += [Connection([bank_before, bank_after], scrambler)]
         
     def read_menu(self, menu):
+        self.connections = []
         for item in menu:
             scrambler = Scrambler(self.wheel1_spec, self.wheel2_spec, self.wheel3_spec,
                                   self.reflector_spec,
@@ -161,3 +166,16 @@ class Bombe(object):
                 possibles = possibles.union({frozenset((b, inactive[0]))})
         return possibles
 
+
+def run_multi_bombe(wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, menu,
+                    start_signal=None, use_diagonal_board=True, 
+                    verify_plugboard=True):
+    allwheels = itertools.product(string.ascii_lowercase, repeat=3)
+
+    with multiprocessing.Pool() as pool:
+        res = pool.map(Bombe(wheel1_spec, wheel2_spec, wheel3_spec, 
+            reflector_spec, menu=menu, start_signal=start_signal, 
+            use_diagonal_board=use_diagonal_board, 
+            verify_plugboard=verify_plugboard),
+                  allwheels)
+    return [r[0] for r in res if r[1]]
\ No newline at end of file
index 830bb2b1fdda74d4a415396edff434c2afc97052..bac7360075d790341150bb6c70936182cf78dc7b 100644 (file)
@@ -2,685 +2,133 @@ import unittest
 import collections
 
 from enigma import *
+from bombe import *
 
-class LetterTransformerTest(unittest.TestCase):
-
-    def test_maps1(self):
-        lt = LetterTransformer([('z', 'a')] + \
-            list(zip(string.ascii_lowercase, string.ascii_lowercase[1:])),
-            raw_transform = True)
-        self.assertEqual(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])
-        self.assertEqual(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])
-
-
-    def test_maps2(self):
-        lt = LetterTransformer(cat(collections.OrderedDict.fromkeys('zyxwc' + string.ascii_lowercase)))
-        self.assertEqual(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])
-        self.assertEqual(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])
-
-    def test_transform(self):
-        lt = LetterTransformer(cat(collections.OrderedDict.fromkeys('zyxwc' + string.ascii_lowercase)))
-        self.assertEqual(cat(lt.forward(l) for l in string.ascii_lowercase),
-            'zyxwcabdefghijklmnopqrstuv')
-        self.assertEqual(cat(lt.backward(l) for l in string.ascii_lowercase),
-            'fgehijklmnopqrstuvwxyzdcba')
-
-
-class PlugboardTest(unittest.TestCase):
-    def setUp(self):
-        self.pb = Plugboard('ua pf rq so ni ey bg hl tx zj'.upper())
-
-    def test_maps(self):
-        self.assertEqual(self.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])
-        self.assertEqual(self.pb.forward_map, self.pb.backward_map)
-
-    def test_transform(self):
-        self.assertEqual(cat(self.pb.forward(l) 
-                for l in string.ascii_lowercase),
-            'ugcdypblnzkhmisfrqoxavwtej')
-        self.assertEqual(cat(self.pb.backward(l) 
-                for l in string.ascii_lowercase),
-            'ugcdypblnzkhmisfrqoxavwtej')
-
-
-class ReflectorTest(unittest.TestCase):
+class ScramblerTest(unittest.TestCase):
     def setUp(self):
-        self.ref = Reflector(reflector_b_spec)
+        self.scrambler = Scrambler(wheel_i_spec, wheel_ii_spec, 
+            wheel_iii_spec, reflector_b_spec)
 
-    def test_maps(self):
-        self.assertEqual(self.ref.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])
-        self.assertEqual(self.ref.forward_map, self.ref.backward_map)
-
-    def test_transform(self):
-        self.assertEqual(cat(self.ref.forward(l) 
-                for l in string.ascii_lowercase),
-            'yruhqsldpxngokmiebfzcwvjat')
-        self.assertEqual(cat(self.ref.backward(l) 
-                for l in string.ascii_lowercase),
-            'yruhqsldpxngokmiebfzcwvjat')
+    def test_attributes(self):
+        self.assertEqual(self.scrambler.wheel_positions, (0, 0, 0))
+        self.assertEqual(self.scrambler.wheel_positions_l, ('a', 'a', 'a'))
 
-
-class SimpleWheelTest(unittest.TestCase):
-    def test_init1(self):
-        rotor_1_transform = list(zip(string.ascii_lowercase, 
-            'EKMFLGDQVZNTOWYHXUSPAIBRCJ'.lower()))
-        wheel_1 = SimpleWheel(rotor_1_transform, raw_transform=True)
-        self.assertEqual(cat(wheel_1.forward(l) 
-                for l in string.ascii_lowercase),
-            'ekmflgdqvzntowyhxuspaibrcj')
-        self.assertEqual(cat(wheel_1.backward(l) 
-                for l in string.ascii_lowercase),
-            'uwygadfpvzbeckmthxslrinqoj')
-
-    def test_init2(self):
-        wheel_2 = SimpleWheel(wheel_ii_spec)
-        self.assertEqual(cat(wheel_2.forward(l) 
-                for l in string.ascii_lowercase),
-            'ajdksiruxblhwtmcqgznpyfvoe')
-        self.assertEqual(cat(wheel_2.backward(l) 
-                for l in string.ascii_lowercase),
-            'ajpczwrlfbdkotyuqgenhxmivs')
+    def test_set_positions(self):
+        self.scrambler.set_positions(1, 2, 3)
+        self.assertEqual(self.scrambler.wheel_positions, (1, 2, 3))
+        self.assertEqual(self.scrambler.wheel_positions_l, ('b', 'c', 'd'))
+        self.scrambler.set_positions('p', 'q', 'r')
+        self.assertEqual(self.scrambler.wheel_positions, (15, 16, 17))
+        self.assertEqual(self.scrambler.wheel_positions_l, ('p', 'q', 'r'))
 
     def test_advance(self):
-        wheel_3 = SimpleWheel(wheel_iii_spec)
-        wheel_3.set_position('a')
-        wheel_3.advance()
-        self.assertEqual(cat(wheel_3.forward(l) 
-                for l in string.ascii_lowercase),
-            'cegikboqswuymxdhvfzjltrpna')
-        self.assertEqual(cat(wheel_3.backward(l) 
-                for l in string.ascii_lowercase),
-            'zfaobrcpdteumygxhwivkqjnls')
-        self.assertEqual(wheel_3.position, 1)
-        self.assertEqual(wheel_3.position_l, 'b')
-
-        for _ in range(24): wheel_3.advance()
-
-        self.assertEqual(wheel_3.position, 25)
-        self.assertEqual(wheel_3.position_l, 'z')
-
-        self.assertEqual(cat(wheel_3.forward(l) 
-                for l in string.ascii_lowercase),
-            'pcegikmdqsuywaozfjxhblnvtr')
-        self.assertEqual(cat(wheel_3.backward(l) 
-                for l in string.ascii_lowercase),
-            'nubhcqdterfvgwoaizjykxmslp')
-
-        wheel_3.advance()
-        self.assertEqual(wheel_3.position, 0)
-        self.assertEqual(wheel_3.position_l, 'a')
-
+        self.assertEqual(self.scrambler.wheel_positions, (0, 0, 0))
+        self.scrambler.advance()
+        self.assertEqual(self.scrambler.wheel_positions, (0, 0, 1))
+        self.scrambler.advance()
+        self.assertEqual(self.scrambler.wheel_positions, (0, 0, 2))
+        self.scrambler.set_positions(0, 0, 25)
+        self.assertEqual(self.scrambler.wheel_positions, (0, 0, 25))
+        self.scrambler.advance()
+        self.assertEqual(self.scrambler.wheel_positions, (0, 0, 0))
+        self.scrambler.set_positions(0, 0, 25)
+        self.scrambler.advance(wheel3=False)
+        self.assertEqual(self.scrambler.wheel_positions, (0, 0, 25))
+        self.scrambler.set_positions(0, 0, 25)
+        self.scrambler.advance(wheel2=True)
+        self.assertEqual(self.scrambler.wheel_positions, (0, 1, 0))
+        self.scrambler.set_positions(0, 0, 25)
+        self.scrambler.advance(wheel1=True, wheel2=True)
+        self.assertEqual(self.scrambler.wheel_positions, (1, 1, 0))
+
+    def test_lookups(self):
+        self.scrambler.set_positions(0, 0, 0)
+        self.assertEqual(cat(self.scrambler.lookup(l) 
+                for l in string.ascii_lowercase),
+            'uejobtpzwcnsrkdgvmlfaqiyxh')
+        self.assertEqual(cat(self.scrambler.lookup(l) 
+                for l in 'uejobtpzwcnsrkdgvmlfaqiyxh'),
+            'abcdefghijklmnopqrstuvwxyz')
+        self.scrambler.set_positions('p', 'q', 'r')
+        self.assertEqual(cat(self.scrambler.lookup(l) 
+                for l in string.ascii_lowercase),
+            'jgqmnwbtvaurdezxclyhkifpso')
+        self.assertEqual(cat(self.scrambler.lookup(l) 
+                for l in 'jgqmnwbtvaurdezxclyhkifpso'),
+            'abcdefghijklmnopqrstuvwxyz')
     
-        self.assertEqual(cat(wheel_3.forward(l) 
-                for l in string.ascii_lowercase),
-            'bdfhjlcprtxvznyeiwgakmusqo')
-        self.assertEqual(cat(wheel_3.backward(l) 
-                for l in string.ascii_lowercase),
-            'tagbpcsdqeufvnzhyixjwlrkom')
-
-
-class SimpleWheelTest(unittest.TestCase):
-    def test_init1(self):
-        wheel = Wheel(wheel_iii_spec, wheel_iii_pegs, position='b', 
-            ring_setting=1)
-        self.assertEqual(wheel.position, 1)
-        self.assertEqual(wheel.peg_positions, [20])
-        self.assertEqual(wheel.position_l, 'b')
-
-        wheel.advance()
-        self.assertEqual(wheel.position, 2)
-        self.assertEqual(wheel.peg_positions, [19])
-        self.assertEqual(wheel.position_l, 'c')
-
-    def test_init2(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', 
-            ring_setting=3)
-        self.assertEqual(wheel.position, 25)
-        self.assertIn(11, wheel.peg_positions)
-        self.assertIn(24, wheel.peg_positions)
-        self.assertEqual(wheel.position_l, 'b')
-        self.assertEqual(cat(wheel.forward(l) 
-                for l in string.ascii_lowercase),
-            'xkqhwpvngzrcfoiaselbtymjdu')
-        self.assertEqual(cat(wheel.backward(l) 
-                for l in string.ascii_lowercase),
-            'ptlyrmidoxbswhnfckquzgeavj')
-
-
-    def test_advance(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, 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.assertEqual(wheel.position_l, 'c')
-        self.assertEqual(cat(wheel.forward(l) 
-                for l in string.ascii_lowercase),
-            'jpgvoumfyqbenhzrdkasxlictw')
-        self.assertEqual(cat(wheel.backward(l) 
-                for l in string.ascii_lowercase),
-            'skxqlhcnwarvgmebjptyfdzuio')
-
-    def test_advance_23(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, 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.assertEqual(wheel.position_l, 'y')
-        self.assertEqual(cat(wheel.forward(l) 
-                for l in string.ascii_lowercase),
-            'mgxantkzsyqjcufirldvhoewbp')
-        self.assertEqual(cat(wheel.backward(l) 
-                for l in string.ascii_lowercase),
-            'dymswobuplgraevzkqifntxcjh')
-
-    def test_advance_24(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, 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.assertEqual(wheel.position_l, 'z')
-        self.assertEqual(cat(wheel.forward(l) 
-                for l in string.ascii_lowercase),
-            'fwzmsjyrxpibtehqkcugndvaol')
-        self.assertEqual(cat(wheel.backward(l) 
-                for l in string.ascii_lowercase),
-            'xlrvnatokfqzduyjphemswbigc')
-
-    def test_advance_25(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, 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.assertEqual(wheel.position_l, 'a')
-        self.assertEqual(cat(wheel.forward(l) 
-                for l in string.ascii_lowercase),
-            'vylrixqwohasdgpjbtfmcuznke')
-        self.assertEqual(cat(wheel.backward(l) 
-                for l in string.ascii_lowercase),
-            'kqumzsnjepyctxiogdlrvahfbw')
-
-    def test_advance_26(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, 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.assertEqual(wheel.position_l, 'b')
-        self.assertEqual(cat(wheel.forward(l) 
-                for l in string.ascii_lowercase),
-            'xkqhwpvngzrcfoiaselbtymjdu')
-        self.assertEqual(cat(wheel.backward(l) 
-                for l in string.ascii_lowercase),
-            'ptlyrmidoxbswhnfckquzgeavj')
-
-
-    def test_advance_27(self):
-        wheel = Wheel(wheel_vi_spec, wheel_vi_pegs, 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.assertEqual(wheel.position_l, 'c')
-        self.assertEqual(cat(wheel.forward(l) 
-                for l in string.ascii_lowercase),
-            'jpgvoumfyqbenhzrdkasxlictw')
-        self.assertEqual(cat(wheel.backward(l) 
-                for l in string.ascii_lowercase),
-            'skxqlhcnwarvgmebjptyfdzuio')
-
-class EnigmaTest(unittest.TestCase):
-
+class BombeTest(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, 
-                1, 1, 1, 
-                '')
-
-        # Setting sheet line 31 from http://www.codesandciphers.org.uk/enigma/enigma3.htm
-        # 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, 
-                6, 20, 24, 
-                'ua pf rq so ni ey bg hl tx zj')
-
-
-    def test_middle_advance(self):
-        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(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(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(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(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(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(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
-            'oljmzxrvucybdqasngpwihtfke')
-
-
-    def test_double_advance(self):
-        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(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(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(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(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(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(cat(self.enigma.lookup(l) for l in string.ascii_lowercase),
-            'baknstqzrmcxjdvygiefwoulph')
-
-
-    def test_simple_encipher(self):
-        self.enigma.set_wheels('a', 'a', 'a')
-        ct = self.enigma.encipher('testmessage')
-        self.assertEqual(ct, 'olpfhnvflyn')
-
-        self.enigma.set_wheels('a', 'd', 't')
-        ct = self.enigma.encipher('testmessage')
-        self.assertEqual(ct, 'lawnjgpwjik')
-
-        self.enigma.set_wheels('b', 'd', 'q')
-        ct = self.enigma.encipher('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
-        self.assertEqual(ct, 
-            'kvmmwrlqlqsqpeugjrcxzwpfyiyybwloewrouvkpoztceuwtfjzqwpbqldttsr')
-        self.assertEqual(cat(self.enigma.wheel_positions_l), 'cha')
-
-
-    def test_advance_with_ring_settings(self):
-        self.enigma31.set_wheels('j', 'e', 'u')
-
-        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(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(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(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(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(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
-            'zevnbpyqowrtxdifhkulscjmga')
-
-
-    def test_advance_with_ring_settings_2(self):
-        self.enigma31.set_wheels('i', 'd', 'z')
-
-        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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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.peg_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.peg_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.peg_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.peg_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.peg_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.peg_positions, ([15], [25], [23]))  
-
-        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.peg_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.peg_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.peg_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.peg_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.peg_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.peg_positions, ([15], [24], [23]))
-
-
-    def test_encipher_with_ring(self):
-
-        self.enigma31.set_wheels('i', 'z', 'd')
-        ct = self.enigma31.encipher('verylongtestmessagewithanextrabitofmessageforgoodmeasure')
-        self.assertEqual(ct, 
-            '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(cat(self.enigma31.lookup(l) for l in string.ascii_lowercase),
-            'mopnigfuesqwadbcktjrhylzvx')
-
-        self.enigma31.set_wheels('i', 'z', 'd')
-        pt = self.enigma31.decipher('apocwtjuikurcfivlozvhffkoacxufcekthcvodfqpxdjqyckdozlqki')
-        self.assertEqual(pt, 
-            'verylongtestmessagewithanextrabitofmessageforgoodmeasure')
+        self.bombe = Bombe(wheel_i_spec, wheel_ii_spec, 
+            wheel_iii_spec, reflector_b_spec)
+        self.plaintext = 'thisisatestmessage'
+        self.ciphertext = 'opgndxcrwomnlnecjz'
+        self.menu = make_menu(self.plaintext, self.ciphertext)
+        self.bombe.read_menu(self.menu)
+
+    def test_menu(self):
+        self.assertEqual(len(self.bombe.connections), 18)
+        self.assertEqual(':'.join(sorted(cat(sorted(c.banks))
+                for c in self.bombe.connections)),
+            'ac:ac:di:el:es:ew:ez:gi:gj:hp:mn:mt:ns:ns:os:ot:rt:sx')
+        self.assertEqual(':'.join(sorted(cat(c.scrambler.wheel_positions_l)
+                for c in self.bombe.connections)),
+            'aaa:aab:aac:aad:aae:aaf:aag:aah:aai:aaj:aak:aal:aam:aan:aao:aap:aaq:aar')
+
+        self.bombe.read_menu(self.menu)
+        self.assertEqual(len(self.bombe.connections), 18)
+
+    def test_signal(self):
+        self.bombe.test(Signal('t', 't'))
+        self.assertEqual(len(self.bombe.banks['t']), 26)
+        self.assertTrue(all(self.bombe.banks['t'].values()))
+        self.assertEqual(sum(1 for s in self.bombe.banks['u'].values() if s), 18)
+
+        self.bombe.set_positions('a', 'a', 'b')
+        self.bombe.test()
+        self.assertEqual(sum(1 for b in self.bombe.banks 
+                for s in self.bombe.banks[b].values() if s),
+            11)
+
+    def test_valid_with_rings(self):
+        pt31 = 'someplaintext'
+        ct31 = 'dhnpforeeimgg'
+        menu31 = make_menu(pt31, ct31)
+        b31 = Bombe(wheel_i_spec, wheel_v_spec, wheel_iii_spec, reflector_b_spec)
+        b31.read_menu(menu31)
+        b31.set_positions('e', 'l', 'f')
+
+        b31.test(Signal('s', 'o'))
+        self.assertEqual(sum(1 for b in b31.banks 
+                for s in b31.banks[b].values() if s),
+            5)
+        self.assertEqual(':'.join(sorted(cat(sorted(p)) 
+                for p in b31.possible_plugboards())),
+            'd:hl:os')
+
+        b31.test(Signal('o', 'o'))
+        self.assertEqual(sum(1 for b in b31.banks 
+                for s in b31.banks[b].values() if s),
+            507)
+        self.assertEqual(':'.join(sorted(cat(sorted(p)) 
+                for p in b31.possible_plugboards())),
+            'bg:ey:fp:in:m:tx')
+
+    def test_invalid_with_rings(self):
+        pt31 = 'someplaintext'
+        ct31 = 'dhnpforeeimgg'
+        menu31 = make_menu(pt31, ct31)
+        b31 = Bombe(wheel_i_spec, wheel_v_spec, wheel_iii_spec, reflector_b_spec)
+        b31.read_menu(menu31)
+        b31.set_positions('a', 'a', 'a')
+
+        b31.test(Signal('a', 'o'))
+        self.assertEqual(sum(1 for b in b31.banks 
+                for s in b31.banks[b].values() if s),
+            514)
+        self.assertEqual(':'.join(sorted(cat(sorted(p)) 
+                for p in b31.possible_plugboards())),
+            '')
 
 if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
+    unittest.main()