Done challenge 4
[cipher-tools.git] / enigma.py
index 72f480792170725c0b59462781d338961c534510..89758f532087985078041473006ce3d6510e3933 100644 (file)
--- a/enigma.py
+++ b/enigma.py
@@ -16,6 +16,21 @@ import string
 import collections
 import multiprocessing
 import itertools
 import collections
 import multiprocessing
 import itertools
+import logging
+
+logger = logging.getLogger('engima')
+logger.setLevel(logging.WARNING)
+# logger.setLevel(logging.INFO)
+# logger.setLevel(logging.DEBUG)
+
+# create the logging file handler
+fh = logging.FileHandler("enigma.log")
+formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+fh.setFormatter(formatter)
+
+# add handler to logger object
+logger.addHandler(fh)
+
 
 # Some convenience functions
 
 
 # Some convenience functions
 
@@ -45,14 +60,14 @@ wheel_viii_spec = 'fkqhtlxocbjspdzramewniuygv'
 beta_wheel_spec = 'leyjvcnixwpbqmdrtakzgfuhos'
 gamma_wheel_spec = 'fsokanuerhmbtiycwlqpzxvgjd'
 
 beta_wheel_spec = 'leyjvcnixwpbqmdrtakzgfuhos'
 gamma_wheel_spec = 'fsokanuerhmbtiycwlqpzxvgjd'
 
-wheel_i_pegs = ['q']
-wheel_ii_pegs = ['e']
-wheel_iii_pegs = ['v']
-wheel_iv_pegs = ['j']
-wheel_v_pegs = ['z']
-wheel_vi_pegs = ['z', 'm']
-wheel_vii_pegs = ['z', 'm']
-wheel_viii_pegs = ['z', 'm']
+wheel_i_notches = ['q']
+wheel_ii_notches = ['e']
+wheel_iii_notches = ['v']
+wheel_iv_notches = ['j']
+wheel_v_notches = ['z']
+wheel_vi_notches = ['z', 'm']
+wheel_vii_notches = ['z', 'm']
+wheel_viii_notches = ['z', 'm']
 
 reflector_b_spec = 'ay br cu dh eq fs gl ip jx kn mo tz vw'
 reflector_c_spec = 'af bv cp dj ei go hy kr lz mx nw tq su'
 
 reflector_b_spec = 'ay br cu dh eq fs gl ip jx kn mo tz vw'
 reflector_c_spec = 'af bv cp dj ei go hy kr lz mx nw tq su'
@@ -63,24 +78,6 @@ class LetterTransformer(object):
     """A generic substitution cipher, that has different transforms in the 
     forward and backward directions. It requires that the transforms for all
     letters by provided.
     """A generic substitution cipher, that has different transforms in the 
     forward and backward directions. It requires that the transforms for all
     letters by provided.
-
-    >>> lt = LetterTransformer([('z', 'a')] + [(l, string.ascii_lowercase[i+1]) \
-            for i, l in enumerate(string.ascii_lowercase[:-1])], \
-            raw_transform = True)
-    >>> 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]
-    >>> 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]
-
-    >>> lt = LetterTransformer(cat(collections.OrderedDict.fromkeys('zyxwc' + string.ascii_lowercase)))
-    >>> 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]
-    >>> 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]
-    >>> cat(lt.forward(l) for l in string.ascii_lowercase)
-    'zyxwcabdefghijklmnopqrstuv'
-    >>> cat(lt.backward(l) for l in string.ascii_lowercase)
-    'fgehijklmnopqrstuvwxyzdcba'
     """
     def __init__(self, specification, raw_transform=False):
         if raw_transform:
     """
     def __init__(self, specification, raw_transform=False):
         if raw_transform:
@@ -136,16 +133,6 @@ class Plugboard(LetterTransformer):
     """A plugboard, a type of letter transformer where forward and backward
     transforms are the same. If a letter isn't explicitly transformed, it is 
     kept as it is.
     """A plugboard, a type of letter transformer where forward and backward
     transforms are the same. If a letter isn't explicitly transformed, it is 
     kept as it is.
-
-    >>> pb = Plugboard('ua pf rq so ni ey bg hl tx zj'.upper())
-    >>> 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]
-    >>> pb.forward_map == pb.backward_map
-    True
-    >>> cat(pb.forward(l) for l in string.ascii_lowercase)
-    'ugcdypblnzkhmisfrqoxavwtej'
-    >>> cat(pb.backward(l) for l in string.ascii_lowercase)
-    'ugcdypblnzkhmisfrqoxavwtej'
     """
     def parse_specification(self, specification):
         return [tuple(clean(p)) for p in specification.split()]
     """
     def parse_specification(self, specification):
         return [tuple(clean(p)) for p in specification.split()]
@@ -170,16 +157,6 @@ class Plugboard(LetterTransformer):
 
 class Reflector(Plugboard):
     """A reflector is a plugboard that requires 13 transforms.
 
 class Reflector(Plugboard):
     """A reflector is a plugboard that requires 13 transforms.
-
-    >>> reflector_b = Reflector(reflector_b_spec)
-    >>> reflector_b.forward_map == reflector_b.backward_map
-    True
-    >>> 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]
-    >>> cat(reflector_b.forward(l) for l in string.ascii_lowercase)
-    'yruhqsldpxngokmiebfzcwvjat'
-    >>> cat(reflector_b.backward(l) for l in string.ascii_lowercase)
-    'yruhqsldpxngokmiebfzcwvjat'
     """
     def validate_transform(self, transform):
         if len(transform) != 13:
     """
     def validate_transform(self, transform):
         if len(transform) != 13:
@@ -208,52 +185,6 @@ class SimpleWheel(LetterTransformer):
     Letter inputs and outputs are given relative to the frame holding the wheel,
     so if the wheel is advanced three places, an input of 'p' will enter the 
     wheel on the position under the wheel's 'q' label.
     Letter inputs and outputs are given relative to the frame holding the wheel,
     so if the wheel is advanced three places, an input of 'p' will enter the 
     wheel on the position under the wheel's 'q' label.
-
-    >>> rotor_1_transform = list(zip(string.ascii_lowercase, 'EKMFLGDQVZNTOWYHXUSPAIBRCJ'.lower()))
-    >>> wheel_1 = SimpleWheel(rotor_1_transform, raw_transform=True)
-    >>> cat(wheel_1.forward(l) for l in string.ascii_lowercase)
-    'ekmflgdqvzntowyhxuspaibrcj'
-    >>> cat(wheel_1.backward(l) for l in string.ascii_lowercase)
-    'uwygadfpvzbeckmthxslrinqoj'
-
-
-    >>> wheel_2 = SimpleWheel(wheel_ii_spec)
-    >>> cat(wheel_2.forward(l) for l in string.ascii_lowercase)
-    'ajdksiruxblhwtmcqgznpyfvoe'
-    >>> cat(wheel_2.backward(l) for l in string.ascii_lowercase)
-    'ajpczwrlfbdkotyuqgenhxmivs'
-
-    >>> wheel_3 = SimpleWheel(wheel_iii_spec)
-    >>> wheel_3.set_position('a')
-    >>> wheel_3.advance()
-    >>> cat(wheel_3.forward(l) for l in string.ascii_lowercase)
-    'cegikboqswuymxdhvfzjltrpna'
-    >>> cat(wheel_3.backward(l) for l in string.ascii_lowercase)
-    'zfaobrcpdteumygxhwivkqjnls'
-    >>> wheel_3.position
-    1
-    >>> wheel_3.position_l
-    'b'
-
-    >>> for _ in range(24): wheel_3.advance()
-    >>> wheel_3.position
-    25
-    >>> wheel_3.position_l
-    'z'
-    >>> cat(wheel_3.forward(l) for l in string.ascii_lowercase)
-    'pcegikmdqsuywaozfjxhblnvtr'
-    >>> cat(wheel_3.backward(l) for l in string.ascii_lowercase)
-    'nubhcqdterfvgwoaizjykxmslp'
-
-    >>> wheel_3.advance()
-    >>> wheel_3.position
-    0
-    >>> wheel_3.position_l
-    'a'
-    >>> cat(wheel_3.forward(l) for l in string.ascii_lowercase)
-    'bdfhjlcprtxvznyeiwgakmusqo'
-    >>> cat(wheel_3.backward(l) for l in string.ascii_lowercase)
-    'tagbpcsdqeufvnzhyixjwlrkom'
     """
     def __init__(self, transform, position='a', raw_transform=False):
         super(SimpleWheel, self).__init__(transform, raw_transform)
     """
     def __init__(self, transform, position='a', raw_transform=False):
         super(SimpleWheel, self).__init__(transform, raw_transform)
@@ -266,7 +197,11 @@ class SimpleWheel(LetterTransformer):
             return object.__getattribute__(self, name)
     
     def set_position(self, position):
             return object.__getattribute__(self, name)
     
     def set_position(self, position):
-        self.position = ord(position) - ord('a')
+        if isinstance(position, str):
+            # self.position = ord(position) - ord('a')
+            self.position = pos(position)
+        else:
+            self.position = position
     
     def forward(self, letter):
         if letter in string.ascii_lowercase:
     
     def forward(self, letter):
         if letter in string.ascii_lowercase:
@@ -288,7 +223,7 @@ class SimpleWheel(LetterTransformer):
 class Wheel(SimpleWheel):
     """A wheel with a movable ring.
 
 class Wheel(SimpleWheel):
     """A wheel with a movable ring.
 
-    The ring holds the letters and the pegs that turn other wheels. The core
+    The ring holds the letters and the notches that turn other wheels. The core
     holds the wiring that does the transformation.
 
     The ring position is how many steps the core is turned relative to the ring.
     holds the wiring that does the transformation.
 
     The ring position is how many steps the core is turned relative to the ring.
