Mercurial > public > enigma
changeset 16:2ce2e8c5a5be
Decided to get rid of the numeric rotor display for now.
Updated comments to make distinction between Wehrmacht & Heer.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Sun, 27 May 2012 19:41:59 -0500 |
parents | 7ea7da689dbd |
children | ce3840115214 |
files | enigma/__init__.py enigma/machine.py enigma/plugboard.py enigma/rotors/data.py enigma/rotors/factory.py enigma/rotors/rotor.py enigma/tests/test_rotor.py |
diffstat | 7 files changed, 52 insertions(+), 68 deletions(-) [+] |
line wrap: on
line diff
--- a/enigma/__init__.py Sun May 27 18:51:40 2012 -0500 +++ b/enigma/__init__.py Sun May 27 19:41:59 2012 -0500 @@ -2,11 +2,11 @@ # This file is part of Py-Enigma, the Enigma Machine simulation. # Py-Enigma is released under the MIT License (see License.txt). -"""This package is a simulation of the wartime Enigma Machines used by the -German military branches. These include the army (Wehrmacht), air force -(Luftwaffe), and navy (Kriegsmarine). For our purposes, the Wehrmacht and -Luftwaffe versions are identical. The Kriegsmarine models M3 and M4 can be -simulated by configuring an EnigmaMachine object with the suitable rotors. +"""This package is a simulation of the Enigma Machines used by the German armed +forces (Wehrmacht) during World War II. These include the army (Heer), air force +(Luftwaffe), and navy (Kriegsmarine). For our purposes, the Heer and Luftwaffe +versions are identical. The Kriegsmarine models M3 and M4 can be simulated by +configuring an EnigmaMachine object with the suitable rotors and reflectors. For a list of the rotors and reflectors we simulate, see the module rotors.data.
--- a/enigma/machine.py Sun May 27 18:51:40 2012 -0500 +++ b/enigma/machine.py Sun May 27 19:41:59 2012 -0500 @@ -31,7 +31,7 @@ the left-most rotor, and the last rotor is the right-most (from the operator's perspective sitting at the machine). - reflector - a rotor object to represent the reflector + reflector - a rotor object to represent the reflector (UKW) plugboard - a plugboard object to represent the state of the plugboard
--- a/enigma/plugboard.py Sun May 27 18:51:40 2012 -0500 +++ b/enigma/plugboard.py Sun May 27 19:41:59 2012 -0500 @@ -2,14 +2,17 @@ # This file is part of Py-Enigma, the Enigma Machine simulation. # Py-Enigma is released under the MIT License (see License.txt). -"""Contains the Plugboard class for simulating the plugboard component.""" +"""Contains the Plugboard class for simulating the plugboard (Steckerbrett) +component of the Enigma Machine. + +""" import collections from itertools import chain import string -# On Wehrmacht models, the plugs are labeled with upper case letters +# On Heer & Luftwaffe (?) models, the plugs are labeled with upper case letters WEHRMACHT_LABELS = string.ascii_uppercase # The number of plugboard cables supplied with a machine: @@ -23,9 +26,9 @@ class Plugboard: """The plugboard allows the operator to swap letters before and after the entry wheel. This is accomplished by connecting cables between pairs of - plugs that are marked with letters (Wehrmacht) or numbers (Kriegsmarine). - Ten cables were issued with each machine; thus up to 10 of these swappings - could be used as part of a machine setup. + plugs that are marked with letters (Heer & Luftwaffe models) or numbers + (Kriegsmarine). Ten cables were issued with each machine; thus up to 10 of + these swappings could be used as part of a machine setup. Each cable swaps both the input and output signals. Thus if A is connected to B, A crosses to B in the keyboard to entry wheel direction and also in @@ -37,7 +40,7 @@ pairs, or None. A value of None or an empty list/tuple indicates no plugboard - connections are to be used (a straight mapping). + connections are to be used (i.e. a straight mapping). Otherwise wiring_pairs must be an iterable of integer pairs, where each integer is between 0-25, inclusive. At most 10 such pairs can be @@ -80,9 +83,9 @@ """Configure the plugboard according to a settings string as you may find on a key sheet. - Two syntaxes are supported, the Wehrmacht and Kriegsmarine styles: + Two syntaxes are supported, the Heer/Luftwaffe and Kriegsmarine styles: - In the Wehrmacht syntax, the settings are given as a string of + In the Heer syntax, the settings are given as a string of alphabetic pairs. For example: 'PO ML IU KJ NH YT GB VF RE DC' In the Kriegsmarine syntax, the settings are given as a string of number @@ -115,7 +118,7 @@ wiring_pairs.append((m - 1, n - 1)) else: - # Wehrmacht syntax + # Heer/Luftwaffe syntax pairs = settings.upper().split() for p in pairs:
--- a/enigma/rotors/data.py Sun May 27 18:51:40 2012 -0500 +++ b/enigma/rotors/data.py Sun May 27 19:41:59 2012 -0500 @@ -11,12 +11,12 @@ # website: "Techical Details of the Engigma Machine" # http://users.telenet.be/d.rijmenants/en/enigmatech.htm # -# Rotors I-V were used by the Wehrmacht, Luftwaffe, and Kriegsmarine. The +# Rotors I-V were used by the Heer, Luftwaffe, and Kriegsmarine. The # Kriegsmarine added rotors VI-VIII to the M3 model, and added Beta & Gamma to # the M4 model (used with thin reflectors only). Note that Beta & Gamma rotors # did not rotate. # -# The Wehrmacht, Luftwaffe, & Kriegsmarine M3 machines used reflectors B & C, +# The Heer, Luftwaffe, & Kriegsmarine M3 machines used reflectors B & C, # while the Kriegsmarine M4 used thin reflectors B & C. #
--- a/enigma/rotors/factory.py Sun May 27 18:51:40 2012 -0500 +++ b/enigma/rotors/factory.py Sun May 27 19:41:59 2012 -0500 @@ -9,13 +9,12 @@ from .data import ROTORS, REFLECTORS -def create_rotor(model, ring_setting=0, alpha_labels=True): +def create_rotor(model, ring_setting=0): """Factory function to create and return a rotor of the given model name.""" if model in ROTORS: data = ROTORS[model] - return Rotor(model, data['wiring'], ring_setting, data['stepping'], - alpha_labels) + return Rotor(model, data['wiring'], ring_setting, data['stepping']) raise RotorError("Unknown rotor type: %s" % model)
--- a/enigma/rotors/rotor.py Sun May 27 18:51:40 2012 -0500 +++ b/enigma/rotors/rotor.py Sun May 27 19:41:59 2012 -0500 @@ -11,11 +11,10 @@ ALPHA_LABELS = string.ascii_uppercase -NUMERIC_LABELS = ['{:02d}'.format(n) for n in range(1, 27)] # In specifying a wiring for a rotor, every letter must be included exactly # once. This variable helps us enforce that: -WIRING_FREQ_SET = set((letter, 1) for letter in string.ascii_uppercase) +WIRING_FREQ_SET = set((letter, 1) for letter in ALPHA_LABELS) class Rotor: @@ -36,12 +35,12 @@ mapped to pin 0 on an alphabetic ring). A ring setting of 1 means the letter "B" is mapped to pin 0. - Each rotor can be in one of 26 positions on the axle, with position 0 where - pin/contact 0 is being indicated in the operator window. The rotor rotates - towards the operator by mechanical means during normal operation as keys are - being pressed during data entry. Position 1 is thus defined to be one step - from position 0. Likewise, position 25 is the last position before another - step returns it to position 0, completing 1 trip around the axle. + Each rotor can be in one of 26 positions on the spindle, with position 0 + where pin/contact 0 is being indicated in the operator window. The rotor + rotates towards the operator by mechanical means during normal operation as + keys are being pressed during data entry. Position 1 is thus defined to be + one step from position 0. Likewise, position 25 is the last position before + another step returns it to position 0, completing 1 trip around the spindle. Finally, a rotor has a "stepping" or "turnover" parameter. Physically this is implemented by putting a notch on the alphabet ring and it controls when @@ -60,8 +59,7 @@ """ - def __init__(self, model_name, wiring, ring_setting=0, stepping=None, - alpha_labels=True): + def __init__(self, model_name, wiring, ring_setting=0, stepping=None): """Establish rotor characteristics: model_name - e.g. "I", "II", "III", "Beta", "Gamma" @@ -69,8 +67,8 @@ wiring - this should be a string of 26 alphabetic characters that represents the internal wiring transformation of the signal as it enters from the right side. This is the format used in various online - resources. For example, for the Wehrmacht & Luftwaffe Enigma type I - rotor the mapping is "EKMFLGDQVZNTOWYHXUSPAIBRCJ". + resources. For example, for the Wehrmacht Enigma type I rotor the + mapping is "EKMFLGDQVZNTOWYHXUSPAIBRCJ". ring_setting - this should be an integer from 0-25, inclusive, which indicates the Ringstellung. A value of 0 means there is no offset; e.g. @@ -85,17 +83,19 @@ be used, e.g. "ZM". Another way to think of this parameter is that when a character in the stepping string is visible in the operator window, a notch is lined up with the pawl on the left side of the rotor. This - will allow the pawl to push up on the rotor to the left when the next - key is depressed. + will allow the pawl to push up on the rotor *and* the rotor to the left + when the next key is depressed. - alpha_labels - when True, the letters A-Z are used for the rotor ring - labels. If False, numeric string labels (01-26) are used. + Note that for purposes of simulation, our rotors will always use + alphabetic labels A-Z. In reality, the Heer & Luftwaffe devices used + numbers 01-26, and Kriegsmarine devices used A-Z. Our usage of A-Z is + simply for simulation convenience. In the future we may allow either + display. """ self.name = model_name self.wiring_str = wiring.upper() self.ring_setting = ring_setting - self.alpha_labels = alpha_labels self.pos = 0 self.rotations = 0 @@ -105,7 +105,7 @@ # check wiring format; must contain A-Z for c in self.wiring_str: - if c not in string.ascii_uppercase: + if c not in ALPHA_LABELS: raise RotorError("invalid wiring: %s" % wiring) # check wiring format; ensure every letter appears exactly once @@ -126,7 +126,7 @@ self.exit_map[v] = i # configure ring labels - self.ring_labels = ALPHA_LABELS if alpha_labels else NUMERIC_LABELS + self.ring_labels = ALPHA_LABELS # for mapping window display values to indices self.display_map = dict(zip(self.ring_labels, range(26))) @@ -149,13 +149,9 @@ rotates the pins and contacts accordingly. A value of 'A' for example puts the rotor in position 0, assuming an - internal ring setting of 0 and alphabetic labels. + internal ring setting of 0. - If the rotor is using alphabetic ring labels, val must be a string in - 'A' - 'Z'. - - If the rotor is not using alphabetic ring labels, val must be a string - of the form '01' - '26'. + The parameter val must be a string in 'A' - 'Z'. Setting the display resets the internal rotation counter to 0.
--- a/enigma/tests/test_rotor.py Sun May 27 18:51:40 2012 -0500 +++ b/enigma/tests/test_rotor.py Sun May 27 19:41:59 2012 -0500 @@ -8,7 +8,7 @@ import collections import string -from ..rotors.rotor import Rotor, ALPHA_LABELS, NUMERIC_LABELS +from ..rotors.rotor import Rotor, ALPHA_LABELS from ..rotors import RotorError @@ -39,41 +39,27 @@ def test_bad_stepping(self): - for alpha in True, False: - self.assertRaises(RotorError, Rotor, 'I', WIRING, - alpha_labels=alpha, stepping="0") - self.assertRaises(RotorError, Rotor, 'I', WIRING, - alpha_labels=alpha, stepping="A0") - self.assertRaises(RotorError, Rotor, 'I', WIRING, - alpha_labels=alpha, stepping=[1]) - self.assertRaises(RotorError, Rotor, 'I', WIRING, - alpha_labels=alpha, stepping=['A', '%', '14']) - self.assertRaises(RotorError, Rotor, 'I', WIRING, - alpha_labels=alpha, stepping=('A', '%', '14')) + self.assertRaises(RotorError, Rotor, 'I', WIRING, stepping="0") + self.assertRaises(RotorError, Rotor, 'I', WIRING, stepping="A0") + self.assertRaises(RotorError, Rotor, 'I', WIRING, stepping=[1]) + self.assertRaises(RotorError, Rotor, 'I', WIRING, stepping=['A', '%', '14']) + self.assertRaises(RotorError, Rotor, 'I', WIRING, stepping=('A', '%', '14')) - def test_alpha_display(self): + def test_display(self): for r in range(26): - rotor = Rotor('I', WIRING, ring_setting=r, alpha_labels=True) + rotor = Rotor('I', WIRING, ring_setting=r) for s in ALPHA_LABELS: rotor.set_display(s) self.assertEqual(s, rotor.get_display()) - def test_numeric_display(self): - - for r in range(26): - rotor = Rotor('I', WIRING, ring_setting=r, alpha_labels=False) - for s in NUMERIC_LABELS: - rotor.set_display(s) - self.assertEqual(s, rotor.get_display()) - def test_wiring(self): """Loop through all ring settings & rotor positions and test the wiring. """ for r in range(26): - rotor = Rotor('I', WIRING, ring_setting=r, alpha_labels=True) + rotor = Rotor('I', WIRING, ring_setting=r) for n, d in enumerate(ALPHA_LABELS): rotor.set_display(d)