Overview
I can see that you are a beginner and that is totally fine! Some of the points I will be making might, sound a bit harsh and go over your head. If they do try to improve the parts of the code you do understand, and instead post a follow up question.
Asking a question on a SE, site is a great way to learn. However another is searching for answers beforehand. In fact all three questions in your bullet list could be googled / found in the manual.
As you know CodeReview has it's own hangman tag -- since you tagged your own question with the tag. This means people have asked hangman related questions earlier, and you could have browsed these questions before posting your own. As an example, even I have asked a question about hangman. Here is another very similar question.
This is not to discredit you for asking, however drawing suggestions and inspiration from similar problems is a fantastic way to learn.
As to pick the lowest hanging fruits. Consistency is a key part of programming, as it greatly increases readability. It also tells a bit about the coders eye for detail. Lets' look at how you have named your global constants:
MAX
num
num_string
words
WORD_TO_GUESS
HIDDEN
LETTERS_GUESSED
Do you see the inconsistency? Pep 8 recommends the following naming-conventions:
CAPITALIZED_WITH_UNDERSCORES
for constants
UpperCamelCase
for class names
lowercase_separated_by_underscores
for other names
General structure
A very rough sketch of how a standard python program should look like is as follows
import
global CONSTANTS
global VARIABLES
functions
classes
if __name__ == '__main__':
main()
All of your code should be split into short simple functions, which each serve a single purpose. You should in general have as little code as possible floating around that is not part of a function. This includes minimizing the need for global
variables and constants. There are some exceptions to this rule, however your code is not one of them.
Some more pointers
As earlier answers stated you can change your dictionary word list into
WORD_LIST = [
'sergeanty',
'amphipneustic',
'hypochil',
'grus',
'depressed',
'distortedly',
'unselected',
'crakow',
'preendorsing',
'convenient'
]
As mentioned earlier you use too many global variables. This
MAX = len(HANGMAN) - 1
num = randint(1, 10)
num_string = str(num)
words = 'rand_word_{}'.format(num_string)
WORD_TO_GUESS = WORD_LIST[words]
HIDDEN = ['_'] * len(WORD_TO_GUESS)
LETTERS_GUESSED = []
can be shortened into
MAX = len(HANGMAN) - 1
word_to_guess = random.choice(WORD_LIST)
hidden = ['_'] * len(word_to_guess)
letters_guessed = set()
Where I have used the choice
function from the random
module. For an example of use see below
>>> import random
>>> a = ["Stack", "Overflow", "rocks"]
>>> print(random.choice(a))
rocks
for a more detailed explanation see the second bullet point at the start.
You might also have noticed that some of the variables are not CAPITALIZED
that is because they do not need to be global variables, and can be moved into the main function.
def play_hangman():
hang_size = 0
word_to_guess = random.choice(WORD_LIST)
hidden = ['_'] * len(word_to_guess)
letters_guessed = set()
Note how I also conveniently changed the name of your main()
function. Naming variables, functions and classes in programming is one of the more difficult things to do. A general rule of thumb is that the names should be clear, distinct and succinct in that order. As others have pointed out main()
only achieves the third, which should be the last thing to worry about.
Jokes aside as a rule you should never except the user to use the correct input syntax. Instead you should build in checks, exceptions to handle these errors.
For an example your code only checks once if I have tried the letter before.
if user_guess in LETTERS_GUESSED:
print 'You already guessed that.. PAY ATTENTION!'
user_guess = raw_input('Guess a letter dude: ')
One way to fix this is using a while
loop instead
while user_guess in LETTERS_GUESSED:
print 'You already guessed that.. PAY ATTENTION!'
user_guess = raw_input('Guess a letter dude: ')
What happens if i decide to try the letter banana
or the letter
? Your function does nothing to handle it. What If I try the more common mistake to try A
then a
? All of this can be fixed by putting the get_user_input
into it's own function
ALPHABETH = 'abcdefghijklmnopqrstuvwxyz'
def guess_letter(letters_guessed):
while True:
guess = raw_input('Guess a letter dude: ').lower()
if guess not in ALPHABETH:
print guess, 'is like totally not in the alphabet dude, try again!'
elif guess in letters_guessed:
print 'You already guessed {}.. PAY ATTENTION!'.format(guess)
else:
return guess
Do note that I did not have to convert ALPHABETH
into a list. Python can compare strings just as well as lists. Also notice how I used Python string lower() method to ensure that the user input was all lowercase
.
Continuing on
The next bit of your code could then be written as
while hang_size < MAX:
print str(hidden)
user_guess = guess_letter(letters_guessed)
letters_guessed.add(user_guess)
if user_guess in word_to_guess:
Where we again used that Python can compare strings
just fine. The add
is used instead of append
since letters_guessed
is now a set
and not a list
. Why am I using sets, you ask?
When you want to store some values which you'll be iterating over,
Python's list constructs are slightly faster. However, if you'll be
storing (unique) values in order to check for their existence, then
sets are significantly faster.
For words as short as your the difference is negligible, however it is a good practice nevertheless.
for num in range(len(word_arr)):
This can be improved using Pythons enumerate()
function that adds a counter to an iterable.
for i, letter in enumerate(word_to_guess):
print i, letter
if user_guess == letter:
hidden[i] = letter
if hidden.count('_') == 0:
print 'You win! Finally you did something right!'
return
I really see no point in turning the for
-loop into a zip
list. Yes, it is shorter. However for a novice it is much harder to understand what is going on.
Winning condition and return
The statement
if HIDDEN.count('_') == 0:
print 'You win! Finally you did something right!'
quit()
Is weird. The if HIDDEN.count('_') == 0:
is cryptic at best. A good practice is to check what you really want to check.. You are not interested in how many _
's are left, but rather if the user has guessed the correct word. This can be checked as follows
user_guessed_word_spaced = ' '.join(hidden)
word_to_guess_spaced = ' '.join(word_to_guess)
if user_guessed_word_spaced == word_to_guess_spaced:
At a first glance this might seem convoluted. However in the actual code word_to_guess_spaced = ' '.join(hidden)
is only declared once. Secondly using a spaced string ' '.join()
instead of ''.join()
allows us to directly print
the output at the start of the while
-loop.
Your way to exit the function is very intuitive for a beginner, however it should be avoided like the plague for a beginner. A more natural way is to use return
instead.
quit
raises the SystemExit
exception behind the scenes.
Furthermore, if you print it, it will give a message:
>>> print (quit)
Use quit() or Ctrl-Z plus Return to exit
>>>
This functionality was included to help people who do not know Python.
After all, one of the most likely things a newbie will try to exit
Python is typing in quit.
Nevertheless, quit should not be used in production code. This is
because it only works if the site module is loaded. Instead, this
function should only be used in the interpreter.
I have made some adjustments to accommodate for this. This includes having only a single return
statement, as well as some better handling of printing the winning conditions.
There are more things to nitpick, which I did not try to fix/improve
- You should always comment your code. You can never be sure you will understand your code 6months down the road. A common way to do this is with PEP 257 -- Docstring Conventions.
- Your creation of strings could be improved.
- Your list of words is short, and a simple fix is to use a larger list from the internet.
- You could also make a short menu to congratulate the user, or ask if one wants to play again.
Code
Using all of these small improvements leads to the following code.
import random
WORD_LIST = [
'sergeanty',
'amphipneustic',
'hypochil',
'grus',
'depressed',
'distortedly',
'unselected',
'crakow',
'preendorsing',
'convenient'
]
HANGMAN = (
"""
x-------x
""",
"""
x-------x
|
|
|
|
|
""",
"""
x-------x
| |
| 0
|
|
|
""",
"""
x-------x
| |
| 0
| |
|
|
""",
"""
x-------x
| |
| 0
| /|\\
|
|
""",
"""
x-------x
| |
| 0
| /|\\
| /
|
""",
"""
x-------x
| |
| 0
| /|\\
| / \\
|
"""
)
MAX = len(HANGMAN) - 1
ALPHABETH = 'abcdefghijklmnopqrstuvwxyz'
def guess_letter(letters_guessed):
while True:
guess = raw_input('Guess a letter dude: ').lower()
if guess not in ALPHABETH:
print guess, 'is like totally not in the alphabet dude, try again!'
elif guess in letters_guessed:
print 'You already guessed {}.. PAY ATTENTION!'.format(guess)
else:
return guess
def play_hangman():
hang_size = 0
word_to_guess = random.choice(WORD_LIST)
word_to_guess_spaced = ' '.join(word_to_guess)
hidden = ['_']*len(word_to_guess)
letters_guessed = set()
user_guessed_word_spaced = ' '.join(hidden)
print "HANGMAN!"
while hang_size < MAX:
print
print user_guessed_word_spaced
user_guess = guess_letter(letters_guessed)
letters_guessed.add(user_guess)
if user_guess in word_to_guess:
print "\nYeah yeah.. We're all impressed.. {} is in the word woohoo..".format(user_guess)
for i, letter in enumerate(word_to_guess):
if user_guess == letter:
hidden[i] = letter
user_guessed_word_spaced = ' '.join(hidden)
if user_guessed_word_spaced == word_to_guess_spaced:
print
print word_to_guess
break
else:
print "{}.. Really? That's the best you can do.. Not in my word..".format(user_guess)
hang_size += 1
print HANGMAN[hang_size]
return user_guessed_word_spaced == word_to_guess_spaced
if __name__ == '__main__':
is_winner = play_hangman()
if is_winner:
print '\nYou win! Finally you did something right!'
else:
print '\nGAME OVER'