6
\$\begingroup\$

Assignment Criteria

Create another version of the Hangman game, this time using Lists. The program should ask for the word to guess and the number of chances to be given.

It should then split the characters in the word into individual items in a list. The other player should then be allowed to guess characters in the word. The program should display correctly guessed characters and unknown characters in the same way as the previous Hangman game.

This a simple Hangman game in Python and I was just wondering if there was any code improvements that could be done with this. I can definitely see a few, but they're very minor changes (e.g. spaces can be removed as it's redundant and never really gets used.)

# ========== Variables ============ #
letter_bank = ['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']

guessed = False  # Word has been guessed. (boolean)

spaces = ""      # Blanks
word_guess = []  # Placeholder
word_chars = []  # Characters in Word.

word = ""      # Word being guessed.
max_guess = None # Maximum amount of Guess.
cur_guess = 0 # Current count of Guesses.
# ================================= #
def findOccurences(s, ch):
    return [i for i, letter in enumerate(s) if letter == ch]

print('''
    Welcome to Hangman! Simplified version of the original Hangman.
''')

word = input("Please enter a word: ").lower()
max_guess = int(input("Maximum Guess: "))

for character in word:
    word_chars.append(character)
    if (character != " "):
        spaces += "_ "
        word_guess.append("-")
    else:
        spaces += ("  ")
        word_guess.append("  ")
        
print("\n" * 50)

print('''
    You are now ready to play.
''')

print("Word: %s. " % (spaces))
while (cur_guess < max_guess) and guessed == False:
    if (word.split() == (''.join(word_guess)).split()):
        guessed = True
        print('''
            You have successfully guessed the word!
        ''')
        break;

    letter_guess = input("Please enter your guess: ").lower()
    if (len(letter_guess) == 1):
        if (letter_guess not in letter_bank):
            print("You have already used that letter!") # Prints if the character is invalid or already used.
        else:
            if (letter_guess in word_chars):
                print("%s is in the word!" % letter_guess)
                letter_bank.remove(letter_guess)
                for each in findOccurences(word, letter_guess):
                    word_guess[each] = letter_guess
                print(''.join(word_guess))
            else:
                print(''.join(word_guess))
                print("%s is not in the word!" % letter_guess)
                letter_bank.remove(letter_guess)
                cur_guess += 1
    else:
        if (letter_guess == word):
            print('''
                You have successfully guessed the word!
            ''')
        else:
            print('''
                You have lost the game! :(
            ''')
            break;
\$\endgroup\$
0

2 Answers 2

3
\$\begingroup\$

Python is not C. You don't need to declare variables at the beginning and you don't need to set them to empty values if the first thing you do in your code is set them to sensible values.

Python is also unlike C in the sense that you don't need () around statements. while expression is perfectly valid (no need for while (expression) and so is if expression.

Python is unlike C in that it does not need ; at the end of lines. While it is not a SyntaxError, it is only needed when you absolutely have to put multiple statements on the same line (e.g. sometimes when using the commandline). This is basically never the case when writing a script in a file.

Python is like C in the sense that it has sensible operator preferences. Instead of while (cur_guess < max_guess) and guessed == False you can write while cur_guess < max_guess and not guessed and the < expression will be evaluated before the and.

A second thing I did there was removing the direct comparison with False. This is almost never needed (same as direct comparison with True), because the value itself is already a bool (and if not, most types have truthy and falsy values which behave just like bools, e.g. for strings the empty string "" is falsy, every other string is thruthy).

Your letter_bank would be better suited for a set, which has fast membership tests (since you do a lot of if letter_guess not in letter_bank. And since you are using the whole lowercase alphabet, why not use the built-in module string to save yourself some typing (next time):

import string
letter_bank = set(string.ascii_lowercase)

You don't even need to change letter_bank.remove(letter_guess), because it is the same for a set.

The same is true for word_chars.

Python has an official style-guide, PEP8, that recommends using lower_case_with_underscores for variables and functions. So your findOccurences would become find_occurences.

To increase readability, I would put more of your functionality into functions. At least have a setup() and a game() function to separate these two parts of logic.

With these (rudimentary) recommendations, your code would become:

import string


def find_occurences(s, ch):
    return [i for i, letter in enumerate(s) if letter == ch]


def game(word, max_guess):
    letter_bank = set(string.ascii_lowercase)
    word_chars = set(word)
    if word_chars - letter_bank:
        print("Words can only contain ascii letters!")
        return False
    word_guess = list(" ".join("-" * len(w) for w in word.split()))
    cur_guess = 0  # Current count of Guesses.

    while cur_guess < max_guess:
        print(''.join(word_guess))
        if word == ''.join(word_guess):
            return True

        letter_guess = input(
            "Please enter your guess (%i remaining):" % (max_guess - cur_guess)).lower()
        if len(letter_guess) != 1:
            return letter_guess == word
        elif letter_guess not in letter_bank:
            print("You have already used that letter or it is not a valid letter!")
        elif letter_guess in word_chars:
            print("%s is in the word!" % letter_guess)
            letter_bank.remove(letter_guess)
            for each in find_occurences(word, letter_guess):
                word_guess[each] = letter_guess
        else:
            print("%s is not in the word!" % letter_guess)
            letter_bank.remove(letter_guess)
            cur_guess += 1
        print()
    return False


def main():
    print('Welcome to Hangman! Simplified version of the original Hangman.')

    word = input("Please enter a word: ").lower()
    max_guess = int(input("Maximum Guess: "))

    print("\n" * 50)
    print('You are now ready to play.')

    if game(word, max_guess):
        print('You have successfully guessed the word!')
    else:
        print('You have lost the game! :(\nThe word was %s' % word)

if __name__ == "__main__":
    main()
\$\endgroup\$
2
\$\begingroup\$

This list declaration:

letter_bank = ['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']

would be much simpler, and less error-prone, done like this using the string module:

import string
# ...
letter_bank = list(string.ascii_lowercase)

This version obtains the lowercase ASCII characters 'abcdefghijklmnopqrstuvwxyz' from string module and then splits the characters in the string into a list.


This boolean variable:

guessed = False  # Word has been guessed. (boolean)

May as well be named like this, for clarity and to eliminate the need for a comment:

word_is_guessed = False

The same kind of naming could easily be applied to all of these declarations so that the line comments will no longer be needed:

spaces = ""      # Blanks
word_guess = []  # Placeholder
word_chars = []  # Characters in Word.

word = ""      # Word being guessed.
max_guess = None # Maximum amount of Guess.
cur_guess = 0 # Current count of Guesses.

Your multi-line strings are just taking up space, where there is not a need for multi-line string formatting. This, for example:

print('''
    Welcome to Hangman! Simplified version of the original Hangman.
''')

Assuming you intend the 4 spaces in front of the line, may as well be written as such and take 2 fewer lines of code:

print('\n    Welcome to Hangman! Simplified version of the original Hangman.\n')

Looking at the code though, I think the 4 leading spaces may be unintentional and a result of indentation habits. If you want a truly left-aligned multi-line string, do it like this (assuming a \n before and after the phrase):

print('''
Welcome to Hangman! Simplified version of the original Hangman.
''')

The findOccurences() function could be improved so that instead of this:

def findOccurences(s, ch):
    return [i for i, letter in enumerate(s) if letter == ch]

You could use better names, as well as Python 3 type hints (which won't change the code behavior, but can make your code cleaner if you use static code analysis):

def findOccurences(string: str, ch: str) -> list:
    return [i for i, letter in enumerate(string) if letter == ch]

(you will notice that single characters are just strings in Python, there is not a "character" type)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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