Step up your coding game with AI-powered Code Explainer. Get insights like never before!
Hangman is a pretty popular game project for beginner programmers. It is a word guessing game by guessing each secret word's letter one by one and for every error guess you got, a man's body part is drawn hanging in the gallow. If the figure of a man is finished (head, body, arms, and legs are drawn), that means you are out of guesses and the game is over.
In a previous tutorial, we've made a text-based hangman game. However, in this tutorial, we'll learn about how to create a graphical hangman game using Pygame in Python.
Let's start by installing the Pygame module with pip
or pip3
in our terminal:
$ pip install pygame
Then create hangman.py
in your app directory. I name mine hangman-gui
. Another file we need is the words.txt
which contains words where we pick a secret word to guess.
Now head to hangman.py
and import pygame
module, pygame.locals
for font and other stuff, os
for accessing the words.txt
file, random
module for random word picking, and ascii_letters
to make sure the player's guess is ASCII.
# hangman-gui/hangman.py
import pygame
from pygame.locals import *
import os
import random
from string import ascii_letters
Let's check first if everything will works:
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Courier New", 40)
running = True
screen = pygame.display.set_mode((400, 500))
screen.fill("white")
message = font.render("hello world", True, ("red"))
screen.blit(message,(0,0))
pygame.display.update()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
Let's run our app by typing python hangman.py
in our terminal in the hangman-gui
directory:
Now that we see Pygame is working we can delete all the codes (left the imports) and type:
pygame.init()
pygame.font.init()
screen = pygame.display.set_mode((400, 500))
pygame.display.set_caption("Hangman")
The pygame.init()
initiates the game while the pygame.font.init()
initiates the fonts so we can use the Pygame's built-in fonts. The screen
creates the game window with the caption "Hangman" at the top.
Let's start coding by adding the Hangman
class and inside it, make a function named __init__()
which needs components such as self.secret_word
, self.guessed_word
, self.wrong_guesses
(a list of false guess), self.wrong_guess_count
, self.taking_guess
, and self.running
:
class Hangman():
def __init__(self):
with open("./words.txt", "r") as file:
# picks secret word and passing it's length
words = file.read().split("\n")
self.secret_word = random.choice(words)
# passing secret word's length for making letter blanks
self.guessed_word = "*" * len(self.secret_word)
self.wrong_guesses = []
self.wrong_guess_count = 0
self.taking_guess = True
self.running = True
As you noticed, I put two boolean variables, the self.taking_guess
and the self.running
, with the same value.
The self.running
manage to run the game. While true, it will render the game window continuously so it won't automatically collapse. When the user hits the exit button of the window, the value of the self.running
will turn to False
causing to collapse the game window to.
On the other hand, the self.taking_guess
controls the ability to take guesses. While its value is True
, it allows the user to enter a guess and when the game is over (the user had won/lost), its value will turn to False
, not allowing the player to make guesses anymore.
Let's also add other game components colors, font, etc. we were using later.
class Hangman():
def __init__(self):
...
self.background_color = (155, 120, 70)
self.gallow_color = (0,0,0)
self.body_color = (255,253,175)
self.font = pygame.font.SysFont("Courier New", 20)
self.FPS = pygame.time.Clock()
Now let's proceed to the other parts of the game.
The gallow is where our man figure will be hanging. To draw the gallow, we are using Pygame Rect
s. The Rect
is short for rectangle, with the syntax of pygame.Rect(X, Y, Width, Height))
. To draw it on the game surface, you can do pygame.draw.rect(screen, color, pygame.Rect(X, Y, Width, Height))
then pygame.display.flip()
to update the screen so we can see the Rect
.
Inside the Hangman
class, make a function named _gallow()
. This function will draw the gallow image using the rects:
def _gallow(self):
stand = pygame.draw.rect(screen, self.gallow_color, pygame.Rect(75, 280, 120, 10))
body = pygame.draw.rect(screen, self.gallow_color, pygame.Rect(128, 40, 10, 240))
hanger = pygame.draw.rect(screen, self.gallow_color, pygame.Rect(128, 40, 80, 10))
rope = pygame.draw.rect(screen, self.gallow_color, pygame.Rect(205, 40,10, 30))
Each of these game configs will draw an image of a gallow and pygame.display.flip()
pass it to the game window, which we'll put inside the while loop later. The reason is if we put multiple flip()
or update()
per function and call them all together, it will cause our whole game to over and over while running.
Now that we have a gallow, let's draw the man figure part by part which represents how many wrong guesses the player already has. Create a _man_pieces()
function inside the class which we call every time we want to draw the man's body parts for every wrong guess. The man is supposed to be placed close to the gallow's rope
so it will look like he is hanging in it:
def _man_pieces(self):
head = pygame.draw.circle(screen, self.body_color, [210, 85], 20, 0)
body = pygame.draw.rect(screen, self.body_color, pygame.Rect(206, 105, 8, 45))
r_arm = pygame.draw.line(screen, self.body_color, [183, 149], [200, 107], 6)
l_arm = pygame.draw.line(screen, self.body_color, [231, 149], [218, 107], 6),
r_leg = pygame.draw.line(screen, self.body_color, [189, 198], [208, 148], 6),
l_leg = pygame.draw.line(screen, self.body_color, [224, 198], [210, 148], 6)
Just like the gallow, each this piece will draw a figure of a human inside the game window after calling the flip()
.
Now we'll try to run the game to see what the gallow and the man looks like. Let's create another function in the class and name it main()
. This function controls running and/or stopping the game:
def main(self):
# game's main components (no need to update)
screen.fill(self.background_color)
self._gallow()
self._man_pieces()
while self.running:
for self.event in pygame.event.get():
if self.event.type == pygame.QUIT:
self.running = False
pygame.display.flip()
self.FPS.tick(60)
pygame.quit()
Inside the while
loop of the main()
function, we put a for
loop that catches all types of events so we can catch for every second all the events happening inside the game window (mouse movements, keys pressed, mouse clicks, etc.). We also put the flip()
and the clock tick so it will refresh all the animation inside the while
loop for every second at a normal speed.
We put the screen.fill()
, the self._gallow()
, and the self._man_pieces()
ahead and outside of the while
loop because these animations do not need to refresh per frame so our code will run faster.
It is also important to put pygame.quit()
after the while
loop or else we'll catch some error.
Below the class and outside of it, let's call the game and then run the hangman.py
in our terminal to see what it looks like:
if __name__ == "__main__":
h = Hangman()
h.main()
The man was properly placed in the gallow as we expected. Let's create the other parts of the game for handling guesses from keys pressed.
Create another function in the class and name it _guess_taker()
. This function will be responsible for taking guesses the player submitted:
def _guess_taker(self, guess_letter):
if guess_letter in ascii_letters:
if guess_letter in self.secret_word and guess_letter not in self.guessed_word:
self._right_guess(guess_letter)
elif guess_letter not in self.secret_word and guess_letter not in self.wrong_guesses:
self._wrong_guess(guess_letter)
The _guess_taker()
checks the value of the key pressed by the player as a letter and not a symbol or number by looking at the ascii_letters
and if it isn't, it will just ignore it. And if it is, it will proceed.
If the guess_letter
is in self.secret_word
and not yet guessed (guess_letter not in self.guessed_word
), it will send the letter in the self._right_guess()
function. And if not in self.secret_word
, and vice versa, it will send the letter to self._wrong_guess()
.
We include the guess_letter, not in self.guessed_word
and guess_letter not in self.wrong_guesses
to not waste a move. This doesn't allow the guess to proceed in self._right_guess()
or self._wrong_guess()
in case the letter is already guessed, or the letter is already in the wrong guesses to prevent adding another body part just because of the mistake the player already committed in the earlier situation.
Let's update the main()
function. Let's put game instructions below and inside the for
loop, we'll call the self._guess_taker()
function.
def main(self):
# game's main components (no need to update)
screen.fill(self.background_color)
self._gallow()
instructions = self.font.render('Press any key to take Guess', True, (9,255,78))
screen.blit(instructions,(35,460))
while self.running:
# shows the guessed word in the game window
guessed_word = self.font.render(f"guessed word: {self.guessed_word}", True, (0,0,138))
screen.blit(guessed_word,(10,370))
# shows the wrong guesses in the game window
wrong_guesses = self.font.render(f"wrong guesses: {' '.join(map(str, self.wrong_guesses))}", True, (125,0,0))
screen.blit(wrong_guesses,(10,420))
for self.event in pygame.event.get():
...
# manages keys pressed
elif self.event.type == pygame.KEYDOWN:
if self.taking_guess:
self._guess_taker(self.event.unicode)
pygame.display.flip()
self.FPS.tick(60)
pygame.quit()
Below the screen fill and the _gallow()
call, we removed the self._man_pieces
and add some text instructions. We also added to the screen the guessed word and the wrong guesses which we put inside the while
loop since they are updating for every guess the player.
The unicode
gets the value of the pressed key so if the type of the self.event
is a KEYDOWN
(a key was pressed) and the game is still taking guesses if self.taking_guess
, we'll call the self._guess_taker()
function and give self.event.unicode
as its argument.
If we check the game by running, you might see:
Now it has the game instructions saying "Press any key to take a guess" and if you take a guess you'll catch an error.
This is because of the two functions (the self._right_guess()
and the self._wrong_guess()
) we called but currently, we don't have. And those are the next things we're adding to the game.
Above the _guess_taker()
function, create two other functions and name them _right_guess()
and _wrong_guess()
. These functions will save the guess whether it belongs to the wrong guesses lista or to guessed word (the right guess).
def _right_guess(self, guess_letter):
index_positions = [index for index, item in enumerate(self.secret_word) if item == guess_letter]
for i in index_positions:
self.guessed_word = self.guessed_word[0:i] + guess_letter + self.guessed_word[i+1:]
# stacks a layer of color on guessed word to hide multiple guessed_word stack
screen.fill(pygame.Color(self.background_color), (10, 370, 390, 20))
The index_positions
collects all the indices where the guessed_letter
is present. We put it in a list because some words had one letter two or more than times. Words like 'dull'
with two letters 'l'
, 'bottom'
with two 'o'
s and 't'
s, and 'attention'
with three 't'
s.
Once we have them, we'll replace the asterisk "*"
character with the letter guessed by the player using the indices we collect. So let's say our secret word is "attention"
and the player's guess is the letter 't'
, the index_positions
list contains 1, 2, and 5 since we start our count from 0. The self.guessed_word
will be "*tt**t***"
.
We also put another layer of color with the same color as the background, this will cover the rest of the stacked guesses behind it that were configured for every right guess the player did. If we didn't put a fill it would be messy and the guessed word is kind of hard to read.
Once an object is already uploaded to the game screen using flip()
or update()
, it is impossible to remove and one of the fast fixes for this is adding a layer of color fill above the area we want to cover.
Next, let's modify the _wrong_guess()
function:
def _wrong_guess(self, guess_letter):
self.wrong_guesses.append(guess_letter)
self.wrong_guess_count += 1
self._man_pieces()
The _wrong_guess()
appends the guessed letter to self.wrong_guesses
, adds 1 to the self.wrong_guess_count
, and calls the _man_pieces()
to add a man's body part based on the self.wrong_guess_count
.
Let's next modify our self._man_pieces()
since we made it earlier to draw the whole body in just a call. We need it to draw each of the parts for every mistake from the head, to the legs:
# draw man's body parts for every wrong guess
def _man_pieces(self):
if self.wrong_guess_count == 1:
head = pygame.draw.circle(screen, self.body_color, [210, 85], 20, 0)
elif self.wrong_guess_count == 2:
body = pygame.draw.rect(screen, self.body_color, pygame.Rect(206, 105, 8, 45))
elif self.wrong_guess_count == 3:
r_arm = pygame.draw.line(screen, self.body_color, [183, 149], [200, 107], 6)
elif self.wrong_guess_count == 4:
l_arm = pygame.draw.line(screen, self.body_color, [231, 149], [218, 107], 6),
elif self.wrong_guess_count == 5:
r_leg = pygame.draw.line(screen, self.body_color, [189, 198], [208, 148], 6),
elif self.wrong_guess_count == 6:
l_leg = pygame.draw.line(screen, self.body_color, [224, 198], [210, 148], 6)
If we try our game, it will work now better, we can take guesses now and check them but it won't tell you whether you win or you lose. It doesn't even stop if the man is fully drawn or the world is already guessed.
In the next one, we're adding the game situation analyzer which also sends us the message for the game state.
Below the _guess_taker()
create a function we call _message()
. This will serve as the analyzer of the game situation and also the responsible for sending messages about whether the player wins or loses:
def _message(self):
# win situation
if self.guessed_word == self.secret_word:
self.taking_guess = False
screen.fill(pygame.Color(0,0,79), (40, 218, 320, 30))
message = self.font.render("YOU WIN!!", True, (255,235,0))
screen.blit(message,(152,224))
# lose situation
elif self.wrong_guess_count == 6:
self.taking_guess = False
screen.fill(pygame.Color("grey"), (40, 218, 320, 30))
message = self.font.render("GAME OVER YOU LOSE!!", True, (150,0,10))
screen.blit(message,(78,224))
# shows the secret word if the player lose
word = self.font.render(f"secret word: {self.secret_word}", True, (255,255,255))
screen.blit(word,(10,300))
# removes the instruction message if not taking guesses anymore
if not self.taking_guess:
screen.fill(pygame.Color(self.background_color), (35, 460, 390, 20))
We also cover a color layer in the game instructions another indicator that the game is over.
Now let's call it inside the main()
. We're placing it inside the while
loop so it will review the game state for every second. Let's call it above the for
loop:
def main(self):
...
while self.running:
...
# checking game state
self._message()
for self.event in pygame.event.get():
...
pygame.display.flip()
self.FPS.tick(60)
pygame.quit()
And now we're done! Here are some of the game snapshots.
The player won with minimal errors:
The player won with no errors:
The game while playing.
The player loses:
Now we know how to create a pretty cool and easy game with Pygame. Discover new words and have fun by playing Hangman.
In this tutorial, we learned about the basics of the Pygame module and make a Hangman game from scratch. I hope you learned something and you liked this tutorial. See you at the next one and have a nice day!
You can check the complete code here.
Here are some PyGame tutorials:
Happy coding ♥
Just finished the article? Why not take your Python skills a notch higher with our Python Code Assistant? Check it out!
View Full Code Assist My Coding
Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!