@@ -301,125 +236,12 @@ class Wheel(SimpleWheel):
     The position_l is the position of the ring, or what would be observed
     by the user of the Enigma machine. 
 
     The position_l is the position of the ring, or what would be observed
     by the user of the Enigma machine. 
 
-    The peg_positions are the number of advances of this wheel before it will 
+    The notch_positions are the number of advances of this wheel before it will 
     advance the next wheel.
 
     advance the next wheel.
 
-    >>> wheel_3 = Wheel(wheel_iii_spec, wheel_iii_pegs, position='b', ring_setting=1)
-    >>> wheel_3.position
-    1
-    >>> wheel_3.peg_positions
-    [20]
-    >>> wheel_3.position_l
-    'b'
-    >>> wheel_3.advance()
-    >>> wheel_3.position
-    2
-    >>> wheel_3.peg_positions
-    [19]
-    >>> wheel_3.position_l
-    'c'
-
-    >>> wheel_6 = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', ring_setting=3)
-    >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
-    'xkqhwpvngzrcfoiaselbtymjdu'
-    >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
-    'ptlyrmidoxbswhnfckquzgeavj'
-    >>> wheel_6.position
-    25
-    >>> 11 in wheel_6.peg_positions
-    True
-    >>> 24 in wheel_6.peg_positions
-    True
-    >>> wheel_6.position_l
-    'b'
-
-    >>> wheel_6.advance()
-    >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
-    'jpgvoumfyqbenhzrdkasxlictw'
-    >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
-    'skxqlhcnwarvgmebjptyfdzuio'
-    >>> wheel_6.position
-    0
-    >>> 10 in wheel_6.peg_positions
-    True
-    >>> 23 in wheel_6.peg_positions
-    True
-    >>> wheel_6.position_l
-    'c'
-
-    >>> for _ in range(22): wheel_6.advance()
-    >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
-    'mgxantkzsyqjcufirldvhoewbp'
-    >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
-    'dymswobuplgraevzkqifntxcjh'
-    >>> wheel_6.position
-    22
-    >>> 1 in wheel_6.peg_positions
-    True
-    >>> 14 in wheel_6.peg_positions
-    True
-    >>> wheel_6.position_l
-    'y'
-
-    >>> wheel_6.advance()
-    >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
-    'fwzmsjyrxpibtehqkcugndvaol'
-    >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
-    'xlrvnatokfqzduyjphemswbigc'
-    >>> wheel_6.position
-    23
-    >>> 0 in wheel_6.peg_positions
-    True
-    >>> 13 in wheel_6.peg_positions
-    True
-    >>> wheel_6.position_l
-    'z'
-
-    >>> wheel_6.advance()
-    >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
-    'vylrixqwohasdgpjbtfmcuznke'
-    >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
-    'kqumzsnjepyctxiogdlrvahfbw'
-    >>> wheel_6.position
-    24
-    >>> 25 in wheel_6.peg_positions
-    True
-    >>> 12 in wheel_6.peg_positions
-    True
-    >>> wheel_6.position_l
-    'a'
-
-    >>> wheel_6.advance()
-    >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
-    'xkqhwpvngzrcfoiaselbtymjdu'
-    >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
-    'ptlyrmidoxbswhnfckquzgeavj'
-    >>> wheel_6.position
-    25
-    >>> 24 in wheel_6.peg_positions
-    True
-    >>> 11 in wheel_6.peg_positions
-    True
-    >>> wheel_6.position_l
-    'b'
-
-    >>> wheel_6.advance()
-    >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
-    'jpgvoumfyqbenhzrdkasxlictw'
-    >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
-    'skxqlhcnwarvgmebjptyfdzuio'
-    >>> wheel_6.position
-    0
-    >>> 23 in wheel_6.peg_positions
-    True
-    >>> 10 in wheel_6.peg_positions
-    True
-    >>> wheel_6.position_l
-    'c'
-
     """
     """
-    def __init__(self, transform, ring_peg_letters, ring_setting=1, position='a', raw_transform=False):
-        self.ring_peg_letters = ring_peg_letters
+    def __init__(self, transform, ring_notch_letters, ring_setting=1, position='a', raw_transform=False):
+        self.ring_notch_letters = ring_notch_letters
         self.ring_setting = ring_setting
         super(Wheel, self).__init__(transform, position=position, raw_transform=raw_transform)
         self.set_position(position)
         self.ring_setting = ring_setting
         super(Wheel, self).__init__(transform, position=position, raw_transform=raw_transform)
         self.set_position(position)
@@ -431,511 +253,35 @@ class Wheel(SimpleWheel):
             return object.__getattribute__(self, name)
 
     def set_position(self, position):
             return object.__getattribute__(self, name)
 
     def set_position(self, position):
