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
setsetis 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
- The computer picks a secret word.
- The player sees blanks:
_ _ _ _ _ _ __ _ _ _ _ _ _. - Player guesses one letter at a time.
- Correct letters fill in their positions.
- Wrong letters cost one “limb” of the hanged figure.
- 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
whilewhileloops,if/elseif/else, and basic file reading.
Getting Started
Create the project
- Create a folder named
hangman-gamehangman-game. - Inside it, create
hangman.pyhangman.py. - Create
words.txtwords.txtnext to it. Suggested starter contents:words.txtpython programming computer science technology development software coding algorithm function variable recursion compiler interpreterwords.txtpython programming computer science technology development software coding algorithm function variable recursion compiler interpreter
Write the code
Hangman
Source# 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 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
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: ...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
import random
import timeimport random
import timerandom.choicerandom.choice picks the word. time.sleeptime.sleep adds drama.
2. Load and pick a word
with open("words.txt", encoding="utf-8") as f:
words = [line.strip().lower() for line in f if line.strip()]
word = random.choice(words)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
guessed_letters = set()
wrong = 0
max_wrong = 6guessed_letters = set()
wrong = 0
max_wrong = 6Why a set for guessed_lettersguessed_letters?
letter in guessed_lettersletter in guessed_lettersis 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
def display(word: str, guessed: set[str]) -> str:
return " ".join(c if c in guessed else "_" for c in word)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
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}'.")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. elseelseclause on awhilewhileruns 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:
STAGES = [
r"""
-----
| |
|
|
|
|
=========""",
r"""
-----
| |
O |
|
|
|
=========""",
r"""
-----
| |
O |
| |
|
|
=========""",
r"""
-----
| |
O |
/| |
|
|
=========""",
r"""
-----
| |
O |
/|\ |
|
|
=========""",
r"""
-----
| |
O |
/|\ |
/ |
|
=========""",
r"""
-----
| |
O |
/|\ |
/ \ |
|
=========""",
]
print(STAGES[wrong]) # in the main loopSTAGES = [
r"""
-----
| |
|
|
|
|
=========""",
r"""
-----
| |
O |
|
|
|
=========""",
r"""
-----
| |
O |
| |
|
|
=========""",
r"""
-----
| |
O |
/| |
|
|
=========""",
r"""
-----
| |
O |
/|\ |
|
|
=========""",
r"""
-----
| |
O |
/|\ |
/ |
|
=========""",
r"""
-----
| |
O |
/|\ |
/ \ |
|
=========""",
]
print(STAGES[wrong]) # in the main loopSix 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
| Problem | Cause | Fix |
|---|---|---|
| Word contains a newline character | Forgot .strip().strip() after readlines()readlines() | Use the list-comp above |
| Capital letters never match | Mixed cases | Lowercase both the word and the guess |
| Pressing Enter empty crashes the game | No length check | Validate len(guess) == 1len(guess) == 1 |
| Counting wrong letters when a duplicate is pressed | No duplicate check | Skip already-guessed letters |
| Win never triggers | Used ==== 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:
fruits.txt
animals.txt
programming.txtfruits.txt
animals.txt
programming.txtAsk the user to pick a category. Each .txt.txt becomes a PathPath:
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()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:
if wrong == 4:
for c in word:
if c not in guessed_letters:
print(f"Hint: contains '{c}'")
guessed_letters.add(c)
breakif wrong == 4:
for c in word:
if c not in guessed_letters:
print(f"Hint: contains '{c}'")
guessed_letters.add(c)
break4. 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
while True:
play_round()
if input("Play again? (y/n): ").lower() != "y":
breakwhile True:
play_round()
if input("Play again? (y/n): ").lower() != "y":
break6. 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:
import requests
word = requests.get("https://random-word-api.herokuapp.com/word").json()[0]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:
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_wrongclass 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_wrongNow 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
STAGESSTAGESlist above. - Add categories and difficulty levels.
- Wrap state in a
HangmanHangmanclass and add unit tests withpytestpytest. - 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 coffeeWas this page helpful?
Let us know how we did
