Python program for the Fractionated Morse Cipher. The Fractionated Morse cipher first converts the plaintext to Morse code, then enciphers fixedsize blocks of Morse code back to letters. This procedure means plaintext letters are mixed into the ciphertext letters, making it more secure than substitution ciphers. http:practicalcryptography.comciphersfractionatedmorsecipher Define possible trigrams of Morse code Create a reverse dictionary for Morse code Encode a plaintext message into Morse code. Args: plaintext: The plaintext message to encode. Returns: The Morse code representation of the plaintext message. Example:  encodetomorsedefend the east '..x.x...x.x.x..xxx....x.xx.x.x...x' Encrypt a plaintext message using Fractionated Morse Cipher. Args: plaintext: The plaintext message to encrypt. key: The encryption key. Returns: The encrypted ciphertext. Example:  encryptfractionatedmorsedefend the east,Roundtable 'ESOAVVLJRSSTRX' Ensure morsecode length is a multiple of 3 Decrypt a ciphertext message encrypted with Fractionated Morse Cipher. Args: ciphertext: The ciphertext message to decrypt. key: The decryption key. Returns: The decrypted plaintext message. Example:  decryptfractionatedmorseESOAVVLJRSSTRX,Roundtable 'DEFEND THE EAST' Example usage of Fractionated Morse Cipher.
import string

MORSE_CODE_DICT = {
    "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": "--..",
    " ": "",
}

# Define possible trigrams of Morse code
MORSE_COMBINATIONS = [
    "...",
    "..-",
    "..x",
    ".-.",
    ".--",
    ".-x",
    ".x.",
    ".x-",
    ".xx",
    "-..",
    "-.-",
    "-.x",
    "--.",
    "---",
    "--x",
    "-x.",
    "-x-",
    "-xx",
    "x..",
    "x.-",
    "x.x",
    "x-.",
    "x--",
    "x-x",
    "xx.",
    "xx-",
    "xxx",
]

# Create a reverse dictionary for Morse code
REVERSE_DICT = {value: key for key, value in MORSE_CODE_DICT.items()}


def encode_to_morse(plaintext: str) -> str:
    """Encode a plaintext message into Morse code.

    Args:
        plaintext: The plaintext message to encode.

    Returns:
        The Morse code representation of the plaintext message.

    Example:
        >>> encode_to_morse("defend the east")
        '-..x.x..-.x.x-.x-..xx-x....x.xx.x.-x...x-'
    """
    return "x".join([MORSE_CODE_DICT.get(letter.upper(), "") for letter in plaintext])


def encrypt_fractionated_morse(plaintext: str, key: str) -> str:
    """Encrypt a plaintext message using Fractionated Morse Cipher.

    Args:
        plaintext: The plaintext message to encrypt.
        key: The encryption key.

    Returns:
        The encrypted ciphertext.

    Example:
        >>> encrypt_fractionated_morse("defend the east","Roundtable")
        'ESOAVVLJRSSTRX'

    """
    morse_code = encode_to_morse(plaintext)
    key = key.upper() + string.ascii_uppercase
    key = "".join(sorted(set(key), key=key.find))

    # Ensure morse_code length is a multiple of 3
    padding_length = 3 - (len(morse_code) % 3)
    morse_code += "x" * padding_length

    fractionated_morse_dict = {v: k for k, v in zip(key, MORSE_COMBINATIONS)}
    fractionated_morse_dict["xxx"] = ""
    encrypted_text = "".join(
        [
            fractionated_morse_dict[morse_code[i : i + 3]]
            for i in range(0, len(morse_code), 3)
        ]
    )
    return encrypted_text


def decrypt_fractionated_morse(ciphertext: str, key: str) -> str:
    """Decrypt a ciphertext message encrypted with Fractionated Morse Cipher.

    Args:
        ciphertext: The ciphertext message to decrypt.
        key: The decryption key.

    Returns:
        The decrypted plaintext message.

    Example:
        >>> decrypt_fractionated_morse("ESOAVVLJRSSTRX","Roundtable")
        'DEFEND THE EAST'
    """
    key = key.upper() + string.ascii_uppercase
    key = "".join(sorted(set(key), key=key.find))

    inverse_fractionated_morse_dict = dict(zip(key, MORSE_COMBINATIONS))
    morse_code = "".join(
        [inverse_fractionated_morse_dict.get(letter, "") for letter in ciphertext]
    )
    decrypted_text = "".join(
        [REVERSE_DICT[code] for code in morse_code.split("x")]
    ).strip()
    return decrypted_text


if __name__ == "__main__":
    """
    Example usage of Fractionated Morse Cipher.
    """
    plaintext = "defend the east"
    print("Plain Text:", plaintext)
    key = "ROUNDTABLE"

    ciphertext = encrypt_fractionated_morse(plaintext, key)
    print("Encrypted:", ciphertext)

    decrypted_text = decrypt_fractionated_morse(ciphertext, key)
    print("Decrypted:", decrypted_text)
