Python Central HubSign in

Python Projects / Intermediate

Anagram Game

Build a Tkinter anagram game in Python — scramble words, validate guesses, track score, then level it up with a real dictionary, hints, a countdown timer, difficulty tiers, and persistent high scores.

Abstract

An anagram game is a deceptively rich little project. On the surface it just scrambles a word and asks you to unscramble it — but underneath it touches randomization, string manipulation, input validation, event-driven GUI programming, and game state. In this tutorial you build a Tkinter anagram game on a short word list, then grow it into something you'd actually want to play: a real dictionary, hints, a per-round countdown timer, difficulty levels, and a high-score table saved to disk.

You will leave understanding:

  • How random.sample produces a scramble and why it can occasionally return the original word.
  • The Entry + Button + Label event loop that drives almost every small Tkinter app.
  • Why you normalize input (strip().lower()) before comparing strings.
  • How to separate game state (score, current word) from game view (the widgets).
  • How to add a non-blocking timer with after instead of sleep.

Prerequisites

  • Python 3.6 or above.
  • A text editor or IDE.
  • Tkinter (ships with Python; on Linux: sudo apt install python3-tk).
  • Comfort with functions, classes, and basic string methods. If GUIs are new, start with Calculator GUI.

Getting Started

Create the project

  1. Create a folder named anagram-game.
  2. Inside it, create anagram_game.py.
  3. Open the folder in your editor.

Write the code

anagram_game.pySource
anagram_game.py
"""
Anagram Game

A Python application that challenges users to form words from a scrambled set of letters.
Features include:
- Generating random anagrams.
- Validating user input against a dictionary.
- Keeping track of the score.
"""

import random
from tkinter import Tk, Label, Entry, Button, messagebox

# Sample word list
WORDS = ["python", "anagram", "challenge", "programming", "developer", "algorithm"]


class AnagramGame:
    def __init__(self, root):
        self.root = root
        self.root.title("Anagram Game")

        self.score = 0
        self.current_word = ""
        self.scrambled_word = ""

        Label(root, text="Unscramble the word:").grid(row=0, column=0, padx=10, pady=10)
        self.word_label = Label(root, text="", font=("Helvetica", 16))
        self.word_label.grid(row=1, column=0, padx=10, pady=10)

        Label(root, text="Your Answer:").grid(row=2, column=0, padx=10, pady=10)
        self.answer_entry = Entry(root, width=30)
        self.answer_entry.grid(row=3, column=0, padx=10, pady=10)

        Button(root, text="Submit", command=self.check_answer).grid(row=4, column=0, pady=10)
        Button(root, text="Next", command=self.next_word).grid(row=5, column=0, pady=10)

        self.next_word()

    def next_word(self):
        """Generate a new scrambled word."""
        self.current_word = random.choice(WORDS)
        self.scrambled_word = "".join(random.sample(self.current_word, len(self.current_word)))
        self.word_label.config(text=self.scrambled_word)
        self.answer_entry.delete(0, "end")

    def check_answer(self):
        """Check the user's answer and update the score."""
        user_answer = self.answer_entry.get().strip().lower()
        if user_answer == self.current_word:
            self.score += 1
            messagebox.showinfo("Correct!", f"Well done! Your score: {self.score}")
        else:
            messagebox.showerror("Incorrect", f"The correct word was: {self.current_word}")
        self.next_word()


def main():
    root = Tk()
    app = AnagramGame(root)
    root.mainloop()


if __name__ == "__main__":
    main()

Run it

cmd
C:\Users\Your Name\anagram-game> python anagram_game.py
# A window opens showing a scrambled word, an answer box,
# and Submit / Next buttons. Type the unscrambled word and Submit.

How it fits together

Read from the top: this is what runs when you execute the file, and which function calls which. It is generated from the code, so it cannot drift from it.

diagram Diagram mermaid

Step-by-Step Explanation

1. The word bank

Python
WORDS = ["python", "anagram", "challenge", "programming", "developer", "algorithm"]

A plain list is fine to start. Later you'll swap this for a dictionary file so the game never runs out of words.

2. Scrambling a word

Python
self.current_word = random.choice(WORDS)
self.scrambled_word = "".join(random.sample(self.current_word, len(self.current_word)))

random.choice picks the answer; random.sample(word, len(word)) returns the letters in a new random order. sample draws without replacement, so every letter is used exactly once — that's what makes it a true anagram rather than a random jumble. Catch: sample can occasionally return the letters in their original order, handing the player the answer for free. The fix is below.

3. Validating the guess

Python
user_answer = self.answer_entry.get().strip().lower()
if user_answer == self.current_word:
    self.score += 1
    messagebox.showinfo("Correct!", f"Well done! Your score: {self.score}")
else:
    messagebox.showerror("Incorrect", f"The correct word was: {self.current_word}")

.strip() kills stray spaces, .lower() makes the check case-insensitive — both essential when comparing free-text input. Skip them and "Python " (with a trailing space) reads as wrong.

