• Top Members
    Reps
    Posts
  • 834 Replies
    2585 Replies
  • 716 Replies
    2025 Replies
  • 331 Replies
    1928 Replies

PyMan, a Python game of Hangman [Tutorial]

User avatar
comathi
Coding God
Posts: 1242

PyMan, a Python game of Hangman [Tutorial]

Sat Jul 13, 2013 1:28 am

It's been a while since I've written a tutorial, so I decided I might as well do it in a new language :)

Since there aren't very many Python tuts on the forum, I won't presume knowledge of the language, but will instead explain parts of the syntax that may be unfamiliar to most of you.

What I will presume, however, is that you have Python 3.x installed (very important lol). If you don't know how to get everything going, you can check out my other tutorial here: Getting started with Python (Coming soon)

Now that those few things are cleared out, let's get started, shall we. The goal of this tutorial will be to make a game (because I believe games are the best way to practice a language) of hangman. Of course, I won't be diving into GUIs with Python just yet, so this will be a command-line game. Still very fun, though :)

Image

Launch your favourite IDE, or Notepad, if you wish, and save a file as "hangman.py". Python doesn't require you use the py extension to execute files, but some IDEs use the extension to determine which syntax to highlight... and it's just good practice anyway.

This is a small script, so there won't be much code, and only one function. First and foremost, however, we'll want to import a few modules:

Code: Select all

import random
import math
Make sure you type it exactly as it is above. Python is a case-sensitive language. The above two statements are the Python equivalent to Imports in VB.NET, except you can't use a module in Python unless you've explicitely imported it first. The random module will, as you may already have guessed, generate random numbers, while the math module will allow us to perform math outside of the basic operators.

Next comes our global variables (they're global by default because they're not inside another function). We have four of there:

Code: Select all

word_list = ['programming','python','hangman','codenstuff','tutorial','potato'] #A list of words from which the script can choose
already_used = "" #Will contain the letters that have already been guessed (to prevent double-guessing a letter)
mistakes = 0 #The number of mistakes the player is at
figures = ["""\
          /---|    {0}
          |
          |
          |
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |
          |
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |   |
          |
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |  /|
          |
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |  /|\\
          |
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |  /|\\
          |  /
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |  /|\\
          |  / \\
          |
          -
          {1}"""]