-        self.position = (pos(position) - self.ring_setting + 1) % 26
-        self.peg_positions = [(pos(p) - pos(position)) % 26  for p in self.ring_peg_letters]
+        if isinstance(position, str):
+            self.position = (pos(position) - self.ring_setting + 1) % 26
+        else:
+            self.position = (position - self.ring_setting) % 26
+        # # self.notch_positions = [(pos(p) - pos(position)) % 26  for p in self.ring_notch_letters]
+        # self.notch_positions = [(pos(p) - (self.position + self.ring_setting - 1)) % 26  for p in self.ring_notch_letters]
+        self.notch_positions = [(self.position + self.ring_setting - 1 - pos(p)) % 26  for p in self.ring_notch_letters]
         
     def advance(self):
         super(Wheel, self).advance()
         
     def advance(self):
         super(Wheel, self).advance()
-        self.peg_positions = [(p - 1) % 26 for p in self.peg_positions]
+        self.notch_positions = [(p + 1) % 26 for p in self.notch_positions]
+        return self.position
 
 
 class Enigma(object):
     """An Enigma machine.
 
 
 
 class Enigma(object):
     """An Enigma machine.
 
-    >>> 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, \
-                '')
-    >>> enigma.set_wheels('a', 'a', 't')
-    >>> enigma.wheel_positions
-    (0, 0, 19)
-    >>> cat(enigma.wheel_positions_l)
-    'aat'
-    >>> enigma.peg_positions
-    ([16], [4], [2])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'puvioztjdhxmlyeawsrgbcqknf'
-
-    >>> enigma.advance()
-    >>> enigma.wheel_positions
-    (0, 0, 20)
-    >>> cat(enigma.wheel_positions_l)
-    'aau'
-    >>> enigma.peg_positions
-    ([16], [4], [1])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'baigpldqcowfyzjehvtsxrkumn'
-
-    >>> enigma.advance()
-    >>> enigma.wheel_positions
-    (0, 0, 21)
-    >>> cat(enigma.wheel_positions_l)
-    'aav'
-    >>> enigma.peg_positions
-    ([16], [4], [0])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'mnvfydiwgzsoablrxpkutchqej'
-
-    >>> enigma.advance()
-    >>> enigma.wheel_positions
-    (0, 1, 22)
-    >>> cat(enigma.wheel_positions_l)
-    'abw'
-    >>> enigma.peg_positions
-    ([16], [3], [25])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'ulfopcykswhbzvderqixanjtgm'
-
-    >>> enigma.advance()
-    >>> enigma.wheel_positions
-    (0, 1, 23)
-    >>> cat(enigma.wheel_positions_l)
-    'abx'
-    >>> enigma.peg_positions
-    ([16], [3], [24])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'qmwftdyovursbzhxaklejicpgn'
-
-    >>> enigma.advance()
-    >>> enigma.wheel_positions
-    (0, 1, 24)
-    >>> cat(enigma.wheel_positions_l)
-    'aby'
-    >>> enigma.peg_positions
-    ([16], [3], [23])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'oljmzxrvucybdqasngpwihtfke'
-
-
-
-
-    >>> enigma.set_wheels('a', 'd', 't')
-    >>> enigma.wheel_positions
-    (0, 3, 19)
-    >>> cat(enigma.wheel_positions_l)
-    'adt'
-    >>> enigma.peg_positions
-    ([16], [1], [2])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'zcbpqxwsjiuonmldethrkygfva'
-
-    >>> enigma.advance()
-    >>> enigma.wheel_positions
-    (0, 3, 20)
-    >>> cat(enigma.wheel_positions_l)
-    'adu'
-    >>> enigma.peg_positions
-    ([16], [1], [1])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'ehprawjbngotxikcsdqlzyfmvu'
-
-    >>> enigma.advance()
-    >>> enigma.wheel_positions
-    (0, 3, 21)
-    >>> cat(enigma.wheel_positions_l)
-    'adv'
-    >>> enigma.peg_positions
-    ([16], [1], [0])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'eqzxarpihmnvjkwgbfuyslodtc'
-
-    >>> enigma.advance()
-    >>> enigma.wheel_positions
-    (0, 4, 22)
-    >>> cat(enigma.wheel_positions_l)
-    'aew'
-    >>> enigma.peg_positions
-    ([16], [0], [25])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'qedcbtpluzmhkongavwfirsyxj'
-
-    >>> enigma.advance()
-    >>> enigma.wheel_positions
-    (1, 5, 23)
-    >>> cat(enigma.wheel_positions_l)
-    'bfx'
-    >>> enigma.peg_positions
-    ([15], [25], [24])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'iwuedhsfazqxytvrkpgncoblmj'
-
-    >>> enigma.advance()
-    >>> enigma.wheel_positions
-    (1, 5, 24)
-    >>> cat(enigma.wheel_positions_l)
-    'bfy'
-    >>> enigma.peg_positions
-    ([15], [25], [23])
-    >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
-    'baknstqzrmcxjdvygiefwoulph'
-
-
-    >>> enigma.set_wheels('a', 'a', 'a')
-    >>> ct = enigma.encipher('testmessage')
-    >>> ct
-    'olpfhnvflyn'
-
-    >>> enigma.set_wheels('a', 'd', 't')
-    >>> ct = enigma.encipher('testmessage')
-    >>> ct
-    'lawnjgpwjik'
-
-
-    >>> enigma.set_wheels('b', 'd', 'q')
-    >>> ct = enigma.encipher('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
-    >>> ct
-    'kvmmwrlqlqsqpeugjrcxzwpfyiyybwloewrouvkpoztceuwtfjzqwpbqldttsr'
-    >>> enigma.left_wheel.position_l
-    'c'
-    >>> enigma.middle_wheel.position_l
-    'h'
-    >>> enigma.right_wheel.position_l
-    'a'
-
-    # 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
-    >>> 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')
-
-    >>> enigma31.set_wheels('j', 'e', 'u')
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (4, 11, 24)
-    >>> cat(enigma31.wheel_positions_l)
-    'jev'
-    >>> enigma31.peg_positions
-    ([7], [21], [0])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'mvqjlyowkdieasgzcunxrbhtfp'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (4, 12, 25)
-    >>> cat(enigma31.wheel_positions_l)
-    'jfw'
-    >>> enigma31.peg_positions
-    ([7], [20], [25])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'sjolzuyvrbwdpxcmtiaqfhknge'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (4, 12, 0)
-    >>> cat(enigma31.wheel_positions_l)
-    'jfx'
-    >>> enigma31.peg_positions
-    ([7], [20], [24])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'qrxedkoywufmlvgsabpzjnicht'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (4, 12, 1)
-    >>> cat(enigma31.wheel_positions_l)
-    'jfy'
-    >>> enigma31.peg_positions
-    ([7], [20], [23])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'hpsukliagqefwvtbjxcodnmrzy'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (4, 12, 2)
-    >>> cat(enigma31.wheel_positions_l)
-    'jfz'
-    >>> enigma31.peg_positions
-    ([7], [20], [22])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'zevnbpyqowrtxdifhkulscjmga'
-
-
-    >>> enigma31.set_wheels('i', 'd', 'z')
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 3)
-    >>> cat(enigma31.wheel_positions_l)
-    'ida'
-    >>> enigma31.peg_positions
-    ([8], [22], [21])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'ikhpqrvcambzjondefwyxgsutl'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 4)
-    >>> cat(enigma31.wheel_positions_l)
-    'idb'
-    >>> enigma31.peg_positions
-    ([8], [22], [20])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'cdabskhgzwfmlqvunyexpojtri'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 5)
-    >>> cat(enigma31.wheel_positions_l)
-    'idc'
-    >>> enigma31.peg_positions
-    ([8], [22], [19])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'pcbwiqhgemyvjsuaftnroldzkx'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 6)
-    >>> cat(enigma31.wheel_positions_l)
-    'idd'
-    >>> enigma31.peg_positions
-    ([8], [22], [18])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'xcbfvdnouptmlghjzwykierasq'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 7)
-    >>> cat(enigma31.wheel_positions_l)
-    'ide'
-    >>> enigma31.peg_positions
-    ([8], [22], [17])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'xfvglbdynuseriwqpmkzjcoaht'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 8)
-    >>> cat(enigma31.wheel_positions_l)
-    'idf'
-    >>> enigma31.peg_positions
-    ([8], [22], [16])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'tfpqlbouynsewjgcdxkahzmriv'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 9)
-    >>> cat(enigma31.wheel_positions_l)
-    'idg'
-    >>> enigma31.peg_positions
-    ([8], [22], [15])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'cjaunvlwtbygzexrspqidfhokm'
 
 
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 10)
-    >>> cat(enigma31.wheel_positions_l)
-    'idh'
-    >>> enigma31.peg_positions
-    ([8], [22], [14])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'yltxkrqvowebzpingfucshjdam'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 11)
-    >>> cat(enigma31.wheel_positions_l)
-    'idi'
-    >>> enigma31.peg_positions
-    ([8], [22], [13])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'myktluzrnxceaiqsohpdfwvjbg'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 12)
-    >>> cat(enigma31.wheel_positions_l)
-    'idj'
-    >>> enigma31.peg_positions
-    ([8], [22], [12])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'pynjrmiugdqxfcvakewzhoslbt'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 13)
-    >>> cat(enigma31.wheel_positions_l)
-    'idk'
-    >>> enigma31.peg_positions
-    ([8], [22], [11])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'mwvedyplnoxhaijgrqtszcbkfu'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 14)
-    >>> cat(enigma31.wheel_positions_l)
-    'idl'
-    >>> enigma31.peg_positions
-    ([8], [22], [10])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'qcbrfeutvoxpnmjladzhgiykws'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 15)
-    >>> cat(enigma31.wheel_positions_l)
-    'idm'
-    >>> enigma31.peg_positions
-    ([8], [22], [9])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'dnoahryetsmukbcvwfjilpqzgx'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 16)
-    >>> cat(enigma31.wheel_positions_l)
-    'idn'
-    >>> enigma31.peg_positions
-    ([8], [22], [8])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'nidcfehgbqsovalyjzkxwmutpr'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 17)
-    >>> cat(enigma31.wheel_positions_l)
-    'ido'
-    >>> enigma31.peg_positions
-    ([8], [22], [7])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'joifxdulcarhzpbntkwqgysevm'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 18)
-    >>> cat(enigma31.wheel_positions_l)
-    'idp'
-    >>> enigma31.peg_positions
-    ([8], [22], [6])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'ptnlsxvozmwdjchayuebrgkfqi'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 19)
-    >>> cat(enigma31.wheel_positions_l)
-    'idq'
-    >>> enigma31.peg_positions
-    ([8], [22], [5])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'slwopzqnmxybihdeguavrtcjkf'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 20)
-    >>> cat(enigma31.wheel_positions_l)
-    'idr'
-    >>> enigma31.peg_positions
-    ([8], [22], [4])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'hcbedwlamzogixkytsrqvufnpj'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 21)
-    >>> cat(enigma31.wheel_positions_l)
-    'ids'
-    >>> enigma31.peg_positions
-    ([8], [22], [3])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'odxbjwzrmelkisavuhnyqpfctg'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 22)
-    >>> cat(enigma31.wheel_positions_l)
-    'idt'
-    >>> enigma31.peg_positions
-    ([8], [22], [2])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'udgbfeclrwnhxksvtioqapjmzy'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 23)
-    >>> cat(enigma31.wheel_positions_l)
-    'idu'
-    >>> enigma31.peg_positions
-    ([8], [22], [1])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'nrdczqxmowvshaiufblypkjgte'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 10, 24)
-    >>> cat(enigma31.wheel_positions_l)
-    'idv'
-    >>> enigma31.peg_positions
-    ([8], [22], [0])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'hkifjdoacebqtzgulyvmpsxwrn'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 11, 25)
-    >>> cat(enigma31.wheel_positions_l)
-    'iew'
-    >>> enigma31.peg_positions
-    ([8], [21], [25])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'yptzuhofqvnmlkgbixwcejsrad'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 11, 0)
-    >>> cat(enigma31.wheel_positions_l)
-    'iex'
-    >>> enigma31.peg_positions
-    ([8], [21], [24])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'vkdcwhqfjibzsptngumoraeyxl'
-
-    >>> enigma31.advance()
-    >>> enigma31.wheel_positions
-    (3, 11, 1)
-    >>> cat(enigma31.wheel_positions_l)
-    'iey'
-    >>> enigma31.peg_positions
-    ([8], [21], [23])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'wenpbqrouxlkychdfgzvitajms'
-
-
-    >>> enigma31.set_wheels('i', 'z', 'd')
-    >>> enigma31.encipher('verylongtestmessagewithanextrabitofmessageforgoodmeasure')
-    'apocwtjuikurcfivlozvhffkoacxufcekthcvodfqpxdjqyckdozlqki'
-    >>> enigma31.wheel_positions
-    (4, 9, 10)
-    >>> cat(enigma31.wheel_positions_l)
-    'jch'
-    >>> enigma31.peg_positions
-    ([7], [23], [14])
-    >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
-    'mopnigfuesqwadbcktjrhylzvx'
-
-    >>> enigma31.set_wheels('i', 'z', 'd')
-    >>> enigma31.decipher('apocwtjuikurcfivlozvhffkoacxufcekthcvodfqpxdjqyckdozlqki')
-    'verylongtestmessagewithanextrabitofmessageforgoodmeasure'
     """
     def __init__(self, reflector_spec,
     """
     def __init__(self, reflector_spec,
-                 left_wheel_spec, left_wheel_pegs,
-                 middle_wheel_spec, middle_wheel_pegs,
-                 right_wheel_spec, right_wheel_pegs,
+                 left_wheel_spec, left_wheel_notches,
+                 middle_wheel_spec, middle_wheel_notches,
+                 right_wheel_spec, right_wheel_notches,
                  left_ring_setting, middle_ring_setting, right_ring_setting,
                  plugboard_setting):
         self.reflector = Reflector(reflector_spec)
                  left_ring_setting, middle_ring_setting, right_ring_setting,
                  plugboard_setting):
         self.reflector = Reflector(reflector_spec)
