create_enigma_config_file.py

#!/usr/bin/python3
# ===================================================================
# Enigma Machine Simulation - create configuration file
# ===================================================================

from EnigmaUtils import *

import copy
import re
import sys
import random
import user_interface as ui
from datetime import date

rotors = {'I':[], 'II':[], 'III':[],   'IV':[],
          'V':[], 'VI':[], 'VII':[], 'VIII':[] }


# ---------------------------------------------------------------
# ---- select alphabet
# ----
# ---- NOTES:
# ---- 1. AN ALPHABET MUST CONTAIN AN EVEN NUMBER OF CHARACTERS
# ---- 2. IT MUST NOT CONTAIN A COMMA
# ---------------------------------------------------------------

def select_alphabet():

    # ---- big alphabet (44 characters)
    alphabet0 = [ 'A','B','C','D','E','F','G','H','I','J','K','L',
                  'M','N','O','P','Q','R','S','T','U','V','W','X',
                  'Y','Z','1','2','3','4','5','6','7','8','9','0',
                  '.','=','+','-','/','*','(',')' ]

    # ---- enigma alphabet
    alphabet1 = [ 'A','B','C','D','E','F','G','H','I','J','K','L',
                  'M','N','O','P','Q','R','S','T','U','V','W','X',
                  'Y','Z' ]

    # ---- test alphabet
    alphabet2 = [ 'A','B','C','D','E','F' ]

    # ---- big alphabet (48 characters)
    alphabet3 = [ 'A','B','C','D','E','F','G','H','I','J','K','L',
                  'M','N','O','P','Q','R','S','T','U','V','W','X',
                  'Y','Z','1','2','3','4','5','6','7','8','9','0',
                  '.','=','+','-','/','*','(',')',' ','\\','"','\'' ]


    # ---- alphabet menu
    print('''select an alphabet for enigma configuration file

   option  description
   ------  ------------------------------------------------
     0     big    alphabet [ A-Z,0-9, ... ] (44 characters) 
     1     enigma alphabet [ A-Z ]
     2     test   alphabet [ A-F ]
     3     bigger alphabet [ A-Z,0-9, ... ] (48 characters)
     9     exit program''')

    # ---- make sure the alphabets have an even number of characters

    if len(alphabet0)%2 != 0 or \
       len(alphabet1)%2 != 0 or \
       len(alphabet2)%2 != 0 or \
       len(alphabet3)%2 != 0:
           print()
           print('Internal error: An alphabet contains an odd ')
           print('number of characters - end program')
           sys.exit()

    while True:

        print()
        s =ui.get_user_input('Select alphabet: ')

        if not s:
            sys.exit()

        tf,i = ui.is_int(s)

        if not tf:
            continue

        if i == 0:
            return alphabet0
        if i == 1:
            return alphabet1
        if i == 2:
            return alphabet2
        if i == 3:
            return alphabet3
        if i == 9:
            print()
            sys.exit()

        print()
        print(f'Unlnown alphabet selection (i)')
        continue

# ---------------------------------------------------------------
# ---- create plugboard connections (two unique characters)
# ---------------------------------------------------------------

def add_plugboard_connections(abc):

    plubs = []

    char_in_use = []

    # ---- display the current alphabet

    for i in range(len(abc)):
        if i % 12 == 0:
            print()
            print('Alphabet:',end='')
        print(f' {abc[i]}',end='')
    print()

    # ---- process user input         

    while True:

        print()
        s = ui.get_user_input('Enter plugboard connection (ab): ')

        if not s:
            break

        # ---- error check the user's input

        s = s.upper()

        if len(s) != 2 or s[0] == s[1]:
            print()
            print(f'Bad connection entered ({s})')
            continue

        c0 = s[0]
        c1 = s[1]

        if c0 not in abc:
            print()
            print(f'Character {c0} not in the alphabet')
            continue

        if c1 not in abc:
            print()
            print(f'Character {c1} not in the alphabet')
            continue

        if c0 in char_in_use:
            print()
            print(f'Character {c0} already in use')
            continue

        if c1 in char_in_use:
            print()
            print(f'Character {c1} already in use')
            continue

        # ---- save the user's input

        char_in_use.append(c0)
        char_in_use.append(c1)

        plugs.append(s)

    return plugs


