The is the first part of a 5 part series. You can view the other parts
Hangman games are a popular programming game that many of my students are asked to implement. I decided to do one in Python using the PyQ5 library.
Before working on this tutorial, you may need to install pyqt5 on your system.
pip3 install pyqt5
Before we begin…
We need to create a file that holds our list of words. Here is one that can help you get started.
Kitties Python Foliage Assorted Soda Stream Democracy Blog Selection Insertion
This file will be called words.txt
throughout this tutorial. It needs to get placed in the same folder as the rest of our scripts.
Application Logic
It is always advisable to seperate the logic of your application from the display. This let’s us reuse our application code but plug it into different displayes. For example, this tutorial uses PyQt5, but in theory, we may wish to have a Tkinter (the standard Python GUI toolkit) version of this program also. When we seperate application logic from display, we can write different clients for the same program.
Let’s begin with the code first and then walk through it. Here is the core application logic for our hangman game.
from random import randint class Hangman: def __init__(self, word_list_file, allowed_guesses=15): self.words = self.read_word_file(word_list_file) self.allowed_guesses = allowed_guesses self.start_game() self.wins = 0 def start_game(self): self.secret_word = self.pick_secret_word() self.display_letters = self.create_display_letters() self.guessed_letters = [] self.guesses = 0 @staticmethod def read_word_file(word_list_file): word_list = [] with open(word_list_file, 'r') as f: for line in f: word_list.append(line.rstrip()) return word_list def pick_secret_word(self): index = randint(0, len(self.words) - 1) return self.words[index].upper() def create_display_letters(self): letters = [] for _ in self.secret_word: letters.append('-') return letters def guess_letter(self, letter): if letter not in self.guessed_letters: guess_wrong = True self.guessed_letters.append(letter) for i in range(len(self.secret_word)): if letter == self.secret_word[i]: guess_wrong = False self.display_letters[i] = letter if guess_wrong: self.guesses += 1 def check_win(self): word = ''.join(self.display_letters) if word == self.secret_word and self.guesses self.allowed_guesses
Our hangman program needs to hold data and have functions that operate on the data. This suggests that we should use Object Orientated Programming (OOP). Let’s begin by pointing out the data Hangman needs to keep track of first.
- wins
- secret word
- display letters
- guessed_letters
- guesses
- allowed guesses
Here is a list of behaviors that Hangman needs to perform to work on the data.
- __init__
- start_game
- read_word_file
- pick_secret_word
- create_display_letters
- guess_letter
- check_win
- check_lose
Let’s take each function one at a time…
__init__
__init__ is a function that all Python classes have that is used to initalize an object. Programmers from other OOP languages such as Java or C++ may refer to this as a constructor method.
Here is our __init__ code
def __init__(self, word_list_file, allowed_guesses=15): self.words = self.read_word_file(word_list_file) self.allowed_guesses = allowed_guesses self.start_game() self.wins = 0
You will notice that our __init__ function requires a word_list_file which is the path to a file that contains our list of words. The first thing this function does is reads the words_list_file using the read_word_file function. That function returns all of the words in the file as a list. One the next line, we set the self.allowed_guesses variable to the supplied allowed_guesses parameter. Then we call self.start_game() to initialize the game.
start_game
start_game initializes our hangman game. The reason this function is seperate from __init__ is so that clients can start a new game of hangman by calling this method, rather than recreating a new Hangman object. Here is the code for the start_game fucntion.
def start_game(self): self.secret_word = self.pick_secret_word() self.display_letters = self.create_display_letters() self.guessed_letters = [] self.guesses = 0
Mostly speaking, we are just initializing our class variables in this function. The first thing we do is pick a secret word. We use self.pick_secret_word() which randomly picks a word from our word list and returns it. Next we need to create a list that holds the user’s correct guesses. At first, we want it to contain only dash (‘-‘) characters, so we use self.create_display_letters to build that list and return it to this function.
Our next line creates an empty list to hold the user’s guesses followed by a line to set their incorrect guesses to zero.
read_word_file
We met read_word_file in __init__. Now let’s talk about how it works. Here is the code.
@staticmethod def read_word_file(word_list_file): word_list = [] with open(word_list_file, 'r') as f: for line in f: word_list.append(line.rstrip()) return word_list
This method is decorated with @staticmethod which indicate the method belongs to the class Hangman rather than individual Hangman objects. The first thing we do is create an empty word_list object that holds our words. The next thing we do is open word_list_file in read mode using the statement with open (word_list_file, 'r') as f
. That statement opens the file and assigns it the variable f. It will also handle closing the file when we are done.
Now we need to read the file line by line. We do that with the for loop. As we go through each line in the file, we need to strip off any trailing ‘\n’ characters, which what line.rstrip()
does. At this point, we can add our word to the word_list. When we are done reading the file, we can return the word_list to the calling method.
pick_secret_word
We met pick_secret_word in the start_game function. Here is the code for this method followed by an explanation.
def pick_secret_word(self): index = randint(0, len(self.words) - 1) return self.words[index].upper()
(note: see Random Int—Python for a post dedicated to picking random items from a list)
In this code, we need to pick a random number between 0 and the number of words – 1. We need to use -1 because lists are zero based. Once we have a number, we return a secret word from the words list and convert it to an upper cased string for easy comparisons later on in the program.
create_display_letters
Now that we have picked our secret word, we can use create_display_letters to assign dash (‘-‘) characters for each unknown letter in the game. Latter on, we will replace the dashes with correct guesses. Here is the code for create_display_letters.
def create_display_letters(self): letters = [] for _ in self.secret_word: letters.append('-') return letters
In this function, we create a letters list object that is empty. Then we go through each letter in self.secret_word. We use the underscore (‘_’) to indicate that we aren’t actually interested in each letter so there is no need to assign to a variable. At each iteration of the loop, we just append a dash to the letters list. When this method returns, we have a list that is the same length as secret word but it is filled with dashes.
guess_letter
This function is the main entry point for clients that use this class. The idea here is that the client get’s the user’s guess from some sort of input (note that this class isn’t concerned with how that input is gathered from the user) and the guess is passed to this guess_letter function. Here is the code followed by an explanation.
def guess_letter(self, letter): if letter not in self.guessed_letters: guess_wrong = True self.guessed_letters.append(letter) for i in range(len(self.secret_word)): if letter == self.secret_word[i]: guess_wrong = False self.display_letters[i] = letter if guess_wrong: self.guesses += 1
Again, this begins with a letter variable that is supplied by the caller. We don’t care at this point how the program got the letter from the user. Now, first we need to check that that the letter wasn’t already guessed. If it was, we just ignore the guess.
Now, we are creating a boolean variable that is set to True to assume that they guessed wrong. The next line adds the guess to the guessed_letters list. Next, we are going to go through self.secret_word one letter at a time using indexes.
Inside of the loop, we check if the guessed letter matches the current letter in self.secret_word at index ‘i’. If we have a match, we set gress_wrong to False, and we replace the dash character in self.display_letters at index to the letter.
Finally, we check if they have guessed right or wrong. If guess_wrong is True, we update the self.guesses by 1 to indicate that they have guessed wrong.
check_win
The client of this class needs a way to check if the user won the game. We use the check_win function as a way to report back to the client if the user has won the game. Here’s the code followed by an explanation.
def check_win(self): word = ''.join(self.display_letters) if word == self.secret_word and self.guesses <= self.allowed_guesses: self.wins += 1 return True else: return False
What we are doing here is assembling the user's guesses into a single string using ''.join(self.display_letters
. The string will end up being something like ‘K-tt-es’ if they haven’t guessed all of the letters in the word yet, but if they have guessed everything right, it would be ‘Kitties’.
The next thing to do is see if word is equal to self.secret_word. As long as they match and the user hasn’t exceeded the number of allowed guesses, they win the game. We increment self.wins by 1 and return True. Alternatively, we return False to indicate they have not one the game.
check_lose
Our final function in Hangman is check_lose. This just checks if they have lost the game by exceeding the number of allowed incorrect guesses.
def check_lose(self): return self.guesses > self.allowed_guesses
Conclusion
The Hangman class demonstrates many important application concepts in Python. It opens files, picks numbers randomly, and tracks the progress of a game. An extremely important concept is that this class is not tied to any user interface. Instead, it contains the application logic used to play a game of hangman, but allows client modules to import it and use it as needed. The next part of this tutorial will focus on a GUI client that uses this class.
For create display letters, you could have just used: list(‘-‘*len(self.secret_word)) or [‘-‘ for _ in self.secret_word]
LikeLike