Hangman: Creating games in OpenCV

As beginners in OpenCV, it is a daunting task to read beautiful yet complicated projects and codes. But fear not! In this post, we will guide you through a fun project which uses very basic Computer Vision and Image Processing techniques which I learned in the first two modules of

As beginners in OpenCV, it is a daunting task to read beautiful yet complicated projects and codes. But fear not! In this post, we will guide you through a fun project which uses very basic Computer Vision and Image Processing techniques which I learned in the first two modules of Computer Vision for Faces. The Hangman Project is a very simple yet engaging game with different results every single time!

Hangman Game Demo

Rules of the game

So how does it work? Let’s go over the rules.

  • You are greeted to a screen which has the hangman’s noose and boxes on the screen.
  • The number of boxes and the spacing between them indicate letters in each word and the number of words, respectively.
  • Pick a random letter and plug it in and the game starts.
    • If your guess is right, phew! you are safe.
    • If not, the man’s head appears.
  • You continue and after your second mistake, you feel discouraged that you won’t be able to save him. Well, worry not, this is where you start getting clues after each mistake.
  • After each mistake, the man’s head, body, hands and legs start to appear and you race to solve the clues and win a life in 6 correct tries!

Creating the Hangman Game

Hangman Flowchart
Hangman Game Flowchart

The basic flowchart of the entire game is shown above. Let’s look into each of these steps one by one.

Data Processing

We won’t go into the depth of the data processing step in this post. To make sure that the game doesn’t run out of new movies and clues any time soon, we are going to go for TMDb dataset (The Movie Database). The data processing steps comprise of removing missing values, keeping only the required columns/features – release year, cast, director, movie keywords, and the tagline. We also remove any entries containing the character ‘,‘ as that cause some hurdles in further data splitting steps. Finally, to reduce the size of the dataset, we only keep movies with titles having not more than 20 characters and tagline length not more than 30.

The final dataset contains 1767 movies and 6 features for each movie.

The pre-processed dataset and the code for this post can be downloaded using the link below.

Movie and Clues Selection

Now that we have the processed dataset, we will use it in our further steps. We will first detail the functions to carry out the particular steps and then at the end, how they will be used.

But before that, let’s import the modules we will need.

import cv2
import numpy as np
import sys

First, let’s load the dataset from the CSV file and store it in dictionary format.

def read_from_csv(csv_f):
    with open(csv_f,'r') as f:
        movie_data = {}
        for line in f.readlines():
            line_split = line.strip().split(",")
            year = line_split[-1].split("|")
            keywords = line_split[-2].split("|")
            tagline = line_split[-3].split("|")
            director = line_split[-4].split("|")
            cast = line_split[-5].split("|")
            movie = line_split[0].upper()
            movie_data[movie] = [year,keywords,tagline,director,cast]
    return movie_data
Download Code To easily follow along this tutorial, please download code by clicking on the button below. It's FREE!

Notice that we are using the following dictionary format for storing the movie and the movie details.

movie_title:[year,list of keywords, tagline, director, list of cast]

Next, we will get a random movie from the list of movies and get the information for that movie (year, keywords, tagline, director and cast).

def get_movie_info(movies_data):
    movies_list = list(movies_data.keys())
    movie = np.random.choice(movies_list,1)[0].upper()
    movie_info = movies_data[movie]
    return movie,movie_info

We will use another function select_hints to select any 3 random hints. If keywords or cast are present as the hints, we will randomly select one element from them.

def select_hints(movie_info):
    # We will randomly select 3 types of
    # hints to display
    hints_index = list(np.random.choice(5,3,replace=False))
    hints = []
    hints_labels = ["Release Year","Keyword","Tagline","Director","Cast"]
    labels = []
    for hint_index in hints_index:
        hint = np.random.choice(movie_info[hint_index],1)[0].upper()
        hints.append(hint)
        labels.append(hints_labels[hint_index].upper())
    return hints,labels

Programming Display

As in the previous section, we will only describe the functions here and we will see their usage in the end of the post.

Load Hangman Canvas

First, we will read the hangman template which is shown below as well.

Hangman Canvas
def get_canvas(canvas_file):
    img = cv2.imread(canvas_file,1)
    return img

Get Character Dimensions

Next, here comes a tricky part. We want to display blank boxes so that the user knows the length of the movie title. We will make it more interesting and general by taking into account the width and height of a character. We won’t use the dimensions directly as the box will then give an idea about the character (think I versus M). We will instead use the maximum height and width among all the characters in that title. We will use the function get_char_coords to calculate the coordinates of these boxes.

def get_char_coords(movie):
    x_coord = 100
    y_coord = 400

    char_ws = []
    char_hs = []

    for i in movie:
        char_width, char_height = cv2.getTextSize(i,
                cv2.FONT_HERSHEY_SIMPLEX,1,2)[0]
        char_ws.append(char_width)
        char_hs.append(char_height)

    max_char_h = max(char_hs)
    max_char_w = max(char_ws)

    char_rects = []

    for i in range(len(char_ws)):
        rect_coord = [(x_coord,y_coord-max_char_h),
                (x_coord+max_char_w,y_coord)]
        char_rects.append(rect_coord)
        x_coord = x_coord + max_char_w

    return char_rects

Draw Blank Boxes

Once we have these coordinates, we will use them to draw the boxes.

def draw_blank_rects(movie,char_rects,img):

    for i in range(len(char_rects)):
        top_left, bottom_right = char_rects[i]
        if not movie[i].isalpha() or 
                ord(movie[i]) < 65 or 
                ord(movie[i]) > 122 or 
                (ord(movie[i]) > 90 and 
                ord(movie[i]) < 97):
            cv2.putText(img,movie[i],(top_left[0],
                    bottom_right[1]),
                    cv2.FONT_HERSHEY_SIMPLEX,
                    1,(0,0,255),2)
            continue
        cv2.rectangle(img,top_left,
                bottom_right,
                (0,0,255),thickness=1,
                lineType = cv2.LINE_8)

    return img

Display Hint

Let’s go over other functions that we will need starting off with the function to display the hints. The hint to be displayed will depend on the number of incorrect attempts. If there is no incorrect attempt, we won’t display any hint. In case of one incorrect attempt, we will display the first hint. Similarly, for less than 4 incorrect attempts, we will display the second hint and finally, for less than 7 incorrect attempts, we will display the third hint.

def draw_hint(img,hints,labels,incorrect_attempts):
    x,y = 20,30
    if incorrect_attempts == 0:
        return img
    elif incorrect_attempts <= 1:
        index = 0
    elif incorrect_attempts <= 3:
        index = 1
    elif incorrect_attempts <= 6:
        index = 2
    cv2.putText(img,"HINT: {}".format(labels[index]),(x,y),
            cv2.FONT_HERSHEY_SIMPLEX,0.6,
            (255,0,255),1)
    cv2.putText(img,"{}".format(hints[index]),(x,y+30),
            cv2.FONT_HERSHEY_SIMPLEX,0.6,
            (255,0,255),1)
    return img

Display correct or incorrect attempt

Let’s also write functions to display if the guessed letter was present in the movie title or not.

def draw_wrong(img,incorrect_attempts):
    cv2.putText(img,"WRONG {}/6".format(incorrect_attempts+1),(380,40),
            cv2.FONT_HERSHEY_SIMPLEX,1,
            (0,0,255),2)
    return img

def draw_right(img):
    cv2.putText(img,"RIGHT",(380,40),
            cv2.FONT_HERSHEY_SIMPLEX,0.7,
            (0,255,0),2)
    return img

Display game won or lost

Similarly, we will also write functions to display if a game was won or lost.

def draw_lost(img):
    cv2.putText(img,"YOU LOST",(380,40),
            cv2.FONT_HERSHEY_SIMPLEX,0.7,
            (0,0,255),2)
    return img

def draw_won(img):
    cv2.putText(img,"YOU WON",(380,40),
            cv2.FONT_HERSHEY_SIMPLEX,0.7,
            (0,255,0),2)
    return img

Display invalid move and character reuse

Now, there are two more cases we have to consider. What if an invalid character was entered? This can be a number or a non-alphanumeric character. And second, what if the user enters a character that has already been entered before?

def draw_invalid(img):
    cv2.putText(img,"INVALID INPUT",(300,40),
            cv2.FONT_HERSHEY_SIMPLEX,0.7,
            (0,0,255),2)
    return img

def draw_reuse(img):
    cv2.putText(img,"ALREADY USED",(300,40),
            cv2.FONT_HERSHEY_SIMPLEX,0.7,
            (0,0,255),2)
    return img

Display used characters