# -------------------------------------------------------------------
# ---- main
# -------------------------------------------------------------------

# ---- select an alphabet

abc = select_alphabet()            # alphabet

abclen     = len(abc)              # abc list length
abcidx     = list(range(abclen))   # abc ordered list of indexes
configfile = 'enigma_config.csv'   # configuration file
plugs      = []                    # plugboard pairs list
reflst     = []                    # reflector list
today      = date.today()          # today's date

# ---- write enigma config file

line_count = 0                     # count, lines           written
plug_count = 0                     # count, plugs           written
refl_count = 0                     # count, reflector lists written
roto_count = 0                     # count, rotor lists     written

with open(configfile,"w") as f:

    # ---- add header lines to output file

    f.write(f'# Enigma Machine Simulation Configuration\n')
    line_count += 1

    f.write(f'date,{today}\n')
    line_count += 1

    # ---- add an unknown character substitution to the output file
    # ---- if an input character is not in the alphabet substitute
    # ---- a known character

    if len(abc) < 25:
        f.write(f'unk,{abc[-1]}\n')
    else:
        f.write('unk,X\n')
    line_count += 1

    # ---- output alphabet length

    f.write(f'len,{abclen}\n')
    line_count += 1

    # ---- add alphabet to output file

    f.write(f'# alphabet,list-idx,char\n')
    line_count += 1

    for i in range(abclen):
        f.write(f'abc,{i},{abc[i]}\n')
        line_count += 1

    # ---- create rotor lists and add them to the output file

    for r in rotors: 

        ranlst = create_randomized_rotor_list(abcidx)

        rotors[r] = ranlst     # save it for later counting

        f.write(f'# rotor-name,in-list-idx,out-list-idx,in-char,out-char\n')
        line_count += 1

        for i in range(len(ranlst)):
            j = ranlst[i]
            f.write(f'{r},{i},{j},{abc[i]},{abc[j]}\n')
            line_count += 1

        roto_count += 1

    # ---- create reflector list and add it to the output file

    reflst = create_randomized_reflector_list(abcidx)

    f.write(f'# reflector,in-list-idx,out-list-idx,in-char,out-char\n')
    line_count += 1

    for i in range(abclen):
        j = reflst[i]
        f.write(f'ref,{i},{j},{abc[i]},{abc[j]}\n')
        line_count += 1

    # ---- verify reflector pairs

    for i in range(abclen):
        j = reflst[i]
        if reflst[j] != i: 
            print(f'Error in reflector list i={i} j={j}')
            print(f'reflst[{i}] = {reflst[i]}')
            print(f'reflst[{j}] = {reflst[j]}')
            sys.exit()

    refl_count += 1

    # ---- add plugboard connections to the output file

    plubs = add_plugboard_connections(abc)

    l = len(plugs)

    print()
    if l == 0:
        print('No plugboard connections')
    else:
        print(f'{l} plugboard connections')
        f.write(f'# plugboard,char-pair\n')
        line_count += 1
        for i in range(l):
            f.write(f'plug,{plugs[i]}\n')
            line_count += 1

# ---- display stats

print()
print(f'date         : {today}')  
print(f'file     name: {configfile}')
print(f'abc       len: {abclen}')
print(f'alphabet  len: {len(abc)}')
print(f'rotor   count: {len(rotors)}')
for r in rotors.keys():
    print(f'  {r:5}   len: {len(rotors[r])}')
print(f'reflector len: {len(reflst)}')
print(f'plugs        : {len(plugs)}')
print(f'lines written: {line_count}')
print()