Word Search++
A browser-based word search puzzle built on the Canvas 2D API. Pick a category or use the Random pool, set your grid size, and find all hidden words to complete the puzzle. Visit /puzzles/wordsearch.
Architecture
The puzzle is entirely client-side JavaScript. The server provides:
- The template (
templates/wordsearch.html) — page layout with controls (category, grid size, generate), the canvas area, and a word-list sidebar. - The game script (
static/js/wordsearch.js) — ~690 lines handling grid generation, canvas rendering, mouse/touch interaction, timer tracking, and the Random-word API fetch. Exports pure functions for Node.js testing. - The game route (
src/routers/wordsearch.py) — serves the template and the/api/wordsearch/random-wordsendpoint. - The word list (
data/words/english.txt) — ~132K curated English words (3–10 letters, lowercase alpha only) filtered from the system dictionary.
Grid Generation
When the user clicks Generate, the following happens:
- An
N×Ngrid (e.g. 20×20) is created as a 2D array ofnullcells. - A random subset of words is selected from the active category. For the Random category, words are fetched from
/api/wordsearch/random-words?count=XwhereX = max(5, min(40, floor(size / 2))). - Words are shuffled, sorted longest-first (harder to place), and the top X are kept.
- Each word is placed by randomly choosing a starting cell and a direction (8 directions: N, S, E, W, NE, NW, SE, SW). Up to 300 attempts are made per word.
- Placement is rejected if any letter in the word would conflict with an existing letter in the grid. Intersections where letters match are allowed.
- Remaining empty cells are filled with random uppercase A–Z letters.
- The grid is rendered on the canvas and the word list appears in the sidebar.
Word placement algorithm
for each word (sorted longest-first):
for up to 300 attempts:
pick random direction (0–7)
pick random starting cell
if word fits within grid bounds and all intersecting cells match:
place the word
record cell positions
break
Direction mapping:
| Index | Direction | Offset (dr, dc) |
|---|---|---|
| 0 | Right | (0, +1) |
| 1 | Down-Right | (+1, +1) |
| 2 | Down | (+1, 0) |
| 3 | Down-Left | (+1, -1) |
| 4 | Left | (0, -1) |
| 5 | Up-Left | (-1, -1) |
| 6 | Up | (-1, 0) |
| 7 | Up-Right | (-1, +1) |
Canvas Rendering
The canvas fills its container at 100% width with auto height. Cell size is calculated as max(10, min(50, availableWidth / cols)) — capped at 50px so cells don't become comically large on small grids. The grid is horizontally centred within the canvas.
Colours are read from CSS custom properties (--bs-body-bg, --bs-body-color, --bs-primary, --bs-success, --bs-border-color) to match the active Bootswatch theme. When the theme changes, a load event listener on the theme stylesheet triggers a re-render.
HiDPI support: the canvas buffer is scaled by window.devicePixelRatio while CSS display size stays at 100%, ensuring crisp rendering on Retina displays.
Interaction
Selecting letters
Click and drag across the canvas to highlight a contiguous line of cells. The selection is constrained to the 8 major axes (horizontal, vertical, 45° diagonal) by determining the dominant direction from the start cell to the current mouse position.
Confirming a word
Click a word in the sidebar word list. The highlighted selection is read left-to-right and right-to-left and compared to the clicked word. If either direction matches, the word is marked as found — cells on the grid turn green and the word in the list is strikethrough.
Direction hints
By default, direction indicators (arrow badges) next to each word are hidden. Click Reveal Hints to show them; click Hide Hints to hide them again.
Timer
A timer starts when the puzzle is generated. It ticks in the status bar (0h 0m 0s format) and stops when all words are found. A completion modal shows your time, word count, and grid size. The timer also stops if you click Reveal All.
Word Categories
| Category | Source | Word count |
|---|---|---|
| Animals | Curated list | 39 |
| Countries | Curated list | 30 |
| Programming | Curated list | 30 |
| Space | Curated list | 26 |
| Food | Curated list | 32 |
| Random | data/words/english.txt |
~132K |
The Random category fetches a fresh subset from the server each time, guaranteeing variety.
Files
| File | Role |
|---|---|
templates/wordsearch.html |
Game template (layout, styles, modals) |
static/js/wordsearch.js |
Game engine (grid generation, rendering, interaction) |
src/routers/wordsearch.py |
GET /puzzles/wordsearch + GET /api/wordsearch/random-words |
data/words/english.txt |
Curated 132K-word pool for Random mode |
tests/test_routes.py |
Route tests for puzzle pages and API endpoint |