Anagram Game
Abstract
Section titled “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.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()) before comparing strings. - How to separate game state (score, current word) from game view (the widgets).
- How to add a non-blocking timer with
afterinstead ofsleep.
Prerequisites
Section titled “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
Section titled “Getting Started”Create the project
Section titled “Create the project”- Create a folder named
anagram-game. - Inside it, create
anagram_game.py. - Open the folder in your editor.
Write the code
Section titled “Write the code”anagram_game.py
pch.viewSource"""
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
Section titled “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.How it fits together
Section titled “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.
flowchart TD RUN(["python anagram_game.py"]) AnagramGame["AnagramGame
class"] main("main") RUN --> main main --> AnagramGame
Step-by-Step Explanation
Section titled “Step-by-Step Explanation”1. The word bank
Section titled “1. The word bank”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
Section titled “2. Scrambling a word”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
Section titled “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}").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
Section titled “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
Section titled “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 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
Section titled “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)Filtering by length (4 <= len <= 8) keeps rounds fair — 2-letter words are trivial, 15-letter words are cruel.
Add a Hint Button
Section titled “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()}")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
Section titled “Add a Per-Round Timer”Use Tkinter’s after — never time.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 tickCancel 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
Section titled “Common Mistakes”| Problem | Cause | Fix |
|---|---|---|
| Scramble shows the real word | random.sample returned original order | Loop until candidate != word |
| “Correct” answer marked wrong | Trailing space or capital letters | Normalize with .strip().lower() on both sides |
| Window freezes during countdown | Used time.sleep on the main thread | Use root.after(1000, ...) instead |
| Timer fires on the wrong word | Old after callback still pending | Track the id and after_cancel it on submit |
| Game crashes at end of list | Indexed past the word bank | Pick randomly, or guard index < len(words) |
| Same word repeats constantly | Tiny word list | Load from a dictionary file |
Variations to Try
Section titled “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
randomwith 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)to JSON; show top 10 at launch. - Web version — rebuild in Flask (see Simple Blog with Flask).
Real-World Applications
Section titled “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
Section titled “Educational Value”- Randomization —
choice,sample, and seeding. - String hygiene — normalizing user input before comparison.
- Event-driven GUIs — the button-callback model.
- Non-blocking timing —
aftervs.sleep, a lesson that scales to every GUI/async app. - State/view separation — the foundation of maintainable UI code.
Next Steps
Section titled “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
Section titled “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
Section titled “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.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading