# Simple game of Hangman

On my quest to become a master programmer I have created a simple game of Hangman, and so I thought I would upload it here and see how it could be refined.

One question I had was my use of exception handling. I tried to utilise it here, but I get the sense it might have been overkill. Should I only be using try/except if I know there will be exceptions, or is it good practice to use them regardless?

"""Hangman
Standard game of Hangman. A word is chosen at random from a list and the
user must guess the word letter by letter before running out of attempts."""

import random

def main():
welcome = ['Welcome to Hangman! A word will be chosen at random and',
'you must try to guess the word correctly letter by letter',
'before you run out of attempts. Good luck!'
]

for line in welcome:
print(line, sep='\n')

# setting up the play_again loop

play_again = True

while play_again:
# set up the game loop

words = ["hangman", "chairs", "backpack", "bodywash", "clothing",
"computer", "python", "program", "glasses", "sweatshirt",
"sweatpants", "mattress", "friends", "clocks", "biology",
"algebra", "suitcase", "knives", "ninjas", "shampoo"
]

chosen_word = random.choice(words).lower()
player_guess = None # will hold the players guess
guessed_letters = [] # a list of letters guessed so far
word_guessed = []
for letter in chosen_word:
word_guessed.append("-") # create an unguessed, blank version of the word
joined_word = None # joins the words in the list word_guessed

HANGMAN = (
"""
-----
|   |
|
|
|
|
|
|
|
--------
""",
"""
-----
|   |
|   0
|
|
|
|
|
|
--------
""",
"""
-----
|   |
|   0
|  -+-
|
|
|
|
|
--------
""",
"""
-----
|   |
|   0
| /-+-
|
|
|
|
|
--------
""",
"""
-----
|   |
|   0
| /-+-\
|
|
|
|
|
--------
""",
"""
-----
|   |
|   0
| /-+-\
|   |
|
|
|
|
--------
""",
"""
-----
|   |
|   0
| /-+-\
|   |
|   |
|
|
|
--------
""",
"""
-----
|   |
|   0
| /-+-\
|   |
|   |
|  |
|
|
--------
""",
"""
-----
|   |
|   0
| /-+-\
|   |
|   |
|  |
|  |
|
--------
""",
"""
-----
|   |
|   0
| /-+-\
|   |
|   |
|  | |
|  |
|
--------
""",
"""
-----
|   |
|   0
| /-+-\
|   |
|   |
|  | |
|  | |
|
--------
""")

print(HANGMAN[0])
attempts = len(HANGMAN) - 1

while (attempts != 0 and "-" in word_guessed):
print(("\nYou have {} attempts remaining").format(attempts))
joined_word = "".join(word_guessed)
print(joined_word)

try:
player_guess = str(input("\nPlease select a letter between A-Z" + "\n> ")).lower()
except: # check valid input
print("That is not valid input. Please try again.")
continue
else:
if not player_guess.isalpha(): # check the input is a letter. Also checks an input has been made.
print("That is not a letter. Please try again.")
continue
elif len(player_guess) > 1: # check the input is only one letter
print("That is more than one letter. Please try again.")
continue
elif player_guess in guessed_letters: # check it letter hasn't been guessed already
continue
else:
pass

guessed_letters.append(player_guess)

for letter in range(len(chosen_word)):
if player_guess == chosen_word[letter]:
word_guessed[letter] = player_guess # replace all letters in the chosen word that match the players guess

if player_guess not in chosen_word:
attempts -= 1
print(HANGMAN[(len(HANGMAN) - 1) - attempts])

if "-" not in word_guessed: # no blanks remaining
print(("\nCongratulations! {} was the word").format(chosen_word))
else: # loop must have ended because attempts reached 0
print(("\nUnlucky! The word was {}.").format(chosen_word))

print("\nWould you like to play again?")

response = input("> ").lower()
if response not in ("yes", "y"):
play_again = False

if __name__ == "__main__":
main()

• Nice validation and well done with the play again question! You could break it into multiple functions, draw the hangman programmatically, download a full dictionary, add emoji. See sources for richardsgames.com/hangman.html – fmalina Sep 27 '18 at 15:51

You have a bare except clause; i.e.,

try:
some_code()
except:
clean_up()


The problem with a bare except is that it will catch all exceptions, including ones you really don’t want to be ignoring (like KeyboardInterrupt and SystemExit). It would be much better if your except block only caught the specific exception you expect, and let all others bubble up as normal.

• In line 200, you have this construction:

for letter in range(len(chosen_word)):
if player_guess == chosen_word[letter]:
word_guessed[letter] = player_guess


You’re looping over the index variable, but also using the list element. It would be better to write:

for idx, letter in enumerate(chosen_word):
if player_guess == letter:
word_guessed[idx] = player_guess

• Rather than using the flag value play_again, you could use a break statement. That is, structure your code as follows:

while True:
# play the game

if not player_response_is_play_again():
break


That’s a bit cleaner and easier to follow, because I don’t have to follow the play_again variable around.

• Your main() function is huge. Most of this code is not reusable, because it’s tied up in a single monolithic function. You’d be much better off breaking it into small, distinct units of code: those are easier to test and debug, and it would be more reusable to boot.

