Hangman is a classic terminal project for practicing strings, sets, loops, conditions, random selection, input validation, and functions. The computer chooses a secret word, the player guesses one letter at a time, correct letters are revealed, and incorrect guesses reduce the remaining lives.
This tutorial builds a complete Hangman game in Python. The final program prevents repeated guesses, validates letters, supports whole-word guesses, displays simple ASCII stages, gives hints, separates game logic into testable functions, and lets the player start another round.
The secret word is selected with the Python random module. Lists and sets hold words and guesses; review the Python lists guide when necessary.
How Hangman Works
- Choose a secret word.
- Display an underscore for every hidden letter.
- Read one letter or a complete-word guess.
- Reveal every matching letter.
- Reduce lives after a wrong new guess.
- Continue until the word is complete or lives reach zero.
The official random.choice() documentation defines the selection function. The Python string methods documentation covers methods such as lower(), strip(), and isalpha().
Choose a Secret Word
import random
WORDS = ["python", "variable", "function", "iterator", "package"]
secret_word = random.choice(WORDS)The standard random module is appropriate here because the game does not protect a secret with security consequences.
Represent Guessed Letters with a Set
A set stores each letter once and makes membership checks clear:
guessed_letters = set()
guessed_letters.add("p")
print("p" in guessed_letters) # TrueUsing a set prevents duplicate letters from appearing in the history and lets the program detect repeated input without scanning a list manually.
Build the Masked Word
def masked_word(secret_word, guessed_letters):
return " ".join(
letter if letter in guessed_letters else "_"
for letter in secret_word
)
print(masked_word("python", {"p", "o"}))
# p _ _ _ o _The function returns display text and does not print it directly. This makes it reusable and easy to test.
Check Whether the Word Is Complete
def word_is_complete(secret_word, guessed_letters):
return all(letter in guessed_letters for letter in secret_word)all() returns true only when every letter satisfies the condition. Repeated letters do not need special handling because checking membership reveals every occurrence.
Validate the Player’s Input
Accept a single alphabetic letter or a full alphabetic word:
def read_guess():
while True:
guess = input("Guess a letter or the whole word: ").strip().lower()
if not guess:
print("Enter a letter or word.")
continue
if not guess.isalpha():
print("Use letters only.")
continue
return guessisalpha() accepts letters beyond plain English ASCII. If a word list uses accents, normalize words and guesses consistently.
Handle Repeated Guesses
Do not remove a life for a letter the player already tried:
if guess in guessed_letters:
print("You already tried that letter.")
continue
guessed_letters.add(guess)A whole-word guess can be tracked in a separate set when repeated word attempts should also be ignored.
Create Simple Hangman Stages
Store one drawing for each number of wrong guesses. The first stage is empty and the last is complete.
HANGMAN_STAGES = [
"""
+---+
| |
|
|
|
|
=========
""",
"""
+---+
| |
O |
|
|
|
=========
""",
"""
+---+
| |
O |
| |
|
|
=========
""",
"""
+---+
| |
O |
/| |
|
|
=========
""",
"""
+---+
| |
O |
/|\ |
|
|
=========
""",
"""
+---+
| |
O |
/|\ |
/ |
|
=========
""",
"""
+---+
| |
O |
/|\ |
/ \ |
|
=========
""",
]With seven stages, the player can make six incorrect guesses. The current drawing is HANGMAN_STAGES[wrong_guesses].
Add Words and Hints
A list of dictionaries keeps each word beside a hint:
WORD_BANK = [
{"word": "python", "hint": "A popular programming language"},
{"word": "iterator", "hint": "Produces values one at a time"},
{"word": "function", "hint": "Reusable block of behavior"},
{"word": "variable", "hint": "A name that refers to a value"},
{"word": "package", "hint": "Distributable collection of modules"},
]Hints make uncommon words fairer and let the project teach vocabulary at the same time.
Complete Hangman Game
import random
HANGMAN_STAGES = [
"""
+---+
| |
|
|
|
|
=========
""",
"""
+---+
| |
O |
|
|
|
=========
""",
"""
+---+
| |
O |
| |
|
|
=========
""",
"""
+---+
| |
O |
/| |
|
|
=========
""",
"""
+---+
| |
O |
/|\ |
|
|
=========
""",
"""
+---+
| |
O |
/|\ |
/ |
|
=========
""",
"""
+---+
| |
O |
/|\ |
/ \ |
|
=========
""",
]
WORD_BANK = [
{"word": "python", "hint": "A popular programming language"},
{"word": "iterator", "hint": "Produces values one at a time"},
{"word": "function", "hint": "Reusable block of behavior"},
{"word": "variable", "hint": "A name that refers to a value"},
{"word": "package", "hint": "Distributable collection of modules"},
]
def masked_word(secret_word, guessed_letters):
return " ".join(
letter if letter in guessed_letters else "_"
for letter in secret_word
)
def word_is_complete(secret_word, guessed_letters):
return all(letter in guessed_letters for letter in secret_word)
def read_guess():
while True:
guess = input("Guess a letter or the whole word: ").strip().lower()
if not guess:
print("Enter a letter or word.")
continue
if not guess.isalpha():
print("Use letters only.")
continue
return guess
def play_round(generator=None):
generator = generator or random.Random()
entry = generator.choice(WORD_BANK)
secret_word = entry["word"].lower()
hint = entry["hint"]
guessed_letters = set()
guessed_words = set()
wrong_guesses = 0
maximum_wrong = len(HANGMAN_STAGES) - 1
print(f"Hint: {hint}")
while wrong_guesses < maximum_wrong:
print(HANGMAN_STAGES[wrong_guesses])
print(masked_word(secret_word, guessed_letters))
print(f"Lives: {maximum_wrong - wrong_guesses}")
if guessed_letters:
print("Letters:", " ".join(sorted(guessed_letters)))
guess = read_guess()
if len(guess) == 1:
if guess in guessed_letters:
print("You already tried that letter.")
continue
guessed_letters.add(guess)
if guess in secret_word:
print("Correct letter!")
else:
wrong_guesses += 1
print("That letter is not in the word.")
else:
if guess in guessed_words:
print("You already tried that word.")
continue
guessed_words.add(guess)
if guess == secret_word:
guessed_letters.update(secret_word)
else:
wrong_guesses += 1
print("That is not the secret word.")
if word_is_complete(secret_word, guessed_letters):
print(masked_word(secret_word, guessed_letters))
print("You won!")
return True
print(HANGMAN_STAGES[wrong_guesses])
print(f"You lost. The word was: {secret_word}")
return False
def wants_to_play_again():
while True:
answer = input("Play again? [y/n]: ").strip().lower()
if answer in {"y", "yes"}:
return True
if answer in {"n", "no"}:
return False
print("Enter y or n.")
def main():
print("Python Hangman")
while True:
play_round()
if not wants_to_play_again():
print("Thanks for playing!")
break
if __name__ == "__main__":
main()Save the program as hangman.py and run:
python hangman.pyTest the Core Functions
from hangman import masked_word, word_is_complete
def test_masked_word():
result = masked_word("letter", {"l", "e", "r"})
assert result == "l e _ _ e r"
def test_word_is_complete():
assert word_is_complete("cat", {"c", "a", "t"}) is True
assert word_is_complete("cat", {"c", "a"}) is FalseThe Python unit testing guide explains assertions and mocks. A seeded random.Random instance can make word selection predictable during a larger test.
Debug the Game
Set breakpoints where guesses are added and where wrong_guesses changes. Inspect secret_word, guessed_letters, and guess. The VS Code debugging guide covers conditional breakpoints and variable inspection.
Load Words from a Text File
from pathlib import Path
def load_words(path):
lines = Path(path).read_text(encoding="utf-8").splitlines()
words = [line.strip().lower() for line in lines]
return [word for word in words if word.isalpha()]Validate that the returned list is not empty before calling choice(). The Python pathlib guide explains file paths and text methods.
Common Mistakes
- Reducing a life for a repeated letter.
- Revealing only one occurrence when a letter appears several times.
- Mixing uppercase words with lowercase guesses without normalization.
- Accepting digits or punctuation as guesses.
- Calling
random.choice()on an empty word list. - Putting all logic inside one long interactive loop.
- Using broad exception handling to hide unrelated programming errors.
- Testing only through manual gameplay instead of testing pure functions.
Ideas to Extend the Game
- Add categories and difficulty levels.
- Allow a limited number of hints.
- Save wins and losses to JSON.
- Use words loaded from separate category files.
- Add colors to terminal output.
- Create a graphical version with Turtle or Pygame.
- Add command-line options for lives, category, and seed.
- Record rounds with the Python logging module.
- Reuse menu and validation ideas from the terminal quiz project.
Frequently Asked Questions
Why use a set for guessed letters?
A set stores unique values and provides direct membership tests.
How are repeated letters revealed?
The masked-word function checks every character against the same set, so every matching position becomes visible.
Can the player guess the complete word?
Yes. The final program treats guesses longer than one letter as complete-word attempts.
How do I use accented words?
Store and compare text consistently. More advanced programs can normalize Unicode and decide whether accented and unaccented letters should be equivalent.
Do I need an external package?
No. This terminal version uses only the standard library.
Conclusion
A Hangman game in Python practices several fundamentals in a single project: random choice, string iteration, sets, input validation, loops, functions, and testing. Separating the mask, win condition, input, and round logic keeps the program understandable.
Once the basic game is reliable, add one improvement at a time. External word files, categories, scores, command-line options, and a graphical interface all fit naturally on top of the same core rules.