4. State vs. view

The AnagramGame class holds state (score, current_word, scrambled_word) as attributes and view (the Label, Entry, Button) as widgets. next_word() mutates state then pushes it into the view with self.word_label.config(...). Keeping these two ideas separate is the single most useful habit you'll carry into bigger GUI apps.

Fix: Never Hand Out the Answer

Re-scramble until the result differs from the original:

Python
def scramble(word):
    if len(word) < 2:
        return word
    while True:
        letters = random.sample(word, len(word))
        candidate = "".join(letters)
        if candidate != word:
            return candidate

For a word with repeated letters (e.g. "letter") some scrambles look identical even when the underlying order changed — comparing strings is still the right check for the player's experience.

Use a Real Dictionary

Hard-coded lists get stale. Load thousands of words from a file:

Python
from pathlib import Path
import random
 
# One word per line. On Linux/Mac, /usr/share/dict/words exists.
WORDS = [
    w.strip().lower()
    for w in Path("words.txt").read_text().splitlines()
    if 4 <= len(w.strip()) <= 8 and w.strip().isalpha()
]
 
def new_word():
    return random.choice(WORDS)

Filtering by length (4 <= len <= 8) keeps rounds fair — 2-letter words are trivial, 15-letter words are cruel.

Add a Hint Button

Reveal the first letter, at the cost of points:

Python
def show_hint(self):
    self.hint_used = True
    first = self.current_word[0]
    self.hint_label.config(text=f"Starts with: {first.upper()}")

Award only half points (or zero) when self.hint_used is True. Hints turn a frustrating round into a learnable one.

Add a Per-Round Timer

Use Tkinter's after — never time.sleep, which freezes the whole window:

Python
def start_timer(self, seconds=20):
    self.time_left = seconds
    self.tick()
 
def tick(self):
    self.timer_label.config(text=f"Time: {self.time_left}s")
    if self.time_left <= 0:
        messagebox.showinfo("Time!", f"The word was: {self.current_word}")
        self.next_word()
        return
    self.time_left -= 1
    self._timer_id = self.root.after(1000, self.tick)   # schedule next tick

Cancel the pending tick whenever the player submits early: self.root.after_cancel(self._timer_id). Otherwise an old timer fires on the next word.

Common Mistakes

ProblemCauseFix
Scramble shows the real wordrandom.sample returned original orderLoop until candidate != word
"Correct" answer marked wrongTrailing space or capital lettersNormalize with .strip().lower() on both sides
Window freezes during countdownUsed time.sleep on the main threadUse root.after(1000, ...) instead
Timer fires on the wrong wordOld after callback still pendingTrack the id and after_cancel it on submit
Game crashes at end of listIndexed past the word bankPick randomly, or guard index < len(words)
Same word repeats constantlyTiny word listLoad from a dictionary file

Variations to Try

  1. Multiplayer — two players alternate; track separate scores.
  2. Categories — animals, countries, programming terms; pick a bank per round.
  3. Streak bonus — extra points for consecutive correct answers.
  4. Definitions — pull a definition from a dictionary API and show it as a hint.
  5. Daily challenge — seed random with today's date so everyone gets the same word.
  6. Difficulty tiers — short words = easy, long words = hard, shorter timer = harder.
  7. Leaderboard — persist (name, score, date) to JSON; show top 10 at launch.
  8. Web version — rebuild in Flask (see Simple Blog with Flask).

Real-World Applications

  • Language learning — vocabulary drills and spelling practice.
  • Brain-training apps — word games like those in Lumosity or NYT Games.
  • Classroom tools — teachers generating spelling exercises.
  • Onboarding mini-games — lightweight engagement in larger apps.

Educational Value

  • Randomization — choice, sample, and seeding.
  • String hygiene — normalizing user input before comparison.
  • Event-driven GUIs — the button-callback model.
  • Non-blocking timing — after vs. sleep, a lesson that scales to every GUI/async app.
  • State/view separation — the foundation of maintainable UI code.

Next Steps

  • Add the re-scramble fix so the answer is never given away.
  • Swap the list for a dictionary file and filter by length.
  • Add a hint button and a per-round timer.
  • Introduce difficulty tiers and a JSON high-score table.

Try it here

Click the scrambled tiles to build the answer (click a placed letter to take it back). Spell the hidden word to win, then click for a new one:

sketch Unscramble the word p5.js
Click letters to build the answer; click a placed letter to remove it. Spell the hidden word to win.

Checkpoint exercise

This checkpoint practices the core of the anagram game: checking two words and grouping words by their sorted letters.

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Conclusion

You built a working anagram game, hardened its weak spots (free answers, sloppy input matching, blocking timers), and sketched a path to a polished word game with dictionaries, hints, timers, and leaderboards. The same Entry → validate → update-state → refresh-view loop powers most small interactive apps you'll write. Full source on GitHub. Explore more games and tools on Python Central Hub.

Was this page helpful?

Let us know how we did