An optimized Wordle solver with a Telegram bot interface and REST API, deployable on PythonAnywhere. Uses a precomputed pattern matrix with min-max ranking to deliver hints in milliseconds.
- Telegram Bot (
flask_app.py): Play Wordle or get solving hints via Telegram commands (/wordle,/solver,/hint,/rollback). - REST API (
/get-hint/<guessAndScore>): Returns the optimal next guess given a history of guesses and scores — designed for a Chrome extension frontend. - Solver Engine: Uses min-max optimization (minimize the worst-case remaining group size) to find the best guess at each step. The full 13,033 × 13,033 pattern matrix is precomputed once and loaded at server startup for O(1) pattern lookups.
The low-level scoring engine. Converts words to integer matrices and scores one guess against ALL answers in a single vectorized NumPy operation. Handles:
- Pattern encoding (base-3 integers via
PATTERN_WEIGHTS = [81, 27, 9, 3, 1]) - Index-based answer space filtering (boolean masks on integer arrays, no DataFrame copies)
- Word list loading and validation
Built on wordle_fast.py's core engine. Key features:
- Precomputed pattern matrix: 13,033 × 13,033 stored as 2 compressed
.npzchunks (~37 MB each), loaded into RAM at startup and shared across all user sessions - Min-max ranking: Picks the guess that minimizes the largest remaining candidate group, directly bounding worst-case performance to ≤6 moves for 98.24% of words
- Optimal opener: Precomputes the best first guess (SERAI) at startup using min-max over the full dictionary
- O(1) pattern lookups:
matrix[guess_idx]instead of computing scores on the fly
Result: 230 failures out of 13,033 words (1.76%), all on obscure/non-corpus words.
wordle_api/
├── wordle_fast.py # Core scoring engine (vectorized NumPy)
├── wordle_solver.py # Min-max solver with precomputed matrix
├── flask_app.py # Flask app + Telegram bot (uses wordle_solver)
├── build_pattern_matrix.py # One-time script to build .npz chunks
├── pattern_matrix_chunk0.npz # Precomputed matrix — rows 0–6516
├── pattern_matrix_chunk1.npz # Precomputed matrix — rows 6517–13032
├── pattern_matrix_meta.npz # Metadata (word list, shapes, version)
├── validated__words.csv # 13,033 five-letter words
├── test_all_words_solve.py # Full solver test (all words as answers)
└── README.md # This file
Returns the single best next guess.
GET /get-hint/serai-00000
Response:
{
"hint": "ponty",
"possible_choices": 629,
"guess_and_score": {"serai": "00000"}
}Returns the top-5 guesses plus progress tracking.
GET /get-hint-multi/serai-12011-strew-11220
Response:
{
"hint": "tares",
"possible_choices": 1,
"guess_and_score": {"serai": "12011", "strew": "11220"},
"top_hints": ["tares", ...],
"progress_tracker": [32, 1]
}Format: <guess><score> pairs separated by -. Score is a 5-digit string of 0 (grey), 1 (yellow), 2 (green).
| Command | Description |
|---|---|
/start |
Show available commands |
/wordle |
Start a new Wordle game (bot picks a secret word) |
/solver |
Start solver mode (you provide guess + score from your own game) |
/hint |
Get the optimal next guess |
/rollback |
Undo the last guess (solver mode only) |
- A PythonAnywhere account (free tier works, paid has more RAM)
- Python 3.10+ environment
Via PythonAnywhere's Files tab or git clone:
wordle_api/
├── wordle_fast.py
├── wordle_solver.py
├── flask_app.py
├── build_pattern_matrix.py
├── validated__words.csv
├── pattern_matrix_chunk0.npz (if already built)
├── pattern_matrix_chunk1.npz (if already built)
└── pattern_matrix_meta.npz (if already built)
Open a Bash console on PythonAnywhere:
pip install flask numpy pandas telepot urllib3If you didn't upload the .npz files:
python build_pattern_matrix.pyThis takes ~60–90 seconds and produces the 3 .npz files (~75 MB total).
- Go to the Web tab and create a new web app
- Choose Manual configuration → Python 3.10+
- Set the source code directory to the path containing your files
- Set the WSGI configuration file to point to
flask_app.py'sappobject
Example WSGI file (/var/www/yourusername_pythonanywhere_com_wsgi.py):
import sys
sys.path.insert(0, '/home/yourusername/wordle_api')
from flask_app import app as applicationIn flask_app.py, update the webhook URL:
bot.setWebhook("https://yourusername.pythonanywhere.com/{}".format(secret), max_connections=15)Click the Reload button on the Web tab. The first reload takes ~10 seconds (matrix loads from disk). If the matrix hasn't been built yet, the first reload takes ~90 seconds.
- The pattern matrix uses ~170 MB of RAM after loading
- PythonAnywhere free tier: 512 MB limit — this fits comfortably
- Each additional user session adds negligible memory (~a few KB for
answer_indices)
| Scenario | Time |
|---|---|
| Matrix load at startup | ~0.7 seconds |
| First hint (full 13k ranking) | ~0.2 seconds |
| Subsequent hints (~100 candidates) | ~50 ms |
| Full game solve (6 turns) | ~0.2 seconds total |
| New user session creation | instant (shared matrix) |
Solver accuracy (tested against all 13,033 words):
- 98.24% solved in ≤6 moves (230 failures, all on obscure/rare words)
- Average solve: ~4.2 moves
- Optimal opener: SERAI (worst-case bucket: 714)
Wordle feedback for a 5-letter guess is encoded as a base-3 integer:
0— letter not in the word (grey)1— letter in the word but wrong position (yellow)2— letter in correct position (green)
PATTERN_WEIGHTS = [81, 27, 9, 3, 1]
# "21012" → 2×81 + 1×27 + 0×9 + 1×3 + 2×1 = 194The solver ranks guesses by three criteria in order:
- Smallest worst-case bucket — minimizes the maximum remaining group after the guess
- Possible answer preference — biases toward guesses that could be the actual answer
- Entropy — tiebreaker using expected information gain
This contrasts with pure entropy minimization, which optimizes the average case but doesn't bound the worst case. Min-max directly reduces the number of 7-move failures.