Skip to content

Hangman Game

Abstract

Hangman is the perfect “second game” project. It is more complex than Rock-Paper-Scissors but still fits in one file. You learn file I/O (loading a word list), random selection, sets (tracking guessed letters), string assembly (showing the partially-revealed word), and game-state management (win, lose, keep going). In this tutorial you will build the classic version with a word list, then layer in ASCII-art gallows, input validation, category selection, score tracking, hints, and a replay loop.

You will learn:

  • Why setset is the right structure for “letters already guessed”.
  • How to reveal a word letter-by-letter from a hidden secret.
  • How to draw step-by-step ASCII art from a list of stages.
  • How to organize game state into clean functions.
  • How to extend a beginner game with categories, hints, and persistence.

The Rules

  1. The computer picks a secret word.
  2. The player sees blanks: _ _ _ _ _ _ __ _ _ _ _ _ _.
  3. Player guesses one letter at a time.
  4. Correct letters fill in their positions.
  5. Wrong letters cost one “limb” of the hanged figure.
  6. Player wins by completing the word; loses if the figure is fully drawn first.

Prerequisites

  • Python 3.6 or above.
  • A code editor or IDE.
  • A text file of words (we will make one).
  • Comfort with whilewhile loops, if/elseif/else, and basic file reading.

Getting Started

Create the project

  1. Create a folder named hangman-gamehangman-game.
  2. Inside it, create hangman.pyhangman.py.
  3. Create words.txtwords.txt next to it. Suggested starter contents:
    words.txt
    python
    programming
    computer
    science
    technology
    development
    software
    coding
    algorithm
    function
    variable
    recursion
    compiler
    interpreter
    words.txt
    python
    programming
    computer
    science
    technology
    development
    software
    coding
    algorithm
    function
    variable
    recursion
    compiler
    interpreter

Write the code

HangmanSource
Hangman
# Hangman game
# -----------------------------------
 
# Importing the random module
import random
import time
 
# Inputting the name of the user
name = input("What is your name? ")
print("Good Luck ! ", name)
 
time.sleep(1)
 
print("The game is about to start !")
print("Let's play Hangman")
 
time.sleep(1)
 
open_file = open("words.txt", "r")
words = open_file.readlines()
 
# Function will choose one random
# word from this list of words
word = random.choice(words)
word = word.lower().strip().replace("\n", "").replace("\r", "").replace(" ", "").replace("-", "")
print("Guess the characters")
 
guesses = ''
turns = len(word) * 2 
 
# Checks if the turns are more than zero
while turns > 0:
    failed = 0
 
    # for every character in secret_word
    for char in word:
 
        # see if the character is in the players guess
        if char in guesses:
            print(char, end="")
 
        else:
            print("_", end="")
 
            # and increase the failed counter with one
            failed += 1
 
    if failed == 0:
        print("\nYou Win")
 
        # this print the correct word
        print("The word is: ", word)
        break
 
    # if failed equals to zero
    # then you will get this message
    if turns == 1:
        print("\nYou have", turns, 'more guess')
    else:
        print("\nYou have", turns, 'more guesses')
 
    guess = input("guess a character: ")
 
    # every input character will be stored in guesses
    guesses += guess
 
    # check input with the character in word
    if guess not in word:
 
        turns -= 1
 
        # if the character doesn’t match the word
        # then “Wrong” will be given as output
        print("Wrong")
 
        # this will print the number of
        # turns left for the user
        if turns == 1:
            print("\nYou have", turns, 'more guess')
        else:
            print("\nYou have", turns, 'more guesses')
 
        if turns == 0:
            print("\nYou Loose")
            print("\nThe word is: ", word)
            break
 
Hangman
# Hangman game
# -----------------------------------
 
# Importing the random module
import random
import time
 
# Inputting the name of the user
name = input("What is your name? ")
print("Good Luck ! ", name)
 
time.sleep(1)
 
print("The game is about to start !")
print("Let's play Hangman")
 
time.sleep(1)
 
open_file = open("words.txt", "r")
words = open_file.readlines()
 
