the trifid cipher uses a table to fractionate each plaintext letter into a trigram mixes the constituents of the trigrams and then applies the table in reverse to turn these mixed trigrams into ciphertext letters https en wikipedia orgwikitrifidcipher fmt off fmt off arrange the triagram value of each letter of messagepart vertically and join them horizontally encryptpart ask testcharactertonumber 132111112 convert each letter of the input string into their respective trigram values join them and split them into three equal groups of strings which are returned decryptpart abcde testcharactertonumber 11111 21131 21122 a helper function that generates the triagrams and assigns each letter of the alphabet to its corresponding triagram and stores this in a dictionary charactertonumber and numbertocharacter after confirming if the alphabet s length is 27 test prepare i am a boy abcdefghijklmnopqrstuvwxyz expected iamaboy abcdefghijklmnopqrstuvwxyz testcharactertonumber testnumbertocharacter test expected true testing with incomplete alphabet prepare i am a boy abcdefghijklmnopqrstuvw traceback most recent call last keyerror length of alphabet has to be 27 testing with extra long alphabets prepare i am a boy abcdefghijklmnopqrstuvwxyzzwwtyyujjgfd traceback most recent call last keyerror length of alphabet has to be 27 testing with punctuations that are not in the given alphabet prepare am i a boy abcdefghijklmnopqrstuvwxyz traceback most recent call last valueerror each message character has to be included in alphabet testing with numbers prepare500 abcdefghijklmnopqrstuvwxyz traceback most recent call last attributeerror int object has no attribute replace validate message and alphabet set to upper and remove spaces check length and characters generate dictionares encryptmessage encrypts a message using the trifidcipher any punctuatuions that would be used should be added to the alphabet parameters message the message you want to encrypt alphabet optional the characters to be used for the cipher period optional the number of characters you want in a group whilst encrypting encryptmessage i am a boy bcdgbqy encryptmessage encryptmessage aide toi le c iel ta id era felixmardstbcghjknopquvwyz 5 fmjfvoissuftfpufeqqc decryptmessage decrypts a trifidcipher encrypted message parameters message the message you want to decrypt alphabet optional the characters used for the cipher period optional the number of characters used in grouping when it was encrypted decryptmessage bcdgbqy iamaboy decrypting with your own alphabet and period decryptmessage fmjfvoissuftfpufeqqc felixmardstbcghjknopquvwyz 5 aidetoilecieltaidera fmt off fmt off arrange the triagram value of each letter of message_part vertically and join them horizontally __encrypt_part ask test_character_to_number 132111112 convert each letter of the input string into their respective trigram values join them and split them into three equal groups of strings which are returned __decrypt_part abcde test_character_to_number 11111 21131 21122 a helper function that generates the triagrams and assigns each letter of the alphabet to its corresponding triagram and stores this in a dictionary character_to_number and number_to_character after confirming if the alphabet s length is 27 test __prepare i am a boy abcdefghijklmnopqrstuvwxyz expected iamaboy abcdefghijklmnopqrstuvwxyz test_character_to_number test_number_to_character test expected true testing with incomplete alphabet __prepare i am a boy abcdefghijklmnopqrstuvw traceback most recent call last keyerror length of alphabet has to be 27 testing with extra long alphabets __prepare i am a boy abcdefghijklmnopqrstuvwxyzzwwtyyujjgfd traceback most recent call last keyerror length of alphabet has to be 27 testing with punctuations that are not in the given alphabet __prepare am i a boy abcdefghijklmnopqrstuvwxyz traceback most recent call last valueerror each message character has to be included in alphabet testing with numbers __prepare 500 abcdefghijklmnopqrstuvwxyz traceback most recent call last attributeerror int object has no attribute replace validate message and alphabet set to upper and remove spaces check length and characters generate dictionares encrypt_message encrypts a message using the trifid_cipher any punctuatuions that would be used should be added to the alphabet parameters message the message you want to encrypt alphabet optional the characters to be used for the cipher period optional the number of characters you want in a group whilst encrypting encrypt_message i am a boy bcdgbqy encrypt_message encrypt_message aide toi le c iel ta id era felixmardstbcghjknopquvwyz 5 fmjfvoissuftfpufeqqc decrypt_message decrypts a trifid_cipher encrypted message parameters message the message you want to decrypt alphabet optional the characters used for the cipher period optional the number of characters used in grouping when it was encrypted decrypt_message bcdgbqy iamaboy decrypting with your own alphabet and period decrypt_message fmjfvoissuftfpufeqqc felixmardstbcghjknopquvwyz 5 aidetoilecieltaidera
from __future__ import annotations