-        self.left_wheel = Wheel(left_wheel_spec, left_wheel_pegs, ring_setting=left_ring_setting)
-        self.middle_wheel = Wheel(middle_wheel_spec, middle_wheel_pegs, ring_setting=middle_ring_setting)
-        self.right_wheel = Wheel(right_wheel_spec, right_wheel_pegs, ring_setting=right_ring_setting)
+        self.left_wheel = Wheel(left_wheel_spec, left_wheel_notches, ring_setting=left_ring_setting)
+        self.middle_wheel = Wheel(middle_wheel_spec, middle_wheel_notches, ring_setting=middle_ring_setting)
+        self.right_wheel = Wheel(right_wheel_spec, right_wheel_notches, ring_setting=right_ring_setting)
         self.plugboard = Plugboard(plugboard_setting)
         
     def __getattribute__(self,name):
         self.plugboard = Plugboard(plugboard_setting)
         
     def __getattribute__(self,name):
@@ -943,8 +289,8 @@ class Enigma(object):
             return self.left_wheel.position, self.middle_wheel.position, self.right_wheel.position 
         elif name=='wheel_positions_l':
             return self.left_wheel.position_l, self.middle_wheel.position_l, self.right_wheel.position_l 
             return self.left_wheel.position, self.middle_wheel.position, self.right_wheel.position 
         elif name=='wheel_positions_l':
             return self.left_wheel.position_l, self.middle_wheel.position_l, self.right_wheel.position_l 