The first variable, word_list is what we call a list. It's similar to Arrays in other languages, but it comes with lots of handy methods and, unlike some languages, can contain any type of variable. You'll also notice that none of our variables have been declared with a type. That's because Python is a dynamically-typed language. word_list is a list now, but the next time we assign it a value, we could make it become a String, a Number or any other data type Python supports.
figures is another example of a list. It contains variables of type String, and as you can see, these strings span multiple lines. This is because we used the triple quote (""" or ''') to enclose each string. Make sure you get the indentations right in the Strings, or else it'll look all messed up when it's printed to the screen.

This may already be obvious to you, but everything following a pound (#) on a line is a comment.

Now that our variables are declared, the fun can begin. We'll start by declaring our first and only function for this script:

Code: Select all

def start_game():
def means we're defining a function, while start_game is the name of said function. We have empty parentheses because our function takes no parameter, followed by a colon, which ends the function declaration statement.

Note: Unlike most languages, Python doesn't delimit blocks of code with brackets or closing tags, instead, each block of code is indented. And the indentations must be consistend throughout the script (in accordance with PEP8, I use four spaces per indentation). For example, the code contained in the level directly beneath our function will be indented once. Code that will be inside another structure within that same function will be indented twice (the code inside an if structure, for example).
To make it easier for you guys, I'll put a number of > "greater than" symbols corresponding to the number of spaces in front of each line. If there's nothing in front of a line, then the line requires no indentation. DO NOT copy those symbols in your code, but replace them with spaces.

We can now proceed to add two (indented) statements that will be used to reset the game once the player either wins or loses:

Code: Select all

>>>>#Variables are reset
>>>>mistakes = 0
>>>>already_used = ""
Now it's time to chose a random word from the word_list variable we declared earlier:

Code: Select all

>>>>#A new word is chosen and the correct figure is displayed    
>>>>chosen_word = word_list[math.floor(len(word_list) * random.random())].lower()
>>>>visible_word = "_ " * len(chosen_word)
This is where the modules we imported earlier come in handy. First of all, we want to get an item in word_list, so we write the variable followed by a bracked (whatever is inside that bracket will correspond to the index of the item we want to fetch).
Since we want a random number, we'll multiply random.random() (which returns a number between 0 and 1 exclusively) by the length (or number of items) in word_list and get the integer part of that number with math.floor(). Also, to avoid mixing upper and lowercase characters, we'll convert the whole word to lowercase using lower().

The second statement simply affects a number of underscores followed by a space to the variable visible_word. This is so we can "hide" the word from the user until he has guessed all the letters. Notice we're using a multiplication symbol with a String. Strange, isn't it? Python allows you to do that. It basically comes back to repeating that same string ("_ ") x number of times (x being the number after the * symbol). So "_ " * 4 will give us "_ _ _ _ ".

Just to make things a little prettier, we're going to print some text, the title of the game:

Code: Select all

>>>>#Print the title (just cuz :-) )
>>>>print("----------PyMan Hangman----------\n")
Now here comes the most important part of the game: the user will be asked to guess a letter, the script will check if that letter is in the random word, and the hangman will be printed on screen. We'll start with that: printing the hangman:

Code: Select all

>>>>while True:
>>>>>>>>print(figures[mistakes].format(already_used, visible_word)+"\n")
while True: creates an infinite loop (you'll see why later). Notice how the second instruction is indented four more spaces than the loop. As for the instruction itself, it simply prints the String in figures at the index corresponding to the number of mistakes the user has made. The format() function allows us to replace the numbers withing curly braces ({}) and the curly braces themselves with the values inside parentheses (already_used and visible_word). Look again at the figures in figures, you'll see a {0} in the top right corner of every figure, as well as a {1} in the bottom of every figure. These will be formatted and replaced by the values in parentheses.

Now, before we ask the user for input, we'll check to see if he's already won or lost. There's a simple way of doing this. If visible_word (ie, the text that is displayed to the user) contains no underscores, it means he/she has guessed all the letters in the word, therefore has won:

Code: Select all

>>>>>>>>if not "_" in visible_word:
>>>>>>>>>>>>print("Congratulations, you guessed the word",chosen_word,"correctly!")
>>>>>>>>>>>>input()
>>>>>>>>>>>>start_game()
"_" in visible_word simply checks if there's at least one occurence of the underscore in the String contained in visible_word. If that's not the case (the user won), we display a congratulatory message. The commas in the print() function serve to deliit String parameters. A space, as well as the String itself, are concatenated to the rest.

input() would normally ask for input from the user, but since we didn't put any prompt withing the parentheses, it'll display a blank line and wait for the user to press ENTER, then call start_game() which will restart the game.

Another possibility is that the user lost. This is determined by the number of mistakes the user has made. In our case, that number is 6. So let's add another condition to our if structure.

Code: Select all

>>>>>>>>elif mistakes == 6:
>>>>>>>>>>>>print("You have lost. The word was",chosen_word,".")
>>>>>>>>>>>>input()
>>>>>>>>>>>>start_game()
Python's version of else if, elseif or however you write it in another language is elif, but the concept stays the same. Like other languages, the single equal sign serves to affect a value, while the double equal sign (==) serves as a comparison operator.

Much like the winning case, the losing case will display a message, followed by the input() function to wait for the user to press ENTER before restarting the game.

If neither of these conditions apply, it means the game should continue. It's not time to prompt the user for a letter and continue on:

Code: Select all

>>>>>>>>else:
>>>>>>>>>>>>guess = input("Guess a letter: ").lower()
In this case, we actually do put a prompt in the input() function. Just to make sure the user doesn't accidentally get an error after inputting a capital letter, we'll convert everything to lowercase again with the lower() function.

Next comes validation of the user's guess. We want to make sure it's a letter (since everything is lowercase, we'll make sure it's between lowercase 'a' and lowercase 'z') and that the user didn't try to guess more than one letter at a time (you could add an option for the user to guess the whole word or part of it, but we won't conver that in this tutorial):

Code: Select all

>>>>>>>>>>>>if len(guess) == 1 and ord(guess) >= 97 and ord(guess) <= 122:

>>>>>>>>>>>>else:
>>>>>>>>>>>>>>>>print("You must enter only one valid character, try again")
There's a couple of new things here. First of all, the len() function, which can be called on any sequence (sequences in Python include Strings, Lists and Sets), returns the length of the sequence, in this case the number of characters in the user's guess. Secondly, we have the ord() function. This takes a String of length 1, and returns it's ASCII value. Lowercase 'a' has an ASCII code of 97, while lowercase 'z' has a code of 122. Making sure the user's answer is somewhere between therefore confirms it's a lowercase letter.

Interesting fact: since ord() take a length 1 String as it's parameter, feeding it a String any longer that that (ie, when the user enters an answer longer than one character) would cause the program to crash. However, Python has this interesting and very useful property of not evalutating the rest of an if statement if the first AND condition evaluates to false. This makes sense, as AND will invariably result in False if a condition on either side of it is false. Therefore, by placing the len() function before the AND, if the length of the guess is more than 1, ord() is never called and can therefore not crash the program.

Let's continue now. Once we know the user has given us a valid character, we want to make sure he hasn't already used it. This is where the already_used variable comes in handy. We'll use something we've already seen a bit earlier: checking for a character in a String, to validate this. The following code should go inside the previous if structure, before the else:

Code: Select all

>>>>>>>>>>>>>>>>if not guess in already_used:


>>>>>>>>>>>>>>>>else:
>>>>>>>>>>>>>>>>>>>>print("You have already used that letter!")
Inside that structure, we'll put the following code (again, before the else:)

Code: Select all

>>>>>>>>>>>>>>>>>>>>if guess in chosen_word:


>>>>>>>>>>>>>>>>>>>>else:
>>>>>>>>>>>>>>>>>>>>>>>>mistakes += 1
>>>>>>>>>>>>>>>>>>>>>>>>print("Sorry, that is incorrect!")

>>>>>>>>>>>>>>>>>>>>#Adds the guessed letter to the guessed list
>>>>>>>>>>>>>>>>>>>>already_used += guess
Again, we check to see if the guessed letter is in the chosen word. If it is not, the number of mistakes is incremented by one and a message is printed to the screen. Whether or not the letter is present in the word, the letter will be added to already_used (that's why it's outside the if structure).

The following must be added before the else:, inside the above if structure:

Code: Select all

>>>>>>>>>>>>>>>>>>>>>>>>new_visible = ""
>>>>>>>>>>>>>>>>>>>>>>>>for c in range(len(chosen_word)):
Here we created a new variable which, as you'll see, will be equivalent to visible_word to which we add the guessed letter where (and if) it occurs in the word.
We also start a for loop, which you should be familiar with. range() returns a series of numbers, in this case from 0 to the length of chosen_word (exclusively), through which the loop will iterate. Inside this loop we'll insert the following code:

Code: Select all

>>>>>>>>>>>>>>>>>>>>>>>>>>>>if guess == chosen_word[c]:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>new_visible += guess + " "
>>>>>>>>>>>>>>>>>>>>>>>>>>>>else:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>new_visible += visible_word[c*2:c*2+2]
For each letter in the chosen word, we check if the guessed letter is the same. If it is, we append to new_visible that letter and a space.
If it is not, however, there is still the possibility that the letter has already been found. So instead of simply appending an underscore and a space, we use visible_word and extract whatever characters are between the indexes on either side of the colon within the brackets ([x:y]). In Python, this is known as slicing. It creates a substring of the String on which we performed the slicing.

There's one last thing we need to do now: make visible_word equal to new_visible. The following line of code will be outside the for loop, so it will be indented the same as new_visible="":

Code: Select all

>>>>>>>>>>>>>>>>>>>>>>>>visible_word = new_visible
Oops! I lied. There's actually a few other lines of code you'll want to add. First of all, at complete end of your function, under the while loop and indented four spaces:

Code: Select all

>>>>return 0
Just to make sure your function returns a value (to tell the interpreter it exited successfully).

And finally:

Code: Select all

if __name__ == "__main__":
>>>>start_game()
This is outside your function, so the if is not indented. When you run the script from the command line with Python, __name__ will be equal to __main__, so start_game() will be called and your script will work properly :)

That's it! You're now ready to run your script (again, if you're not sure how, you can refer to my 'Getting Started tutorial'). Have fun adding your own words to the list, and challenging yourself or friends to guess them lol cooll;

Full Code

Code: Select all

import random
import math

word_list = ['programming','python','hangman','codenstuff','tutorial','potato'] #A list of words from which the script can choose
already_used = "" #Will contain the letters that have already been guessed (to prevent double-guessing a letter)
mistakes = 0 #The number of mistakes the player is at
figures = ["""\
          /---|    {0}
          |
          |
          |
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |
          |
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |   |
          |
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |  /|
          |
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |  /|\\
          |
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |  /|\\
          |  /
          |
          -
          {1}""",
          """\
          /---|    {0}
          |   O
          |  /|\\
          |  / \\
          |
          -
          {1}"""]

def start_game():
>>>>#Variables are reset
>>>>mistakes = 0
>>>>already_used = ""

>>>>#A new word is chosen and the correct figure is displayed    
>>>>chosen_word = word_list[math.floor(len(word_list) * random.random())].lower()
>>>>visible_word = "_ " * len(chosen_word)

>>>>#Print the title (just cuz :-) )
>>>>print("----------PyMan Hangman----------\n")

>>>>while True:
>>>>>>>>print(figures[mistakes].format(already_used, visible_word)+"\n")
>>>>>>>>if not "_" in visible_word:
>>>>>>>>>>>>print("Congratulations, you guessed the word",chosen_word,"correctly!")
>>>>>>>>>>>>input()
>>>>>>>>>>>>start_game()
>>>>>>>>elif mistakes == 6:
>>>>>>>>>>>>print("You have lost. The word was",chosen_word,".")
>>>>>>>>>>>>input()
>>>>>>>>>>>>start_game()
>>>>>>>>else:
>>>>>>>>>>>>guess = input("Guess a letter: ").lower()
>>>>>>>>>>>>if len(guess) == 1 and ord(guess) >= 97 and ord(guess) <= 122:
>>>>>>>>>>>>>>>>if not guess in already_used:
>>>>>>>>>>>>>>>>>>>>if guess in chosen_word:
>>>>>>>>>>>>>>>>>>>>>>>>new_visible = ""
>>>>>>>>>>>>>>>>>>>>>>>>for c in range(len(chosen_word)):
>>>>>>>>>>>>>>>>>>>>>>>>>>>>if guess == chosen_word[c]:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>new_visible += guess + " "
>>>>>>>>>>>>>>>>>>>>>>>>>>>>else:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>new_visible += visible_word[c*2:c*2+2]
>>>>>>>>>>>>>>>>>>>>>>>>visible_word = new_visible
>>>>>>>>>>>>>>>>>>>>else:
>>>>>>>>>>>>>>>>>>>>>>>>mistakes += 1
>>>>>>>>>>>>>>>>>>>>>>>>print("Sorry, that is incorrect!")

>>>>>>>>>>>>>>>>>>>>#Adds the guessed letter to the guessed list
>>>>>>>>>>>>>>>>>>>>already_used += guess

>>>>>>>>>>>>>>>>else:
>>>>>>>>>>>>>>>>>>>>print("You have already used that letter!")
>>>>>>>>>>>>else:
>>>>>>>>>>>>>>>>print("You must enter only one valid character, try again")
>>>>return 0

if __name__ == "__main__":
>>>>start_game()
Download the script

This file is hosted off-site.


Post Reply

Return to “Tutorials”