Now, to make it easier for the user to know what characters have already been used, we will display them as well.

We will make one more addition here. We will display the valid character entered in red so that the user can see what character they entered.

def draw_used_chars(img,chars_entered,letter):
    cv2.putText(img,"Letters used:",(300,80),
            cv2.FONT_HERSHEY_SIMPLEX,0.5,
            (0,0,0),1)
    y = 120
    x = 350
    count = 0
    for i in chars_entered:
        if count == 10:
           x += 50
           y = 120
        if i==letter:
           cv2.putText(img,i,(x,y),
                cv2.FONT_HERSHEY_SIMPLEX,0.5,
                (0,0,255),1)
        else:
           cv2.putText(img,i,(x,y),
                cv2.FONT_HERSHEY_SIMPLEX,0.5,
                (0,0,0),1)
        y += 20
        count += 1
    return img

Display Hangman body

Next comes the turn to write functions to draw different body parts of the hangman – face, back, left and right arms, left and right legs. What body part will be drawn depends on the number of incorrect attempts.

def draw_hangman(img,num_tries):
    if num_tries==1:
        return draw_circle(img)
    elif num_tries==2:
        return draw_back(img)
    elif num_tries==3:
        return draw_left_hand(img)
    elif num_tries==4:
        return draw_right_hand(img)
    elif num_tries==5:
        return draw_left_leg(img)
    elif num_tries==6:
        return draw_right_leg(img)
    else:
        return img

def draw_circle(img):
    cv2.circle(img,(190,160),40,(0,0,0),thickness=2,
            lineType=cv2.LINE_AA)
    return img

def draw_back(img):
    cv2.line(img,(190,200),(190,320),
            (0,0,0),thickness=2,
            lineType=cv2.LINE_AA)
    return img

def draw_left_hand(img):
    cv2.line(img,(190,240),(130,200),
            (0,0,0),thickness=2,
            lineType=cv2.LINE_AA)
    return img

def draw_right_hand(img):
    cv2.line(img,(190,240),(250,200),
            (0,0,0),thickness=2,
            lineType=cv2.LINE_AA)
    return img

def draw_left_leg(img):
    cv2.line(img,(190,320),(130,360),
            (0,0,0),thickness=2,
            lineType=cv2.LINE_AA)
    return img

def draw_right_leg(img):
    cv2.line(img,(190,320),(250,360),
            (0,0,0),thickness=2,
            lineType=cv2.LINE_AA)
    return img

Display character occurrences

We also want to display all the occurrences of the letter entered in the movie title (if it is present in the movie title).

def displayLetter(img,letter,movie,char_rects):
    for i in range(len(movie)):
        if movie[i]==letter:
            top_left, bottom_right = char_rects[i]
            cv2.putText(img, movie[i],
                    (top_left[0],bottom_right[1]),
                    cv2.FONT_HERSHEY_SIMPLEX,
                    1,(255,0,0),2)
    return img

Reveal movie title

Finally, we want to reveal the correct movie title once the game ends.

def revealMovie(movie,img,char_rects):
    #img = cv2.imread(canvas_file,1)
    for i in range(len(movie)):
        top_left, bottom_right = char_rects[i]
        cv2.putText(img,movie[i],(top_left[0],bottom_right[1]),
                cv2.FONT_HERSHEY_SIMPLEX,
                1,(0,255,0),2)
    return img

Programming the game

Now that we have covered all the functions, let’s see how we are going to use them in our game.

We will start off with reading the data from the CSV file and getting a random movie.

import cv2
import numpy as np
from utils import *

movie_csv = "movies-list-short.csv"
canvas = "blank-canvas.png"

movies_data = read_from_csv(movie_csv)

movie, movie_info = get_movie_info(movies_data)

We will also select the 3 random hints as discussed before.

hints,labels = select_hints(movie_info)

Now, let’s start off with the blank Hangman canvas and zero incorrect attempts.

char_rects = get_char_coords(movie)

img = draw_blank_rects(movie,char_rects,img)

cv2.namedWindow("Hangman", cv2.WND_PROP_FULLSCREEN)
cv2.setWindowProperty("Hangman",cv2.WND_PROP_FULLSCREEN,cv2.WINDOW_FULLSCREEN)

cv2.imshow("Hangman",img)

chars_entered = []

incorrect_attempts = 0

img_copy = img.copy()

