EnigmaMachine.py

# ===================================================================
# test the enigma machine simulation with and without rotor advance
# ===================================================================
# the user can advance the rotors with the "advance" command.
# -------------------------------------------------------------------
# Note: because this is software, we do some special "stuff" with
# characters not in the enigma machine's alphabet [A-Z]. see the
# code/comments.
# ===================================================================

from EnigmaConfig    import EnigmaConfig
from EnigmaRotor     import EnigmaRotor
import EnigmaUtils
import user_interface as ui
import copy
import re
import sys


class EnigmaMachine:

    # ---------------------------------------------------------------
    # ---- initialize object
    # ---------------------------------------------------------------
    def __init__(self,configfile):

        # ---- enigma machine configuration

        self.config = EnigmaConfig(configfile)

        # ---- enigma machine alphabet

        self.abc    = self.config.abc
        self.abcidx = self.config.abcidx
        self.abclen = self.config.abclen
        self.abcunk = self.config.abcunk

        self.rotorkeys = self.config.rotorkeys

        # ---- left rotor, middle rotor, right rotor,
        # ---- reflector, plugboard

        self.lr = EnigmaRotor(self.config,
                self.config.default_rotor_names[0],
                self.config.default_rotor_starts[0])
        self.mr = EnigmaRotor(self.config,
                self.config.default_rotor_names[1],
                self.config.default_rotor_starts[1])
        self.rr = EnigmaRotor(self.config,
                self.config.default_rotor_names[2],
                self.config.default_rotor_starts[2])
        self.rf = copy.deepcopy(self.config.reflst)
        self.pg = copy.deepcopy(self.config.pglst)

        # ---- automatic advance rotors

        self.auto_advance_rotors = True 

        # ---- substitution debug messages

        self.substitution_debug = False

    # ---------------------------------------------------------------
    # ---- display internal state of enigma machine
    # ---------------------------------------------------------------
    def display_internal_state(self):
        print()
        print('                    -- Left Rotor --- ' +
              '-- Middle Rotor - -- Right Rotor --')
        print('plugboard reflector  ' +
              'l-to-r   r-to-l   l-to-r   r-to-l   l-to-r   r-to-l')
        print('--------- --------- -------- -------- ' +
              '-------- -------- -------- --------')
        for i in range(self.abclen):
             print(f'{i:>2} -> {self.pg[i]:<2}  ' +
                   f'{i:>2} -> {self.rf[i]:<2}  ' +
                   f'{i:>2} -> {self.lr.ltor[i]:<2} ' +
                   f'{self.lr.rtol[i]:>2} <- {i:<2} ' +
                   f'{i:>2} -> {self.mr.ltor[i]:<2} ' +
                   f'{self.mr.rtol[i]:>2} <- {i:<2} ' +
                   f'{i:>2} -> {self.rr.ltor[i]:<2} ' +
                   f'{self.rr.rtol[i]:>2} <- {i:<2}')
        print(f'---- auto advance rotors ({self.auto_advance_rotors})')
        print(f'---- right  start {self.rr.rotor_start:<2} ' +
              f'rotor {self.rr.rotor_name:<4} ' +
              f'count {self.rr.rotor_count}')
        print(f'---- middle start {self.mr.rotor_start:<2} ' +
              f'rotor {self.mr.rotor_name:<4} ' +
              f'count {self.mr.rotor_count}')
        print(f'---  left   start {self.lr.rotor_start:<2} ' +
              f'rotor {self.lr.rotor_name:<4} ' +
              f'count {self.lr.rotor_count}')
        return

    # ---------------------------------------------------------------
    # ---- display rotor stats of enigma machine
    # ---------------------------------------------------------------
    def display_rotor_stats(self):
        print()
        print(f'auto advance rotors ({self.auto_advance_rotors})')
        print(f'right  start {self.rr.rotor_start:<2} ' +
              f'rotor {self.rr.rotor_name:<4} ' +
              f'count {self.rr.rotor_count}')
        print(f'middle start {self.mr.rotor_start:<2} ' +
              f'rotor {self.mr.rotor_name:<4} ' +
              f'count {self.mr.rotor_count}')
        print(f'left   start {self.lr.rotor_start:<2} ' +
              f'rotor {self.lr.rotor_name:<4} ' +
              f'count {self.lr.rotor_count}')
        return

    # ---------------------------------------------------------------
    # ---- convert character to index
    # ---------------------------------------------------------------
    def char_to_idx(self,c):
        if c in self.abc:
            for i in range(self.abclen):
                if self.abc[i] == c:
                    return i
        return -1

    # ---------------------------------------------------------------
    # ---- convert index to character
    # ---------------------------------------------------------------
    def idx_to_char(self,idx):
         return self.abc[idx]

    # ---------------------------------------------------------------
    # ---- advance the rotors one position
    # ---- advance each rotor until it makes a complete revolution
    # ----   then start over again
    # ---- note: tf is a true/false flag
    # ----       True  have completed a full rotation
    # ----       False have not completed a complete full rotation
    # ---------------------------------------------------------------
    def advance_rotors(self):

        tf = self.advance(self.rr)           # right rotor
        if tf:
            tf = self.advance(self.mr)       # middle rotor
            if tf:
                tf = self.advance(self.lr)   # left rotor
        return

    # ---------------------------------------------------------------
    # ---- advance a rotor
    # ---- return:
    # ----     True  have completed a full rotation
    # ----     False have not completed a complete full rotation
    # ---------------------------------------------------------------
    def advance(self,r):

        # ---- advance rotor's rtol list

        rtollen = len(r.rtol)

        tmp = r.rtol[0]

        for i in range(rtollen-1):
            r.rtol[i] = (r.rtol[i+1] - 1) % rtollen

        r.rtol[-1] = (tmp-1) % rtollen

        # ---- sync rotor's ltor list with its rotl list

        for i in range(rtollen):
            r.ltor[r.rtol[i]] = i  

        # ---- check for a complete rotation of the rotor
        # ---- (end of list)

        r.rotor_count += 1
        if not r.rotor_count < rtollen:
            r.rotor_count = 0
            return True
        return False

    # ---------------------------------------------------------------
    # ---- reset the internal state to the initial configuration
    # ---------------------------------------------------------------
    def reset(self):

        self.lr = EnigmaRotor(self.config,
                self.config.default_rotor_names[0],
                self.config.default_rotor_starts[0])
        self.mr = EnigmaRotor(self.config,
                self.config.default_rotor_names[1],
                self.config.default_rotor_starts[1])
        self.rr = EnigmaRotor(self.config,
                self.config.default_rotor_names[2],
                self.config.default_rotor_starts[2])

        self.lr.rotor_count = 0
        self.mr.rotor_count = 0
        self.rr.rotor_count = 0

        return

    # ---------------------------------------------------------------
    # ---- set rotor configuration
    # ---- the initial rotor configuration is still available
    # ---- using reset_initial_state function 
    # ---------------------------------------------------------------
    def set(self,names,starts):

        # --- verify input lists

        if len(names)  != 3 or len(starts) != 3:
               print()
               print('internal error - bad set rotor comfiguration ' +
                     'names or starts list size')
               print(f'({names}) ({starts})')
               print()
               sys.exit()

        for i in range(3):
            if names[i] not in self.rotorkeys: 
               print()
               print('internal error - bad set rotor comfiguration ' +
                     'rotor name')
               print(f'({names})')
               print()
               sys.exit()

        for s in range(3):
            if s< 0 or s >self.config.abclen-1:
               print()
               print('internal error - bad set rotor comfiguration ' +
                     'start position')
               print(f'({starts})')
               print()
               sys.exit()

        # ---- set rotor configuration

        self.lr = EnigmaRotor(self.config,names[0],starts[0])
        self.mr = EnigmaRotor(self.config,names[1],starts[1])
        self.rr = EnigmaRotor(self.config,names[2],starts[2])

        self.lr.rotor_count = 0
        self.mr.rotor_count = 0
        self.rr.rotor_count = 0

        return


    # ---------------------------------------------------------------
    # ---- change auto advance
    # ---------------------------------------------------------------

    def auto_advance_on(self):
        self.auto_advance_rotors = True
        return True

    def auto_advance_off(self):
        self.auto_advance_rotors = False
        return False

    def auto_advance_state(self):
        return auto_advance_rotors

    # ---------------------------------------------------------------
    # ---- change substitution debug
    # ---------------------------------------------------------------

    def sub_debug_on(self):
        self.substitution_debug = True
        return True

    def sub_debug_off(self):
        self.substitution_debug = False
        return False

    def sub_debug_state(self):
        return self.substitution_debug 

    # ---------------------------------------------------------------
    # ---- substitute character
    # ---------------------------------------------------------------
    def substitution(self,idx):

        idx1 = self.pg[idx]                      # plugboard
        idx2 = self.rr.rtol_substitution(idx1)
        idx3 = self.mr.rtol_substitution(idx2)
        idx4 = self.lr.rtol_substitution(idx3)
        idx5 = self.rf[idx4]                     # reflector
        idx6 = self.lr.ltor_substitution(idx5)
        idx7 = self.mr.ltor_substitution(idx6)
        idx8 = self.rr.ltor_substitution(idx7)
        idx9 = self.pg[idx8]                      # plugboard

        if self.substitution_debug:
            print(f'{idx} --> ',end='')
            print(f'{idx1} --> ',end='')
            print(f'{idx2} --> ',end='')
            print(f'{idx3} --> ',end='')
            print(f'{idx4} --> ',end='')
            print(f'{idx5} --> ',end='')
            print(f'{idx6} --> ',end='')
            print(f'{idx7} --> ',end='')
            print(f'{idx8} --> ',end='')
            print(f'{idx9}')

            print(f'{self.abc[idx]} --> ',end='')
            print(f'{self.abc[idx1]} --> ',end='')
            print(f'{self.abc[idx2]} --> ',end='')
            print(f'{self.abc[idx3]} --> ',end='')
            print(f'{self.abc[idx4]} --> ',end='')
            print(f'{self.abc[idx5]} --> ',end='')
            print(f'{self.abc[idx6]} --> ',end='')
            print(f'{self.abc[idx7]} --> ',end='')
            print(f'{self.abc[idx8]} --> ',end='')
            print(f'{self.abc[idx9]}')
            print(f'[{idx} ({self.abc[idx]})]  -->  ' +
                  f'[{idx8} ({self.abc[idx9]})]')

        if self.auto_advance_rotors:
            self.advance_rotors()

        return idx9