• When you changed the loop, your "letter" is now an element of the list, instead of an index. You need to change the way the word_guessed data structure is used, if you're going to assign something new to it. This is not always cleaner when you consider the big picture. – ShreevatsaR Jul 6 '15 at 20:09
• @ShreevatsaR Good spot, my mistake. Fixed. – alexwlchan Jul 6 '15 at 20:10

You have way too much code in your main function. Try and see if you can separate the logic out into separate functions, each with their own arguments. For example, the below code block:

words = ["hangman", "chairs", "backpack", "bodywash", "clothing",
"computer", "python", "program", "glasses", "sweatshirt",
"sweatpants", "mattress", "friends", "clocks", "biology",
"algebra", "suitcase", "knives", "ninjas", "shampoo"
]

chosen_word = random.choice(words).lower()


Could easily be separated into a function named choose_word, like this:

def choose_word(word_list):
"""
Choose a random word from the word_list
and return it.
"""
return random.choice(word_list)


You also have some long mutliline strings. For example, the following multiline string:

"""
-----
|   |
|
|
|
|
|
|
|
--------
"""


Can very easily be shortened to this:

"-----\n|   |\n|\n|\n|\n|\n|\n|\n|\n--------"


Do note, this isn't required though. It's just personal preference.

The way you're printing multiple pieces of text is odd, as well. For example, the below piece of code:

welcome = ['Welcome to Hangman! A word will be chosen at random and',
'you must try to guess the word correctly letter by letter',
'before you run out of attempts. Good luck!'
]

for line in welcome:
print(line, sep='\n')


Can again be shortened to this:

print(
"Welcome to Hangman! A word will be chosen at random and",
"you must try to guess the word correctly letter by letter",
"before you run out of attempts. Good luck!",
sep="\n"
)


I also noticed that you're mixing the use of single quotes, '', and double quotes, "". While this isn't a huge deal, I'd recommend choosing one or the other, and sticking with it.

The way you're styling lists is odd as well. While it isn't required, I'd recommend doing it like this:

my_list = [
value,
...
]


Finally, when getting user input, you don't need to convert it to a string using the str function. You can just get it like this: user_input = input("prompt").

• That "ugly mutliline string" is much more readable, as you actually get to see the real output – Simon Forsberg Jul 6 '15 at 18:15
• @SimonAndréForsberg I've edited my post to include that as optional. – Ethan Bierlein Jul 6 '15 at 18:17
• Some good points here, but I disagree with condensing the string. – user34073 Jul 6 '15 at 18:24
• @SirPython Nope. Try the example. It works fine. – Ethan Bierlein Jul 6 '15 at 18:27
for line in welcome:
print(line, sep='\n')


This looks like the kind of code that would be in a function to print out multiple strings in array. However, since you only have one message that is in an array, this seems kind of overkill, especially since there are only three lines.

It would be a lot easier just to print out the message as it is:

print "..."
print "..."
...


Instead of trying to get your own list of words, I recommend trying one of these three options:

• Find an API.

There are probably tons of API's on the internet that will serve as a dictionary for your program.

Pro: You don't have to download anything; just make a simple call to an API.

Con: APIs have limits such as calls per day. Also, sometimes your code can get hung up on an API call.

Unix words in a dictionary that comes with Unix and Unix-like operating systems.

Pro: It's just a file on the computer that has a single word per line, making it very easy to read.

Con: Your user may not be using Unix or a Unix-like operating system.

• Use PyEnchant.

I don't know about this one. I saw it being used in some Python code before that involved an English words reference, but I don't know how accessing it's built-in dictionary works.

chosen_word = random.choice(words).lower()


There is no point in calling the method .lower because all the words in your dictionary are already lower-case.

When asking if the user wants to play again:

print("\nWould you like to play again?")


Instead of having to have a conditional where you check for two options later ("yes" and "y"), give the user their options:

print("\nWould you like to play again? [Y/N]")


And, to take it step further and get rid of an extra function call, put this message inside the input call:

input("\nWould you like to play again? [Y/N]\n>")


The variable welcome in the beginning of your code:

welcome = ['Welcome to Hangman! A word will be chosen at random and',
'you must try to guess the word correctly letter by letter',
'before you run out of attempts. Good luck!'
]


Should not be in an array; it should be in a tuple because the values in it are not going to change.

The same goes to your words array.

To answer your question about exceptions, I think that you should only use try/exception if you know that there will be errors. It is always good to catch and stop an error, but there is no point in trying if there will be no error.

Also, knowing that there will be errors helps because then you can catch the right kind of error, rather than just catching Exception.

You did a good job catching the error here and differing between using try/except and if/else:

try:
player_guess = str(input("\nPlease select a letter between A-Z" + "\n> ")).lower()
except: # check valid input
print("That is not valid input. Please try again.")
continue


However, you should be catching the type of error that would come out of that line.

I'm not sure which error will come out, but I think it is either a TypeError or an IOError. Either way, you'd change your try/except to something like this:

try:
...
catch ExceptionType:
...


        try:
player_guess = str(input("\nPlease select a letter between A-Z" + "\n> ")).lower()
except: # check valid input
print("That is not valid input. Please try again.")
continue
else:
if not player_guess.isalpha(): # check the input is a letter. Also checks an input has been made.
print("That is not a letter. Please try again.")
continue
elif len(player_guess) > 1: # check the input is only one letter
print("That is more than one letter. Please try again.")
continue
elif player_guess in guessed_letters: # check it letter hasn't been guessed already