patternpythonMinor
Enigma Machine Simulation in Python
Viewed 0 times
machinepythonenigmasimulation
Problem
Background and Example
This code simulates the Enigma machine, minus the plugboard. Here's some test code that illustrates how the machine's construction and use:
Rotors
The
```
class Rotor:
"""
Models a 'rotor' in an Enigma machine
Rotor("BCDA", 1) means that A->B, B->C, C->D, D->A and the rotor has been
rotated once from ABCD (the clear text character 'B' is facing the user)
Args:
mappings (string) encipherings for the machine's alphabet.
offset (int) the starting position of the rotor
"""
def __init__(self, mappings, offset=0):
self.initial_offset = offset
self.reset()
self.forward_mappings = dict(zip(self.alphabet, mappings))
self.reverse_mappings = dict(zip(mappings, self.alphabet))
def reset(self):
"""
Helper to re-initialize the rotor to its initial configuration
Returns: void
"""
self.alphabet = Machine.ALPHABET
self.rotate(self.initial_offset)
self.rotations = 1
def rotate(self, offset=1):
"""
Rotates the rotor the given number of characters
Args: offset (int) how many turns to make
Returns: void
"""
for _ in range(offset):
self.alphabet = self.alphabet[1:] + self.alphabet[0]
self.rotations = offset
def encipher(self, character):
"""
Gets the cipher text mapping of a plain text character
This code simulates the Enigma machine, minus the plugboard. Here's some test code that illustrates how the machine's construction and use:
>>> r1 = Rotor("VEADTQRWUFZNLHYPXOGKJIMCSB", 1)
>>> r2 = Rotor("WNYPVJXTOAMQIZKSRFUHGCEDBL", 2)
>>> r3 = Rotor("DJYPKQNOZLMGIHFETRVCBXSWAU", 3)
>>> reflector = Reflector("EJMZALYXVBWFCRQUONTSPIKHGD")
>>> machine = Machine([r1, r2, r3], reflector)
>>> x = machine.encipher("ATTACK AT DAWN")
>>> machine.decipher(x)
'ATTACK AT DAWN'Rotors
The
Rotor class is pretty simple. A Rotor knows how to rotate itself, and provides methods for navigating connections with the adjacent circuits through the encipher and decipher methods.```
class Rotor:
"""
Models a 'rotor' in an Enigma machine
Rotor("BCDA", 1) means that A->B, B->C, C->D, D->A and the rotor has been
rotated once from ABCD (the clear text character 'B' is facing the user)
Args:
mappings (string) encipherings for the machine's alphabet.
offset (int) the starting position of the rotor
"""
def __init__(self, mappings, offset=0):
self.initial_offset = offset
self.reset()
self.forward_mappings = dict(zip(self.alphabet, mappings))
self.reverse_mappings = dict(zip(mappings, self.alphabet))
def reset(self):
"""
Helper to re-initialize the rotor to its initial configuration
Returns: void
"""
self.alphabet = Machine.ALPHABET
self.rotate(self.initial_offset)
self.rotations = 1
def rotate(self, offset=1):
"""
Rotates the rotor the given number of characters
Args: offset (int) how many turns to make
Returns: void
"""
for _ in range(offset):
self.alphabet = self.alphabet[1:] + self.alphabet[0]
self.rotations = offset
def encipher(self, character):
"""
Gets the cipher text mapping of a plain text character
Solution
Your
This saves having to do a costly list addition
Different commonly used ASCII character classes are included in the
It is better to ask forgiveness than permission. One place where you can use this is the check whether the character to encode is in the character set. Just use
This way you avoid having to go through the list more often than necessary (the edge case
In the same function you have the two blocks
I would also add the reflector rotating into a method:
You can then use these like this:
I made
Rotor.rotate can be simplified to def rotate(self, offset=1):
self.rotations = offset
self.alphabet = self.alphabet[offset:] + self.alphabet[:offset]This saves having to do a costly list addition
offset times.Different commonly used ASCII character classes are included in the
string module. You can use string.ascii_uppercase for the uppercase alphabet.join can take a generator expression directly, so you can get rid of one set of parenthesis in Machine.encipher:return "".join(self.encipher_character(x) for x in text.upper())It is better to ask forgiveness than permission. One place where you can use this is the check whether the character to encode is in the character set. Just use
try..except in Machine.encipher_character:# compute the contact position of the first rotor and machine's input
try:
contact_index = Machine.ALPHABET.index(x)
except ValueError:
return xThis way you avoid having to go through the list more often than necessary (the edge case
Z will iterate through the alphabet twice, once to see if it is there and once to actually get the index).In the same function you have the two blocks
# propagate contact right and # propagate contact left. The comments already suggest that this would be a perfect place to make them a function. They also only differ by whether or not the rotors are traversed in reverse or not and whether to use rotor.encipher or rotor.decipher. Make a method Machine.rotate_rotors:def rotate_rotors(self, left=False):
"""propagate contact right or left"""
iter_direction = reversed if left else iter
for rotor in iter_direction(self.rotors):
contact_letter = rotor.alphabet[self.contact_index]
func = rotor.decipher if left else rotor.encipher
self.contact_index = rotor.alphabet.index(func(contact_letter))I would also add the reflector rotating into a method:
def rotate_reflector(self):
"""reflect and compute the starting contact position with the right rotor"""
contact_letter = Machine.ALPHABET[self.contact_index]
x = self.reflector.reflect(contact_letter)
self.contact_index = Machine.ALPHABET.index(x)You can then use these like this:
self.contact_index = Machine.ALPHABET.index(x)
self.rotate_rotors()
self.rotate_reflector()
self.rotate_rotors(left=True)I made
contact_index a property of the class now, this way we don't have to pass in the contact_index every time and return it. I also made your comments into docstrings.Code Snippets
def rotate(self, offset=1):
self.rotations = offset
self.alphabet = self.alphabet[offset:] + self.alphabet[:offset]return "".join(self.encipher_character(x) for x in text.upper())# compute the contact position of the first rotor and machine's input
try:
contact_index = Machine.ALPHABET.index(x)
except ValueError:
return xdef rotate_rotors(self, left=False):
"""propagate contact right or left"""
iter_direction = reversed if left else iter
for rotor in iter_direction(self.rotors):
contact_letter = rotor.alphabet[self.contact_index]
func = rotor.decipher if left else rotor.encipher
self.contact_index = rotor.alphabet.index(func(contact_letter))def rotate_reflector(self):
"""reflect and compute the starting contact position with the right rotor"""
contact_letter = Machine.ALPHABET[self.contact_index]
x = self.reflector.reflect(contact_letter)
self.contact_index = Machine.ALPHABET.index(x)Context
StackExchange Code Review Q#139475, answer score: 4
Revisions (0)
No revisions yet.