# Function will choose one random
# word from this list of words
word = random.choice(words)
word = word.lower().strip().replace("\n", "").replace("\r", "").replace(" ", "").replace("-", "")
print("Guess the characters")
 
guesses = ''
turns = len(word) * 2 
 
# Checks if the turns are more than zero
while turns > 0:
    failed = 0
 
    # for every character in secret_word
    for char in word:
 
        # see if the character is in the players guess
        if char in guesses:
            print(char, end="")
 
        else:
            print("_", end="")
 
            # and increase the failed counter with one
            failed += 1
 
    if failed == 0:
        print("\nYou Win")
 
        # this print the correct word
        print("The word is: ", word)
        break
 
    # if failed equals to zero
    # then you will get this message
    if turns == 1:
        print("\nYou have", turns, 'more guess')
    else:
        print("\nYou have", turns, 'more guesses')
 
    guess = input("guess a character: ")
 
    # every input character will be stored in guesses
    guesses += guess
 
    # check input with the character in word
    if guess not in word:
 
        turns -= 1
 
        # if the character doesn’t match the word
        # then “Wrong” will be given as output
        print("Wrong")
 
        # this will print the number of
        # turns left for the user
        if turns == 1:
            print("\nYou have", turns, 'more guess')
        else:
            print("\nYou have", turns, 'more guesses')
 
        if turns == 0:
            print("\nYou Loose")
            print("\nThe word is: ", word)
            break
 

Run it

command
C:\Users\Your Name\hangman-game> python hangman.py
What is your name? John
Good luck, John! Let's play Hangman.
Word: _ _ _ _ _ _ _
Guesses left: 6
Guess a letter: p
Word: p _ _ _ _ _ _
Guess a letter: ...
command
C:\Users\Your Name\hangman-game> python hangman.py
What is your name? John
Good luck, John! Let's play Hangman.
Word: _ _ _ _ _ _ _
Guesses left: 6
Guess a letter: p
Word: p _ _ _ _ _ _
Guess a letter: ...

Step-by-Step Explanation

1. Imports

hangman.py
import random
import time
hangman.py
import random
import time

random.choicerandom.choice picks the word. time.sleeptime.sleep adds drama.

2. Load and pick a word

hangman.py
with open("words.txt", encoding="utf-8") as f:
    words = [line.strip().lower() for line in f if line.strip()]
word = random.choice(words)
hangman.py
with open("words.txt", encoding="utf-8") as f:
    words = [line.strip().lower() for line in f if line.strip()]
word = random.choice(words)

Reading inside a withwith block ensures the file is closed automatically. The list comprehension strips trailing newlines and skips empty lines.

3. Game state

hangman.py
guessed_letters = set()
wrong = 0
max_wrong = 6
hangman.py
guessed_letters = set()
wrong = 0
max_wrong = 6

Why a set for guessed_lettersguessed_letters?

  • letter in guessed_lettersletter in guessed_letters is O(1).
  • Adding a duplicate is harmless.
  • It mirrors the natural language of the rules — “have I guessed this letter already?”

4. Reveal the secret partially

hangman.py
def display(word: str, guessed: set[str]) -> str:
    return " ".join(c if c in guessed else "_" for c in word)
hangman.py
def display(word: str, guessed: set[str]) -> str:
    return " ".join(c if c in guessed else "_" for c in word)

A list/generator comprehension over the secret word: keep characters the player has guessed, otherwise show __. " ".join(...)" ".join(...) spaces them out for readability.

5. The main loop

hangman.py
while wrong < max_wrong:
    print(f"\n{display(word, guessed_letters)}")
    print(f"Guesses left: {max_wrong - wrong}")
    guess = input("Guess a letter: ").lower().strip()
 
    if len(guess) != 1 or not guess.isalpha():
        print("Enter a single letter.")
        continue
    if guess in guessed_letters:
        print("Already guessed.")
        continue
 
    guessed_letters.add(guess)
    if guess in word:
        if all(c in guessed_letters for c in word):
            print(f"\n🎉 You win! The word was '{word}'.")
            break
    else:
        wrong += 1
        print("Wrong!")