-        elif name=='peg_positions':
-            return self.left_wheel.peg_positions, self.middle_wheel.peg_positions, self.right_wheel.peg_positions
+        elif name=='notch_positions':
+            return self.left_wheel.notch_positions, self.middle_wheel.notch_positions, self.right_wheel.notch_positions
         else:
             return object.__getattribute__(self, name)
 
         else:
             return object.__getattribute__(self, name)
 
@@ -968,9 +314,9 @@ class Enigma(object):
     def advance(self):
         advance_middle = False
         advance_left = False
     def advance(self):
         advance_middle = False
         advance_left = False
-        if 0 in self.right_wheel.peg_positions:
+        if 0 in self.right_wheel.notch_positions:
             advance_middle = True
             advance_middle = True
-        if 0 in self.middle_wheel.peg_positions:
+        if 0 in self.middle_wheel.notch_positions:
             advance_left = True
             advance_middle = True
         self.right_wheel.advance()
             advance_left = True
             advance_middle = True
         self.right_wheel.advance()
@@ -995,191 +341,11 @@ class Enigma(object):
 #     print('enigma.advance()')
 #     print("assert(enigma.wheel_positions == {})".format(enigma.wheel_positions))
 #     print("assert(cat(enigma.wheel_positions_l) == '{}')".format(cat(enigma.wheel_positions_l)))
 #     print('enigma.advance()')
 #     print("assert(enigma.wheel_positions == {})".format(enigma.wheel_positions))
 #     print("assert(cat(enigma.wheel_positions_l) == '{}')".format(cat(enigma.wheel_positions_l)))
