3
\$\begingroup\$

Caeser's cipher code where there are a converter and a game part with user input. The code includes keys (settable and not settable).

import random


def change_letter(original, new_message, key_number, alpha):
    for letter in original:
        if letter in alpha.lower():
            new_message += alpha[(alpha.lower().index(letter) + key_number) % 26].lower()
        elif letter in alpha:
            new_message += alpha[(alpha.index(letter) + key_number) % 26]
        else:
            new_message += letter

    return new_message


def encipher(the_key, message, inp=True):
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    output_message = ""

    if inp:
        try:
            the_key = int(input('Key?  ・  '))
        except ValueError:
            the_key = input('\nNot valid input  ・  ')
            while not the_key.isnumeric():
                the_key = input('Not valid input  ・  ')

    output_message = change_letter(message, output_message, int(the_key), alphabet)

    return output_message


def convert():
    while True:
        key = None
        type_word = input('\nType your word  ・  ')
        print('\n{}  ::  {}'.format(type_word, encipher(key, type_word)))
        convert_again = input('\nWould you like to convert again? | Y/N  ・  ').upper()

        if convert_again == 'Y' or convert_again == 'YES':
            continue
        break


def play():
    while True:
        key = random.randint(1, 25)
        words = ('WWE', 'Javascript', 'Pythonista', 'Computer Science', 'Eighty-three', 'Event Log', '5K Freestyle')
        word = random.choice(words)
        question = input('\nWhat is {} of key {}  ・  '.format(word, key)).lower()
        changed_word = encipher(key, word, inp=False)

        if question == changed_word.lower():
            print(f'Congratulations! You guessed {changed_word} from {word}')
        else:
            print(f'Oops! Wrong answer. The word is {changed_word} from {word}')

        play_again = input('\nWould you like to play again? | Y/N  ・  ')

        if play_again.upper() == 'YES' or play_again.upper() == 'Y':
            continue
        break


def main():
    while True:
        convert_or_play = input('C to convert, P to play  ・  ')
        if convert_or_play.upper() == 'C':
            convert()
        elif convert_or_play.upper() == 'P':
            play()
        else:
            break
        print()


if __name__ == '__main__':
    main()
\$\endgroup\$
1
\$\begingroup\$
def change_letter(original, new_message, key_number, alpha):
  • docstrings: Python documentation strings (or docstrings) provide a convenient way of associating documentation with Python modules, functions, classes, and methods.It’s specified in source code that is used, like a comment, to document a specific segment of code.

You should include a docstring indicating what the parameters are and what the function does:

def change_letter(original, new_message, key_number, alpha):
    """
    Encode message.
    original: a string (word)
    new_message: empty string
    key_number: int
    alpha: a string containing lowercase alphabet
    """

unecessary function parameter: new_message

There is no need to bother user with entering an empty string.

string immutability::

new_message += alpha[(alpha.lower().index(letter) + key_number) % 26].lower()

A string is immutable in Python therefore each time you're adding to a string, you're creating another string instance and this is pretty inefficient, use list comprehension syntax instead.

list comprehension syntax:

You can use list comprehensions to replace this whole function in the following way:

def encode(message: str, shift: int):
   """Encode and return message."""
return ''.join([chr(ord(char) + shift) for char in message.lower()])

the use of ord and chr is very useful in this case since both do not care about whether a character is uppercase, lowercase or digit, they convert everything and they are much more efficient than alpha.lower().index(letter) + key_number) % 26 which call .lower() and .index() for each letter in the word.

def encipher(the_key, message, inp=True):
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  • same comments regarding docstrings.
  • you can use string.ascii_uppercase instead of manually writing the letters with possibility of a slip of hand and the creation of a bug in the case of a missed letter.
  • if inp: I did not find a single case where inp might be set to False so there is no need for this parameter.

This whole encipher() can be shortened to the following:

def encipher(message, encryption_key=input('Enter key: ')):
    """Encipher according to a user given key, return an error if key is not digit."""
    while not encryption_key.isdigit():
        encryption_key = input('Invalid input, enter a number: ')
    return encode(message, int(encryption_key))

f-strings: since you're using Python 3, in Python source code, an f-string is a literal string, prefixed with 'f', which contains expressions inside braces. The expressions are replaced with their values.

print('\n{}  ::  {}'.format(type_word, encipher(key, type_word)))

expression can be written using f-strings in the following way:

print(f"\n{type_word} : {encipher(key, type_word)}")

same goes for question = input('\nWhat is {} of key {} ・ '.format(word, key)).lower()

can be written:

question = input(f'What is the translation of {word} using {key} as key? ').lower()

Here's a refactored version of the code:

import random


def encode(message: str, shift: int):
    """Encode and return message."""
    return ''.join([chr(ord(char) + shift) for char in message.lower()])


def encipher():
    """Encode message interactively."""
    message = input('Enter word/message: ')
    encryption_key = input('Enter key: ')
    while not encryption_key.isdigit():
        key = input('Invalid input, enter a number: ')
    encoded_message = encode(message, int(encryption_key))
    print(f'{message} has been encoded to {encoded_message}')
    re_encode = input(f'Would you like to convert {encoded_message} again? y/n: ').lower()
    while re_encode.lower() not in 'yesno':
        print(f'Invalid input {re_encode}')
        re_encode = input(f'Would you like to convert another word? y/n: ').lower()
    if re_encode in 'no':
        print('Thank you for using Cesar cipher.')
        exit(0)
    if re_encode in 'yes':
        encipher()


def play():
    """Play a cipher game."""
    key = random.randint(1, 25)
    words = ('WWE', 'Javascript', 'Pythonista', 'Computer Science', 'Eighty-three', 'Event Log', '5K Freestyle')
    word = random.choice(words)
    answer = input(f'What is the translation of {word} using {key} as key? ')
    encoded_word = encode(word, key)
    if answer == encoded_word:
        print(f'Correct guess {encoded_word}')
    if answer != encoded_word:
        print(f'Oops! wrong guess {answer} the correct answer is {encoded_word}')
    replay = input('Would you like to play again? y/n: ')
    while replay not in 'yesno':
        print(f'Invalid input {replay}')
        replay = input('Would you like to play again? y/n: ')
    if replay in 'no':
        print('Thank you for using Cesar cipher.')
        exit(0)
    if replay in 'yes':
        play()


if __name__ == '__main__':
    while True:
        game_or_encode = input('Enter e to encode or p to play e/p: ').lower()
        while game_or_encode not in 'ep':
            print(f'Invalid entry {game_or_encode}')
            game_or_encode = input('Enter e to encode or p to play e/p: ').lower()
        if game_or_encode == 'e':
            encipher()
        if game_or_encode == 'p':
            play()
|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ what does "''.join([chr(ord(char) + shift) for char in message.lower()])" explicitly mean? \$\endgroup\$ – miAK Sep 7 '19 at 5:19
  • 1
    \$\begingroup\$ The ord() gets the numerical representation of a character if you add shift then the numerical representation is shifted by the shift value. Then the call to chr() converts a numerical value back to a letter. example: ord('a') = 97, ord('c') = 99 so if you called chr(99) it converts the 99 to its respective letter 'c', the list contains all the converted letters(strings) so a call to join() joins the list into the converted(encoded) word. \$\endgroup\$ – user203258 Sep 7 '19 at 5:27
  • 1
    \$\begingroup\$ You might want to check this link docs.python.org/2/library/functions.html \$\endgroup\$ – user203258 Sep 7 '19 at 5:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.