Skip to content

Anagram Game

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.samplerandom.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()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 afterafter instead of sleepsleep.

Prerequisites

  • Python 3.6 or above.
  • A text editor or IDE.
  • Tkinter (ships with Python; on Linux: sudo apt install python3-tksudo 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-gameanagram-game.
  2. Inside it, create anagram_game.pyanagram_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()
 
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

command
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.
command
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.

Step-by-Step Explanation

1. The word bank

anagram_game.py
WORDS = ["python", "anagram", "challenge", "programming", "developer", "algorithm"]
anagram_game.py
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

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

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

3. Validating the guess

anagram_game.py
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}")
anagram_game.py
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().strip() kills stray spaces, .lower().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 AnagramGameAnagramGame class holds state (scorescore, current_wordcurrent_word, scrambled_wordscrambled_word) as attributes and view (the LabelLabel, EntryEntry, ButtonButton) as widgets. next_word()next_word() mutates state then pushes it into the view with self.word_label.config(...)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:

fix.py
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
fix.py
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:

dictionary.py
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)
dictionary.py
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 <= 84 <= 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:

hint.py
def show_hint(self):
    self.hint_used = True
    first = self.current_word[0]
    self.hint_label.config(text=f"Starts with: {first.upper()}")
hint.py
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_usedself.hint_used is TrueTrue. Hints turn a frustrating round into a learnable one.

Add a Per-Round Timer

Use Tkinter’s afterafter — never time.sleeptime.sleep, which freezes the whole window:

timer.py
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
timer.py
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)self.root.after_cancel(self._timer_id). Otherwise an old timer fires on the next word.

Common Mistakes

ProblemCauseFix
Scramble shows the real wordrandom.samplerandom.sample returned original orderLoop until candidate != wordcandidate != word
“Correct” answer marked wrongTrailing space or capital lettersNormalize with .strip().lower().strip().lower() on both sides
Window freezes during countdownUsed time.sleeptime.sleep on the main threadUse root.after(1000, ...)root.after(1000, ...) instead
Timer fires on the wrong wordOld afterafter callback still pendingTrack the id and after_cancelafter_cancel it on submit
Game crashes at end of listIndexed past the word bankPick randomly, or guard index < len(words)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 randomrandom 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)(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

  • Randomizationchoicechoice, samplesample, and seeding.
  • String hygiene — normalizing user input before comparison.
  • Event-driven GUIs — the button-callback model.
  • Non-blocking timingafterafter vs. sleepsleep, 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.

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.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did