else:
    print(f"\nYou lost. The word was '{word}'.")
hangman.py
while wrong < max_wrong:
    print(f"\n{display(word, guessed_letters)}")
    print(f"Guesses left: {max_wrong - wrong}")
    guess = input("Guess a letter: ").lower().strip()
 
    if len(guess) != 1 or not guess.isalpha():
        print("Enter a single letter.")
        continue
    if guess in guessed_letters:
        print("Already guessed.")
        continue
 
    guessed_letters.add(guess)
    if guess in word:
        if all(c in guessed_letters for c in word):
            print(f"\n🎉 You win! The word was '{word}'.")
            break
    else:
        wrong += 1
        print("Wrong!")
else:
    print(f"\nYou lost. The word was '{word}'.")

Several patterns in play:

  • Input validation rejects multi-char or non-alphabetic input.
  • Duplicate detection uses the set.
  • Win check asks “are every letter of the word now in guessedguessed?”all(...)all(...) is the right tool.
  • elseelse clause on a whilewhile runs only if the loop ended normally (the player ran out of guesses). It is a lesser-known but elegant feature of Python.

ASCII-Art Gallows

A 7-frame gallows that draws progressively as the player loses:

gallows.py
STAGES = [
    r"""
       -----
       |   |
           |
           |
           |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
           |
           |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
       |   |
           |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
      /|   |
           |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
      /|\  |
           |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
      /|\  |
      /    |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
      /|\  |
      / \  |
           |
    =========""",
]
 
print(STAGES[wrong])     # in the main loop
gallows.py
STAGES = [
    r"""
       -----
       |   |
           |
           |
           |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
           |
           |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
       |   |
           |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
      /|   |
           |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
      /|\  |
           |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
      /|\  |
      /    |
           |
    =========""",
    r"""
       -----
       |   |
       O   |
      /|\  |
      / \  |
           |
    =========""",
]
 
print(STAGES[wrong])     # in the main loop

Six wrong guesses → seven stages (0 wrong = empty gallows, 6 wrong = full hangman). The r"""..."""r"""...""" is a raw triple-quoted string — backslashes inside stay literal.

Common Mistakes

ProblemCauseFix
Word contains a newline characterForgot .strip().strip() after readlines()readlines()Use the list-comp above
Capital letters never matchMixed casesLowercase both the word and the guess
Pressing Enter empty crashes the gameNo length checkValidate len(guess) == 1len(guess) == 1
Counting wrong letters when a duplicate is pressedNo duplicate checkSkip already-guessed letters
Win never triggersUsed ==== instead of all(... in ...)all(... in ...)Use all(c in guessed_letters for c in word)all(c in guessed_letters for c in word)

Variations to Try

1. Categories

Store one word list per category:

text
fruits.txt
animals.txt
programming.txt
text
fruits.txt
animals.txt
programming.txt

Ask the user to pick a category. Each .txt.txt becomes a PathPath:

categories.py
from pathlib import Path
cats = {p.stem: p for p in Path("words").glob("*.txt")}
choice = input(f"Pick: {', '.join(cats)}\n> ")
words = cats[choice].read_text().splitlines()
categories.py
from pathlib import Path
cats = {p.stem: p for p in Path("words").glob("*.txt")}
choice = input(f"Pick: {', '.join(cats)}\n> ")
words = cats[choice].read_text().splitlines()

2. Difficulty levels

  • Easy: 4–6 letter words, 8 guesses.
  • Medium: 7–9 letter words, 6 guesses.
  • Hard: 10+ letter words, 5 guesses, only common letters give hints.

3. Hints

Give a hint after N wrong guesses — reveal one letter automatically:

hint.py
if wrong == 4:
    for c in word:
        if c not in guessed_letters:
            print(f"Hint: contains '{c}'")
            guessed_letters.add(c)
            break
hint.py
if wrong == 4:
    for c in word:
        if c not in guessed_letters:
            print(f"Hint: contains '{c}'")
            guessed_letters.add(c)
            break