-#     print("assert(enigma.peg_positions == {})".format(enigma.peg_positions))
+#     print("assert(enigma.notch_positions == {})".format(enigma.notch_positions))
 #     print("assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == '{}')".format(cat(enigma.lookup(l) for l in string.ascii_lowercase)))
 #     print()
 
 
 #     print("assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == '{}')".format(cat(enigma.lookup(l) for l in string.ascii_lowercase)))
 #     print()
 
 
-##################################
-# # Bombe
-##################################
-
-Signal = collections.namedtuple('Signal', ['bank', 'wire'])
-Connection = collections.namedtuple('Connection', ['banks', 'scrambler'])
-MenuItem = collections.namedtuple('MenuIem', ['before', 'after', 'number'])
-
-
-class Scrambler(object):
-    def __init__(self, wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec,
-                 wheel1_pos='a', wheel2_pos='a', wheel3_pos='a'):
-        self.wheel1 = SimpleWheel(wheel1_spec, position=wheel1_pos)
-        self.wheel2 = SimpleWheel(wheel2_spec, position=wheel2_pos)
-        self.wheel3 = SimpleWheel(wheel3_spec, position=wheel3_pos)
-        self.reflector = Reflector(reflector_spec)
-    
-    def __getattribute__(self, name):
-        if name=='wheel_positions':
-            return self.wheel1.position, self.wheel2.position, self.wheel3.position 
-        elif name=='wheel_positions_l':
-            return self.wheel1.position_l, self.wheel2.position_l, self.wheel3.position_l 
-        else:
-            return object.__getattribute__(self, name)
-    
-    def advance(self, wheel1=False, wheel2=False, wheel3=True):
-        if wheel1: self.wheel1.advance()
-        if wheel2: self.wheel2.advance()
-        if wheel3: self.wheel3.advance()
-            
-    def lookup(self, letter):
-        a = self.wheel3.forward(letter)
-        b = self.wheel2.forward(a)
-        c = self.wheel1.forward(b)
-        d = self.reflector.forward(c)
-        e = self.wheel1.backward(d)
-        f = self.wheel2.backward(e)
-        g = self.wheel3.backward(f)
-        return g
-    
-    def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos):
-        self.wheel1.set_position(wheel1_pos)
-        self.wheel2.set_position(wheel2_pos)
-        self.wheel3.set_position(wheel3_pos)      
-
-
-class Bombe(object):
-    def __init__(self, wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec,
-                menu=None, start_signal=None, use_diagonal_board=True, 
-                verify_plugboard=True):
-        self.connections = []
-        self.wheel1_spec = wheel1_spec
-        self.wheel2_spec = wheel2_spec
-        self.wheel3_spec = wheel3_spec
-        self.reflector_spec = reflector_spec
-        if menu:
-            self.read_menu(menu)
-        if start_signal:
-            self.test_start = start_signal
-        self.use_diagonal_board = use_diagonal_board
-        self.verify_plugboard = verify_plugboard
-        
-    def __getattribute__(self, name):
-        if name=='wheel_positions':
-            return self.connections[0].scrambler.wheel_positions
-        elif name=='wheel_positions_l':
-            return self.connections[0].scrambler.wheel_positions_l
-        else:
-            return object.__getattribute__(self, name)
-        
-    def __call__(self, start_positions):
-        return start_positions, self.test(initial_signal=self.test_start,
-            start_positions=start_positions, 
-            use_diagonal_board=self.use_diagonal_board,
-            verify_plugboard=self.verify_plugboard)
-        
-    def add_connection(self, bank_before, bank_after, scrambler):
-        self.connections += [Connection([bank_before, bank_after], scrambler)]
-        
-    def read_menu(self, menu):
-        for item in menu:
-            scrambler = Scrambler(self.wheel1_spec, self.wheel2_spec, self.wheel3_spec,
-                                  self.reflector_spec,
-                                  wheel3_pos=unpos(item.number - 1))
-            self.add_connection(item.before, item.after, scrambler)
-        most_common_letter = (collections.Counter(m.before for m in menu) + \
-                              collections.Counter(m.after for m in menu)).most_common(1)[0][0]
-        self.test_start = Signal(most_common_letter, most_common_letter)
-        
-    def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos):
-        for i, c in enumerate(self.connections):
-            c.scrambler.set_positions(wheel1_pos, wheel2_pos, unpos(pos(wheel3_pos) + i))
-    
-    def test(self, initial_signal=None, start_positions=None, use_diagonal_board=True,
-            verify_plugboard=True):
-        self.banks = {label: 
-                      dict(zip(string.ascii_lowercase, [False]*len(string.ascii_lowercase)))
-                      for label in string.ascii_lowercase}
-        if start_positions:
-            self.set_positions(*start_positions)
-        if not initial_signal:
-            initial_signal = self.test_start
-        self.pending = [initial_signal]
-        self.propagate(use_diagonal_board)
-        live_wire_count = len([self.banks[self.test_start.bank][w] 
-                    for w in self.banks[self.test_start.bank] 
-                    if self.banks[self.test_start.bank][w]])
-        if live_wire_count < 26:
-            if verify_plugboard:
-                possibles = self.possible_plugboards()
-                return all(s0.isdisjoint(s1) for s0 in possibles for s1 in possibles if s0 != s1)
-            else:
-                return True
-        else:
-            return False
-        
-    def propagate(self, use_diagonal_board):
-        while self.pending:
-            current = self.pending[0]
-            # print("processing", current)
-            self.pending = self.pending[1:]
-            if not self.banks[current.bank][current.wire]:
-                self.banks[current.bank][current.wire] = True
-                if use_diagonal_board:
-                    self.pending += [Signal(current.wire, current.bank)]
-                for c in self.connections:
-                    if current.bank in c.banks:
-                        other_bank = [b for b in c.banks if b != current.bank][0]
-                        other_wire = c.scrambler.lookup(current.wire)
-                        # print("  adding", other_bank, other_wire, "because", c.banks)
-                        self.pending += [Signal(other_bank, other_wire)]
-    
-    def run(self, run_start=None, wheel1_pos='a', wheel2_pos='a', wheel3_pos='a', use_diagonal_board=True):
-        if not run_start:
-            run_start = self.test_start
-        self.solutions = []
-        self.set_positions(wheel1_pos, wheel2_pos, wheel3_pos)
-        for run_index in range(26*26*26):
-            if self.test(initial_signal=run_start, use_diagonal_board=use_diagonal_board):
-                self.solutions += [self.connections[0].scrambler.wheel_positions_l]
-            advance3 = True
-            advance2 = False
-            advance1 = False
-            if (run_index + 1) % 26 == 0: advance2 = True
-            if (run_index + 1) % (26*26) == 0: advance1 = True
-            for c in self.connections:
-                c.scrambler.advance(advance1, advance2, advance3)
-        return self.solutions
-    
-    def possible_plugboards(self):
-        possibles = set()
-        for b in self.banks:
-            active = [w for w in self.banks[b] if self.banks[b][w]]
-            inactive = [w for w in self.banks[b] if not self.banks[b][w]]
-            if len(active) == 1:
-                possibles = possibles.union({frozenset((b, active[0]))})
-            if len(inactive) == 1:
-                possibles = possibles.union({frozenset((b, inactive[0]))})
-        return possibles
-
-
-def make_menu(plaintext, ciphertext):
-    return [MenuItem(p, c, i+1) 
-            for i, (p, c) in enumerate(zip(plaintext, ciphertext))]
-
-
-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]]
-
-
 if __name__ == "__main__":
     import doctest
     # doctest.testmod(extraglobs={'lt': LetterTransformer(1, 'a')})
 if __name__ == "__main__":
     import doctest
     # doctest.testmod(extraglobs={'lt': LetterTransformer(1, 'a')})