205 lines
7.5 KiB
Markdown
205 lines
7.5 KiB
Markdown
# Crossword Puzzle Generator for Amazon KDP
|
||||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
A Python CLI tool that reads words and clues from CSV files, generates crossword puzzle grids, and outputs print-ready PDF books formatted for Amazon KDP upload.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## How It Works (High Level)
|
|||
|
|
|
|||
|
|
1. **Read** a CSV file containing words and their clues
|
|||
|
|
2. **Generate** crossword grids by placing words on a grid so they interlock at shared letters
|
|||
|
|
3. **Render** each puzzle and its solution as pages in a print-ready PDF
|
|||
|
|
4. **Format** the PDF to meet KDP's trim size, margin, and bleed requirements
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Input Format
|
|||
|
|
|
|||
|
|
A CSV file with at minimum two columns:
|
|||
|
|
|
|||
|
|
```csv
|
|||
|
|
word,clue
|
|||
|
|
PYTHON,"A large snake, or a programming language"
|
|||
|
|
GRID,"A network of lines that cross each other"
|
|||
|
|
PUZZLE,"A game or problem that tests ingenuity"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Optional columns:
|
|||
|
|
- `difficulty` — to group words into easy/medium/hard puzzles
|
|||
|
|
- `category` — to generate themed puzzles (e.g., "Animals", "Science")
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Core Components
|
|||
|
|
|
|||
|
|
### 1. CSV Parser (`csv_reader.py`)
|
|||
|
|
|
|||
|
|
- Read and validate the CSV (reject duplicates, too-short words, non-alpha characters)
|
|||
|
|
- Normalize words to uppercase
|
|||
|
|
- Group words by category/difficulty if those columns exist
|
|||
|
|
- Return a list of `(word, clue)` tuples ready for the generator
|
|||
|
|
|
|||
|
|
### 2. Crossword Generator (`generator.py`)
|
|||
|
|
|
|||
|
|
This is the core algorithm. It places words onto a 2D grid so they intersect at shared letters.
|
|||
|
|
|
|||
|
|
**Algorithm (backtracking with heuristics):**
|
|||
|
|
|
|||
|
|
1. Sort words longest-first (longer words are harder to place, so do them early)
|
|||
|
|
2. Place the first word horizontally in the center of the grid
|
|||
|
|
3. For each remaining word:
|
|||
|
|
a. Find all candidate positions where it intersects an already-placed word at a shared letter
|
|||
|
|
b. Score each candidate (prefer positions that keep the grid compact and balanced)
|
|||
|
|
c. Place the word at the best valid position (no collisions, no unintended adjacencies)
|
|||
|
|
d. If no valid position exists, skip the word (or backtrack)
|
|||
|
|
4. Number the cells (across and down) in reading order (left-to-right, top-to-bottom)
|
|||
|
|
5. Return the grid, placed words with their positions/directions, and numbered clue lists
|
|||
|
|
|
|||
|
|
**Grid rules to enforce:**
|
|||
|
|
- Words cannot run parallel and adjacent (would create unintended mini-words)
|
|||
|
|
- Every letter in the grid must belong to a placed word
|
|||
|
|
- Minimum grid density target to avoid sparse-looking puzzles
|
|||
|
|
- Configurable grid size (e.g., 15x15 for standard, 10x10 for easy)
|
|||
|
|
|
|||
|
|
**Output of this step:** a `Crossword` object containing:
|
|||
|
|
- The letter grid (2D array)
|
|||
|
|
- List of placed words with start position, direction, and clue number
|
|||
|
|
- Across clues list (numbered)
|
|||
|
|
- Down clues list (numbered)
|
|||
|
|
|
|||
|
|
### 3. PDF Renderer (`renderer.py`)
|
|||
|
|
|
|||
|
|
Uses **ReportLab** to generate print-ready PDFs.
|
|||
|
|
|
|||
|
|
**Each puzzle produces two pages:**
|
|||
|
|
|
|||
|
|
1. **Puzzle page:**
|
|||
|
|
- Title (e.g., "Puzzle 1" or a category name)
|
|||
|
|
- The empty grid with numbered cells and black squares
|
|||
|
|
- Across clues listed below or beside the grid
|
|||
|
|
- Down clues listed below or beside the grid
|
|||
|
|
|
|||
|
|
2. **Solution page:**
|
|||
|
|
- The filled grid with all letters visible
|
|||
|
|
- Labeled "Solution to Puzzle N"
|
|||
|
|
|
|||
|
|
**Page layout considerations:**
|
|||
|
|
- Grid cells should be large enough to write in (~0.3–0.4 inches per cell)
|
|||
|
|
- Clue text should be readable (10–12pt font)
|
|||
|
|
- Clean, professional look (black borders, light grid lines)
|
|||
|
|
|
|||
|
|
### 4. KDP Formatter (`kdp_format.py`)
|
|||
|
|
|
|||
|
|
Handles KDP-specific requirements:
|
|||
|
|
|
|||
|
|
- **Trim sizes:** Support common KDP sizes (8.5"x11", 6"x9", 8"x10")
|
|||
|
|
- **Margins:** KDP requires minimum margins that vary by page count (typically 0.5" outside, 0.75"+ gutter/inside)
|
|||
|
|
- **Page count:** Must be between 24–828 pages
|
|||
|
|
- **Bleed:** Not needed for puzzle books (no content to edge)
|
|||
|
|
- **Front matter:** Generate a title page, copyright page, and optional instructions page
|
|||
|
|
- **Page numbering:** Footer with page numbers
|
|||
|
|
- **Table of contents:** Optional, mapping puzzle numbers to pages
|
|||
|
|
|
|||
|
|
### 5. CLI Interface (`main.py`)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
python main.py --input words.csv \
|
|||
|
|
--output crossword_book.pdf \
|
|||
|
|
--puzzles 50 \
|
|||
|
|
--grid-size 15 \
|
|||
|
|
--trim-size 8.5x11 \
|
|||
|
|
--title "The Ultimate Crossword Book" \
|
|||
|
|
--author "Your Name"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Arguments:**
|
|||
|
|
| Flag | Description | Default |
|
|||
|
|
|------|-------------|---------|
|
|||
|
|
| `--input` | Path to CSV file | required |
|
|||
|
|
| `--output` | Output PDF path | `output.pdf` |
|
|||
|
|
| `--puzzles` | Number of puzzles to generate | `20` |
|
|||
|
|
| `--grid-size` | Grid dimensions (e.g., 15 for 15x15) | `13` |
|
|||
|
|
| `--trim-size` | KDP page size | `8.5x11` |
|
|||
|
|
| `--title` | Book title for the cover page | `"Crossword Puzzles"` |
|
|||
|
|
| `--author` | Author name | `""` |
|
|||
|
|
| `--words-per-puzzle` | Target words per puzzle | `20` |
|
|||
|
|
| `--solutions-at-end` | Group all solutions at the back | `false` |
|
|||
|
|
| `--seed` | Random seed for reproducibility | none |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Project Structure
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
crossword-kdp-generator/
|
|||
|
|
├── main.py # CLI entry point
|
|||
|
|
├── csv_reader.py # CSV parsing and validation
|
|||
|
|
├── generator.py # Crossword grid generation algorithm
|
|||
|
|
├── renderer.py # PDF rendering (grids, clues, layout)
|
|||
|
|
├── kdp_format.py # KDP trim sizes, margins, front matter
|
|||
|
|
├── models.py # Data classes (Crossword, PlacedWord, Cell, etc.)
|
|||
|
|
├── requirements.txt # Dependencies
|
|||
|
|
├── sample_words.csv # Example input file
|
|||
|
|
└── README.md # Usage instructions
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Dependencies
|
|||
|
|
|
|||
|
|
| Package | Purpose |
|
|||
|
|
|---------|---------|
|
|||
|
|
| `reportlab` | PDF generation |
|
|||
|
|
| `click` | CLI argument parsing |
|
|||
|
|
|
|||
|
|
No heavy dependencies needed — the crossword algorithm is pure Python, and ReportLab handles all the PDF work.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Generation Strategy for a Full Book
|
|||
|
|
|
|||
|
|
A single CSV might have hundreds of words, but each puzzle only uses ~15–25 of them. The tool will:
|
|||
|
|
|
|||
|
|
1. Shuffle the word list (using the optional seed for reproducibility)
|
|||
|
|
2. Pull a batch of N words for each puzzle
|
|||
|
|
3. Attempt to generate a crossword grid from that batch
|
|||
|
|
4. If the grid doesn't meet a minimum quality threshold (too few intersections, too sparse), retry with a different subset
|
|||
|
|
5. Repeat until the requested number of puzzles is generated
|
|||
|
|
6. Assemble all puzzles into the final PDF with front matter and optional solutions section at the back
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Edge Cases to Handle
|
|||
|
|
|
|||
|
|
- **Too few words in CSV** — error out with a clear message
|
|||
|
|
- **Words that can't interlock** — skip them, log a warning
|
|||
|
|
- **Grid overflow** — if words don't fit the requested grid size, increase grid or reduce words per puzzle
|
|||
|
|
- **Duplicate words across puzzles** — allow (different puzzles can reuse words) but avoid within a single puzzle
|
|||
|
|
- **Non-ASCII characters** — strip accents or reject, since crosswords are typically A-Z only
|
|||
|
|
- **Very long words** — warn if a word exceeds grid size
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Stretch Goals (Post-MVP)
|
|||
|
|
|
|||
|
|
- **Cover page PDF** — generate a separate KDP-compliant cover
|
|||
|
|
- **Multiple difficulty levels** in one book (easy section, medium, hard)
|
|||
|
|
- **Themed sections** based on CSV category column
|
|||
|
|
- **Word search mode** — reuse the same CSV to generate word search puzzles too
|
|||
|
|
- **Web UI** — a simple local web interface instead of CLI
|
|||
|
|
- **ISBN barcode** placement on back cover
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Implementation Order
|
|||
|
|
|
|||
|
|
1. ~~`models.py` — define data structures~~ ✅
|
|||
|
|
2. ~~`csv_reader.py` — parse and validate input~~ ✅
|
|||
|
|
3. ~~`generator.py` — crossword algorithm (the hard part)~~ ✅
|
|||
|
|
4. ~~`renderer.py` — PDF grid and clue rendering~~ ✅
|
|||
|
|
5. ~~`kdp_format.py` — page layout and front matter~~ ✅
|
|||
|
|
6. ~~`main.py` — wire it all together with CLI~~ ✅
|
|||
|
|
7. ~~Test with sample data and iterate on grid quality~~ ✅
|