4. Score tracking

A win earns (letters - wrong) * 10(letters - wrong) * 10 points. Persist between sessions in a JSON file. See Personal Diary for the same persistence pattern.

5. Replay loop

replay.py
while True:
    play_round()
    if input("Play again? (y/n): ").lower() != "y":
        break
replay.py
while True:
    play_round()
    if input("Play again? (y/n): ").lower() != "y":
        break

6. Two-player mode

Player 1 enters the word secretly (getpass.getpassgetpass.getpass hides it from the terminal); Player 2 guesses.

7. GUI version

Tkinter with a letter-grid of buttons and an image-based gallows. See Basic Music Player for the GUI pattern.

8. Web version

Flask plus a single HTML page. The state lives in the user’s session. See Basic Web Server.

9. Word source from an API

Pull a random English word from a dictionary API instead of words.txtwords.txt:

api_word.py
import requests
word = requests.get("https://random-word-api.herokuapp.com/word").json()[0]
api_word.py
import requests
word = requests.get("https://random-word-api.herokuapp.com/word").json()[0]

10. Definition reveal

After the round, look up the word’s definition using pip install nltkpip install nltk + the WordNet corpus, and show it to teach vocabulary.

Refactoring with a Class

For a serious version, wrap state in a class:

hangman_class.py
class Hangman:
    def __init__(self, word: str, max_wrong: int = 6):
        self.word = word.lower()
        self.guessed: set[str] = set()
        self.wrong = 0
        self.max_wrong = max_wrong
 
    def guess(self, letter: str) -> str:
        letter = letter.lower()
        if letter in self.guessed:
            return "duplicate"
        self.guessed.add(letter)
        if letter not in self.word:
            self.wrong += 1
            return "miss"
        return "hit"
 
    @property
    def display(self) -> str:
        return " ".join(c if c in self.guessed else "_" for c in self.word)
 
    @property
    def is_won(self) -> bool:
        return all(c in self.guessed for c in self.word)
 
    @property
    def is_lost(self) -> bool:
        return self.wrong >= self.max_wrong
hangman_class.py
class Hangman:
    def __init__(self, word: str, max_wrong: int = 6):
        self.word = word.lower()
        self.guessed: set[str] = set()
        self.wrong = 0
        self.max_wrong = max_wrong
 
    def guess(self, letter: str) -> str:
        letter = letter.lower()
        if letter in self.guessed:
            return "duplicate"
        self.guessed.add(letter)
        if letter not in self.word:
            self.wrong += 1
            return "miss"
        return "hit"
 
    @property
    def display(self) -> str:
        return " ".join(c if c in self.guessed else "_" for c in self.word)
 
    @property
    def is_won(self) -> bool:
        return all(c in self.guessed for c in self.word)
 
    @property
    def is_lost(self) -> bool:
        return self.wrong >= self.max_wrong

Now the game logic is testable without input()input() — perfect for unit tests.

Real-World Applications

  • Vocabulary teaching — a hangman with curated word lists works in language learning apps.
  • CAPTCHA-style verification in a friendly form.
  • Conference / classroom icebreaker — group hangman on a projector.
  • Onboarding gamification — turn product terminology into a guessing game.

Educational Value

This project teaches:

  • File I/O — reading lists from text files.
  • Sets — O(1) membership tests and natural-language semantics.
  • String manipulation — partial reveal, comparison, validation.
  • Game-state management — wrong count, guessed letters, win/lose conditions.
  • Refactoring — moving from a script to a class for testability.

Next Steps

  • Build the ASCII-art version with the STAGESSTAGES list above.
  • Add categories and difficulty levels.
  • Wrap state in a HangmanHangman class and add unit tests with pytestpytest.
  • Build a GUI or web version.
  • Use a real word API so the dictionary never runs out.

Conclusion

You built a complete Hangman game from a 50-line script: file loading, secret picking, partial reveal, input validation, win/lose conditions, and ASCII-art gallows. The same patterns power dozens of guessing games and quiz apps. Full source on GitHub. Explore more games on Python Central Hub.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did