Next, we will code the section for taking input from user and displaying the letters entered. We will also need to display if the attempt is correct or wrong or if it is invalid or already used. The loop will break if the user runs out of tries.

We achieve the above by doing the following.

  1. Create a copy of the current image. This is to make sure that we don’t end up overwriting words like – WRONG, CORRECT, etc. or the hints.
  2. Next, based on the number of incorrect attempts, we will display the hint on the image.
  3. If the user has used up all his lives, we will display YOU LOST and the loop will break.
  4. If the user managed to guess all the characters of the movie, we will display YOU WON and break the loop.
  5. To check if the character entered by the user is valid or not, we will check if the character lies between a-z or A-Z. If the move is invalid, we will display the corresponding message – INVALID MOVE and the game will continue.
  6. The valid character entered by the user will be checked to see if it has already been used before, in which case the corresponding message will be displayed and the game will continue.
  7. Note that in the last 2 steps, the number of incorrect attempts made is not changed.
  8. If the character entered is a new one, we will first append it to the list of the characters used and then check if it is present in the movie title, in which case we will display CORRECT and display all occurrences of the character in the movie.
  9. If the character was not found in the movie title, we will display WRONG and increment the number of incorrect attempts.
  10. Finally, once the game is won or lost, we will reveal the correct movie title.
while 1:
    img = img_copy.copy()
    img = draw_hint(img,hints,labels,incorrect_attempts)
    if incorrect_attempts >= 6:
        img = draw_lost(img)
        break
    elif check_all_chars_found(movie, chars_entered):
        img = draw_won(img)
        break
    else:
        letter = cv2.waitKey(0) & 0xFF
        if letter < 65 or letter > 122 or (letter > 90 and letter < 97):
            img = draw_invalid(img)
            cv2.imshow("Hangman",img)
            continue
        else:
            letter = chr(letter).upper()
        if letter in chars_entered:
            img = draw_reuse(img)
            img = draw_used_chars(img,chars_entered,letter)
            cv2.imshow("Hangman",img)
            continue
        else:
            chars_entered.append(letter)
            if letter in movie:
                img = draw_right(img)
                img = displayLetter(img,letter,movie,char_rects)
                img_copy = displayLetter(img_copy,letter,movie,
                        char_rects)
            else:
                img = draw_wrong(img,incorrect_attempts)
                incorrect_attempts += 1
    img = draw_used_chars(img,chars_entered,letter)
    img = draw_hangman(img,incorrect_attempts)
    img_copy = draw_used_chars(img_copy,chars_entered,letter)
    img_copy = draw_hangman(img_copy,incorrect_attempts)
    cv2.imshow("Hangman",img)

img = revealMovie(movie,img,char_rects)
cv2.imshow("Hangman",img)
cv2.waitKey(0)

cv2.destroyAllWindows()

Let’s quickly summarise the different parts of the Hangman game with the help of the image shown below.

Different regions of Hangman game

Phew! That’s it. I really hope you enjoyed the post and make sure to try out the game! Do keep in mind that this entire exhaustive project has been made with ONLY the basics of OpenCV and if I can do this, so can you!



Read Next

VideoRAG: Redefining Long-Context Video Comprehension

VideoRAG: Redefining Long-Context Video Comprehension

Discover VideoRAG, a framework that fuses graph-based reasoning and multi-modal retrieval to enhance LLMs' ability to understand multi-hour videos efficiently.

AI Agent in Action: Automating Desktop Tasks with VLMs

AI Agent in Action: Automating Desktop Tasks with VLMs

Learn how to build AI agent from scratch using Moondream3 and Gemini. It is a generic task based agent free from…

The Ultimate Guide To VLM Evaluation Metrics, Datasets, And Benchmarks

The Ultimate Guide To VLM Evaluation Metrics, Datasets, And Benchmarks

Get a comprehensive overview of VLM Evaluation Metrics, Benchmarks and various datasets for tasks like VQA, OCR and Image Captioning.

Subscribe to our Newsletter

Subscribe to our email newsletter to get the latest posts delivered right to your email.

Subscribe to receive the download link, receive updates, and be notified of bug fixes

Which email should I send you the download link?

🎃 Halloween Sale: Exclusive Offer – 30% Off on All Courses.
D
H
M
S
Expired
 

Get Started with OpenCV

Subscribe To Receive

We hate SPAM and promise to keep your email address safe.​