An autonomous Wordle-style solver that reads game state directly from PIL Images using pixel color sampling, then applies constraint-based filtering with an information-gain heuristic to solve the word in as few guesses as possible.
Built across three progressively harder assignments — text feedback, then image feedback, then an optimised solving strategy — and packaged here as a standalone, runnable project.
WordPy is a Wordle clone. A target word is chosen from a dictionary of 5,922 real 5-letter English words. The bot has 6 guesses to find it. After each guess the game engine renders a feedback image: one colored block per letter.
| Block color | Hex | Meaning |
|---|---|---|
| Dark blue | #00274C |
Correct letter, correct position |
| Yellow | #FFCB05 |
Letter exists in the word, wrong position |
| Grey | #D3D3D3 |
Letter is not in the word |
The bot doesn't receive text feedback — it reads the PIL Image directly. For each letter block, it samples a pixel from the top-left corner of the block (x=block_left+2, y=2). The corner is always pure background color regardless of font or antialiasing, so color classification is exact:
block_x = i * (block_width + space_between_letters)
sample_x, y = block_x + 2, 2
hex_color = f"#{r:02X}{g:02X}{b:02X}" # _tuple_to_str()Block geometry comes from DisplaySpecification, so the pixel math automatically adjusts if you change block size, spacing, or font.
After decoding each image, three constraint sets are updated:
known_letters[i] — confirmed letter at position i (blue blocks)
in_word_letters — letters present but position unknown (yellow blocks)
excluded_letters — letters absent from the word (grey blocks)
From the filtered candidate pool, the bot picks the word with the most unique letters — a greedy information-gain heuristic that maximises new letters tested per guess:
best = max(remaining_words, key=lambda w: len(set(w)))WordPy-Solver/
├── main.py # CLI entry point — run and configure everything from here
├── requirements.txt
├── wordpy/
│ ├── __init__.py # Package exports
│ ├── letter.py # Letter class — single character with feedback state
│ ├── display_spec.py # DisplaySpecification — colors, fonts, pixel geometry
│ ├── game_engine.py # GameEngine — word validation, PIL rendering, game loop
│ └── bot.py # Bot — image reader + constraint solver (your code)
├── assets/
│ ├── words.txt # 5,922 unique 5-letter English words (real Wordle list)
│ └── fonts/ # 8 bundled fonts — swap without changing any code
│ ├── Poppins-Bold.ttf (modern geometric sans-serif)
│ ├── Poppins-Regular.ttf (lighter weight)
│ ├── DejaVuSans-Bold.ttf (clean sans, similar to Open Sans)
│ ├── DejaVuSerif-Bold.ttf (traditional serif)
│ ├── Caladea-Bold.ttf (Calibri-compatible)
│ ├── Carlito-Bold.ttf (Calibri-compatible, metrically identical)
│ ├── LiberationSans-Bold.ttf (Arial-compatible)
│ └── Lora-Bold.ttf (elegant display serif)
└── tests/
└── test_all.py # 36 tests covering all classes and the full pipeline
Attribution:
game_engine.pyis based on the GameEngine provided in the course assignments.letter.py,display_spec.py,bot.py, and the solving strategy are my own work.
git clone https://github.com/Waqas01CP/WordPy-Solver
cd WordPy-Solver
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt# Random target word, default Michigan blue/maize theme
python main.py
# Specific target word
python main.py --word FIELD
# Different visual theme
python main.py --word CRANE --theme dark
python main.py --word STATE --theme neon
# Different font (any filename from assets/fonts/ without .ttf)
python main.py --word FIELD --font Caladea-Bold
python main.py --word FIELD --font DejaVuSerif-Bold
python main.py --word FIELD --font Lora-Bold
# Save each guess as a PNG image (for demos)
python main.py --word FIELD --save-images
# List all available fonts
python main.py --list-fonts
# Benchmark solve rate over 100 random games
python main.py --benchmark 100Playing a game of WordyPy using the word list file of assets/words.txt.
The target word for this round is FIELD
Evaluating bot guess of ABHOR
Was this guess correct? False
Sending guess results to bot: ?????
Updated known letters: [None, None, None, None, None]
Updated excluded letters: {'A', 'B', 'H', 'O', 'R'}
Evaluating bot guess of SWEPT
Was this guess correct? False
Sending guess results to bot: ?????
Evaluating bot guess of GUILD
Was this guess correct? False
Sending guess results to bot: ?*I*D
Updated known letters: [None, None, 'I', None, 'D']
Updated excluded letters: {...}
Evaluating bot guess of FIELD
Was this guess correct? True
Sending guess results to bot: FIELD
Great job, you found the target word in 4 guesses!
# Run directly
python tests/test_all.py
# Or with pytest
pip install pytest
python -m pytest tests/ -vThe test suite covers:
Letterclass: attributes, feedback states, uppercase handlingBot._tuple_to_str: RGB/RGBA to hex conversion (including the assignment's example cases)Bot._process_image: correct, in-word, absent, and mixed pixel patterns- Full game integration: 5-word demo list, invalid words, no repeats
DisplaySpecification: defaults, all four themes, font loading
Drop any .ttf file into assets/fonts/ and refer to it by filename (without extension):
cp ~/Downloads/MyFont-Bold.ttf assets/fonts/
python main.py --font MyFont-Bold --word FIELDNo code changes needed — display_spec.py discovers all .ttf files in that directory automatically.
Python 3.10+ · Pillow (PIL) · Object-Oriented Design · Constraint Filtering · Greedy Information-Gain Heuristic