Editor

Word Guessing Game in Python

Intermediate · Games · about 60 minutes to build · 107 lines

You have six tries to find a secret five-letter word, and every guess shows which letters are in the right spot, elsewhere in the word or not in it.

The program

Press Run and answer its questions in the box that appears under the code.

word_guessing_game.py
"""Word guessing game: find the secret five-letter word in six tries."""
import random
WORD_LENGTH = 5
MAX_TRIES = 6
WORDS = [
"apple", "brave", "chair", "cloud", "dance", "eagle", "flame", "grape",
"house", "jelly", "knife", "lemon", "magic", "night", "ocean", "pearl",
"plant", "queen", "river", "sheep", "smile", "stone", "tiger", "train",
"voice", "water", "zebra", "bread", "lucky", "spoon",
]
RIGHT_SPOT = "+"
WRONG_SPOT = "?"
NOT_IN_WORD = "."
def ask_guess(tried):
"""Ask until the player types a new word of WORD_LENGTH letters."""
while True:
guess = input(f"Guess {len(tried) + 1} of {MAX_TRIES}: ").strip().lower()
if len(guess) != WORD_LENGTH or not (guess.isascii() and guess.isalpha()):
print(f" Please type a word of {WORD_LENGTH} letters.")
elif guess in tried:
print(" You already tried that word.")
else:
return guess
def score_guess(guess, secret):
"""Return one mark per letter: right spot, wrong spot or not in the word."""
marks = [NOT_IN_WORD] * WORD_LENGTH
unmatched = []
# First pass: letters in the right spot.
for i in range(WORD_LENGTH):
if guess[i] == secret[i]:
marks[i] = RIGHT_SPOT
else:
unmatched.append(secret[i])
# Second pass: letters that are in the word but elsewhere. Each letter of
# the secret is used once, so a guess with two e's gets one "?" for one e.
for i in range(WORD_LENGTH):
if marks[i] == NOT_IN_WORD and guess[i] in unmatched:
marks[i] = WRONG_SPOT
unmatched.remove(guess[i])
return marks
def show_feedback(guess, marks):
"""Print the guess with its marks underneath."""
print(" " + " ".join(guess.upper()))
print(" " + " ".join(marks))
def play_round(secret):
"""Play one game and return True if the player finds the word."""
tried = []
ruled_out = set()
while len(tried) < MAX_TRIES:
guess = ask_guess(tried)
tried.append(guess)
marks = score_guess(guess, secret)
show_feedback(guess, marks)
if guess == secret:
print(f"\nYou found {secret.upper()} in {len(tried)} of {MAX_TRIES} tries.")
return True
# A letter is ruled out only if it appears nowhere in the secret.
ruled_out.update(ch for ch in guess if ch not in secret)
letters = " ".join(sorted(ruled_out)).upper()
print(f" Not in the word: {letters or 'none yet'}")
print()
print(f"Out of tries. The word was {secret.upper()}.")
return False
def ask_yes_no(prompt):
"""Ask a yes/no question and return True for yes."""
while True:
answer = input(prompt).strip().lower()
if answer in ("y", "yes"):
return True
if answer in ("n", "no"):
return False
print(" Please type y or n.")
def main():
"""Explain the marks, then play rounds until the player stops."""
print("=== Word Guessing Game ===")
print(f"Find the secret {WORD_LENGTH}-letter word in {MAX_TRIES} tries.")
print("Marks under each letter:")
print(f" {RIGHT_SPOT} right spot {WRONG_SPOT} in the word, other spot "
f"{NOT_IN_WORD} not in the word\n")
while True:
play_round(random.choice(WORDS))
print()
if not ask_yes_no("Play again? (y/n) "):
break
print()
print("Thanks for playing.")
if __name__ == "__main__":
main()

Open it in the full editor to save it, share it and keep building.

Sample run

What the program printed, and what was typed (in yellow).

=== Word Guessing Game ===
Find the secret 5-letter word in 6 tries.
Marks under each letter:
  + right spot   ? in the word, other spot   . not in the word

Guess 1 of 6: crane
  C R A N E
  . . . ? +
  Not in the word: A C R

Guess 2 of 6: 12345
  Please type a word of 5 letters.
Guess 2 of 6: noise
  N O I S E
  ? . + . +
  Not in the word: A C O R S

Guess 3 of 6: crane
  You already tried that word.
Guess 3 of 6: whine
  W H I N E
  . . + ? +
  Not in the word: A C H O R S W

Guess 4 of 6: knife
  K N I F E
  + + + + +

You found KNIFE in 4 of 6 tries.

Play again? (y/n) n
Thanks for playing.

How it is built

1. The words and the marks

The marks are constants, so you can swap +, ? and . for other symbols in one place.

WORD_LENGTH = 5
MAX_TRIES = 6
WORDS = [
    "apple", "brave", "chair", "cloud", "dance", "eagle", "flame", "grape",
    "house", "jelly", "knife", "lemon", "magic", "night", "ocean", "pearl",
    "plant", "queen", "river", "sheep", "smile", "stone", "tiger", "train",
    "voice", "water", "zebra", "bread", "lucky", "spoon",
]
RIGHT_SPOT = "+"
WRONG_SPOT = "?"
NOT_IN_WORD = "."

2. Checking a guess

A guess must be five letters from a to z: isascii() rules out accented letters and isalpha() rules out digits and spaces. A word already tried is refused without using up a try.

def ask_guess(tried):
    """Ask until the player types a new word of WORD_LENGTH letters."""
    while True:
        guess = input(f"Guess {len(tried) + 1} of {MAX_TRIES}: ").strip().lower()
        if len(guess) != WORD_LENGTH or not (guess.isascii() and guess.isalpha()):
            print(f"  Please type a word of {WORD_LENGTH} letters.")
        elif guess in tried:
            print("  You already tried that word.")
        else:
            return guess

3. Marking each letter

The first pass marks letters in the right spot and collects the secret's other letters in unmatched. The second pass gives a ? only while a matching letter is left in unmatched, so two e's in a guess get one ? when the secret has one e.

def score_guess(guess, secret):
    """Return one mark per letter: right spot, wrong spot or not in the word."""
    marks = [NOT_IN_WORD] * WORD_LENGTH
    unmatched = []

    # First pass: letters in the right spot.
    for i in range(WORD_LENGTH):
        if guess[i] == secret[i]:
            marks[i] = RIGHT_SPOT
        else:
            unmatched.append(secret[i])

    # Second pass: letters that are in the word but elsewhere. Each letter of
    # the secret is used once, so a guess with two e's gets one "?" for one e.
    for i in range(WORD_LENGTH):
        if marks[i] == NOT_IN_WORD and guess[i] in unmatched:
            marks[i] = WRONG_SPOT
            unmatched.remove(guess[i])
    return marks

4. Six tries

The loop runs until tried holds MAX_TRIES words. ruled_out is a set, so a letter guessed twice is listed once, and sorted() prints the letters in alphabetical order.

def play_round(secret):
    """Play one game and return True if the player finds the word."""
    tried = []
    ruled_out = set()
    while len(tried) < MAX_TRIES:
        guess = ask_guess(tried)
        tried.append(guess)
        marks = score_guess(guess, secret)
        show_feedback(guess, marks)
        if guess == secret:
            print(f"\nYou found {secret.upper()} in {len(tried)} of {MAX_TRIES} tries.")
            return True

        # A letter is ruled out only if it appears nowhere in the secret.
        ruled_out.update(ch for ch in guess if ch not in secret)
        letters = " ".join(sorted(ruled_out)).upper()
        print(f"  Not in the word: {letters or 'none yet'}")
        print()
    print(f"Out of tries. The word was {secret.upper()}.")
    return False

Make it bigger

  • Accept only real words by checking each guess against a longer word list.
  • Print the alphabet after each guess with ruled-out letters replaced by dots, like a keyboard.
  • Add a hard mode where a letter marked + must stay in the same spot in later guesses.
  • Keep statistics across games: games won, the current streak and how many tries each win took.

What it practises