TEST_CHARACTER_TO_NUMBER = {
    "A": "111", "B": "112", "C": "113", "D": "121", "E": "122", "F": "123", "G": "131",
    "H": "132", "I": "133", "J": "211", "K": "212", "L": "213", "M": "221", "N": "222",
    "O": "223", "P": "231", "Q": "232", "R": "233", "S": "311", "T": "312", "U": "313",
    "V": "321", "W": "322", "X": "323", "Y": "331", "Z": "332", "+": "333",
}


TEST_NUMBER_TO_CHARACTER = {val: key for key, val in TEST_CHARACTER_TO_NUMBER.items()}


def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> str:
    
    one, two, three = "", "", ""
    for each in (character_to_number[character] for character in message_part):
        one += each[0]
        two += each[1]
        three += each[2]

    return one + two + three


def __decrypt_part(
    message_part: str, character_to_number: dict[str, str]
) -> tuple[str, str, str]:
    
    this_part = "".join(character_to_number[character] for character in message_part)
    result = []
    tmp = ""
    for digit in this_part:
        tmp += digit
        if len(tmp) == len(message_part):
            result.append(tmp)
            tmp = ""

    return result[0], result[1], result[2]


def __prepare(
    message: str, alphabet: str
) -> tuple[str, str, dict[str, str], dict[str, str]]:
    
    
    alphabet = alphabet.replace(" ", "").upper()
    message = message.replace(" ", "").upper()

    
    if len(alphabet) != 27:
        raise KeyError("Length of alphabet has to be 27.")
    if any(char not in alphabet for char in message):
        raise ValueError("Each message character has to be included in alphabet!")

    
    character_to_number = dict(zip(alphabet, TEST_CHARACTER_TO_NUMBER.values()))
    number_to_character = {
        number: letter for letter, number in character_to_number.items()
    }

    return message, alphabet, character_to_number, number_to_character


def encrypt_message(
    message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5
) -> str:
    
    message, alphabet, character_to_number, number_to_character = __prepare(
        message, alphabet
    )

    encrypted_numeric = ""
    for i in range(0, len(message) + 1, period):
        encrypted_numeric += __encrypt_part(
            message[i : i + period], character_to_number
        )

    encrypted = ""
    for i in range(0, len(encrypted_numeric), 3):
        encrypted += number_to_character[encrypted_numeric[i : i + 3]]
    return encrypted


def decrypt_message(
    message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5
) -> str:
    
    message, alphabet, character_to_number, number_to_character = __prepare(
        message, alphabet
    )

    decrypted_numeric = []
    for i in range(0, len(message), period):
        a, b, c = __decrypt_part(message[i : i + period], character_to_number)

        for j in range(len(a)):
            decrypted_numeric.append(a[j] + b[j] + c[j])

    return "".join(number_to_character[each] for each in decrypted_numeric)


if __name__ == "__main__":
    import doctest

    doctest.testmod()
    msg = "DEFEND THE EAST WALL OF THE CASTLE."
    encrypted = encrypt_message(msg, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ")
    decrypted = decrypt_message(encrypted, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ")
    print(f"Encrypted: {encrypted}\nDecrypted: {decrypted}")