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.sampleproduces 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
afterafterinstead ofsleepsleep.
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
- Create a folder named
anagram-gameanagram-game. - Inside it, create
anagram_game.pyanagram_game.py. - Open the folder in your editor.
Write the code
anagram_game.py
Source"""
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
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
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.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
WORDS = ["python", "anagram", "challenge", "programming", "developer", "algorithm"]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
self.current_word = random.choice(WORDS)
self.scrambled_word = "".join(random.sample(self.current_word, len(self.current_word)))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
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}")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:
def scramble(word):
if len(word) < 2:
return word
while True:
letters = random.sample(word, len(word))
candidate = "".join(letters)
if candidate != word:
return candidatedef scramble(word):
if len(word) < 2:
return word
while True:
letters = random.sample(word, len(word))
candidate = "".join(letters)
if candidate != word:
return candidateFor 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:
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)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:
def show_hint(self):
self.hint_used = True
first = self.current_word[0]
self.hint_label.config(text=f"Starts with: {first.upper()}")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:
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 tickdef 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 tickCancel 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
| Problem | Cause | Fix |
|---|---|---|
| Scramble shows the real word | random.samplerandom.sample returned original order | Loop until candidate != wordcandidate != word |
| “Correct” answer marked wrong | Trailing space or capital letters | Normalize with .strip().lower().strip().lower() on both sides |
| Window freezes during countdown | Used time.sleeptime.sleep on the main thread | Use root.after(1000, ...)root.after(1000, ...) instead |
| Timer fires on the wrong word | Old afterafter callback still pending | Track the id and after_cancelafter_cancel it on submit |
| Game crashes at end of list | Indexed past the word bank | Pick randomly, or guard index < len(words)index < len(words) |
| Same word repeats constantly | Tiny word list | Load from a dictionary file |
Variations to Try
- Multiplayer — two players alternate; track separate scores.
- Categories — animals, countries, programming terms; pick a bank per round.
- Streak bonus — extra points for consecutive correct answers.
- Definitions — pull a definition from a dictionary API and show it as a hint.
- Daily challenge — seed
randomrandomwith today’s date so everyone gets the same word. - Difficulty tiers — short words = easy, long words = hard, shorter timer = harder.
- Leaderboard — persist
(name, score, date)(name, score, date)to JSON; show top 10 at launch. - 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 —
choicechoice,samplesample, and seeding. - String hygiene — normalizing user input before comparison.
- Event-driven GUIs — the button-callback model.
- Non-blocking timing —
afteraftervs.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:
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 coffeeWas this page helpful?
Let us know how we did
