xwords
is a fast library that fills crossword puzzles.
This repo also contains a lightweight CLI for invoking the library.
This is foremost a hobbyist project for me to learn a bit about profiling and optimizing rust. I am more than happy to accept contributions or to consider feature requests, but please be aware that the future of this project is somewhat uncertain.
This command fills a grid that is stored in a local file using a wordlist.
xwords
USAGE:
xwords [FLAGS] [OPTIONS] --input <FILE>
FLAGS:
-h, --help Prints help information
-l, --log Prints intermediate progress information to stderr. Default is false.
-p, --profile Profile the program. Default is false.
-r, --random Randomize word fill. Default is false.
-V, --version Prints version information
OPTIONS:
-a, --author <AUTHOR> Author name across output. Defaults to `xwords-rs`.
-c, --copyright <COPYRIGHT> Copyright text for across output. Defaults to `<YEAR> Public domain.`
-f, --format <FORMAT> Output format. Can be `grid` for simple grid or `across` for Across Puzzle V2 text.
Default is `grid`.
-i, --input <FILE> Input crossword file location.
-m, --max-time <SECONDS> Maximum number of seconds to process. Default is 120s (2 minutes).
-t, --title <TITLE> Puzzle title for across output. Defaults to title case file name.
-w, --words <WORDS_FILE_NAME> File name from /words without extension to use for filling. Default is `en`.
Example:
$ xwords --input grids/20201005_empty.txt
CFS.ANGELI.ORDU
AIA.DEEPAS.SEIN
SCLAVONIAN.MFAS
IKANTLETGO.ALLE
OLDY..ROE.ANOSE
.YOOHOOMRSBLOOM
...FUGUE.IRIDAL
FAA.LIS.ECO.SPY
IMPROV.ACOOK...
BILLIEJEANKING.
SEUSS.IAD..CEIL
..STTIC..ALKALI
CITI.CACOMISTLE
CORN.OMOLON.LIS
CLEE.NATURA.YST
This command runs in about 2 seconds on my machine.
Example with random word fill:
$ xwords --input grids/waffle.txt --random
FLUKY
T.W.U
ETAAC
N.I.C
SITKA
To output an Across Puzzle V2 text file, use the --format
flag:
$ xwords --input grids/waffle.txt --format across --title "Waffle" --author "Adi" --copyright "2025 Adi"
<ACROSS PUZZLE V2>
<TITLE>
Waffle
<AUTHOR>
Adi
<COPYRIGHT>
2025 Adi
<SIZE>
5x5
<GRID>
SHIAS
L.S.K
IFIDO
P.T.R
TOSET
<ACROSS>
SHIAS
IFIDO
TOSET
<DOWN>
SLIPT
ISITS
SKORT
The and are the word placeholders for clues.
To use other word lists, you can specify the file name from the /words
directory without the extension:
$ xwords --input grids/waffle.txt --random --words ro_dex_000
DINȚA
E.O.L
PĂTAT
U.A.U
SĂTUL
Included word lists are:
en
- English words (default) - contains a rather large set of common words, some are not that common.ro_dex_NNN
- Romanian words - contains a Romanian words from scrabble dictionary. NNN is the usage frequency, e.g.ro_dex_080
is the most common 80% of words. The frequency is not very accurate, but it gives a good idea of the most common words. Included frequencies are:080
,070
,060
,050
,000
. The last is for all words in scrabble dictionary.
use xwords::{crossword::Crossword, fill_crossword_with_default_wordlist};
fn main() -> Result<(), String> {
let empty_crossword = Crossword::new(String::from(
"
XXXX.XXXX.XXXXX
XXXX.XXXX.XXXXX
XXXXXXXXX.XXXXX
XXX.XXX.XXX.XXX
..XXXX.XXXXXXXX
XXXXXX.XXXXX...
XXXXX.XXXX.XXXX
XXX.XXXXXXX.XXX
XXXX.XXXX.XXXXX
...XXXXX.XXXXXX
XXXXXXXX.XXXX..
XXX.XXX.XXX.XXX
XXXXX.XXXXXXXXX
XXXXX.XXXX.XXXX
XXXXX.XXXX.XXXX
",
))?;
let filled_crossword = fill_crossword_with_default_wordlist(&empty_crossword, false)?;
println!("{}", filled_crossword);
Ok(())
}
/*
ZETA.TWIT.VOWEL
ETAT.IANA.EVOKE
RINTINTIN.REVIE
OCT.TIE.TUI.ENR
..ATHA.TASTINGS
TOLEAN.ILIES...
ISIAC.TEAN.STEM
ZAT.ACHATES.HRA
AYES.SETE.TYEES
...TUTSI.URALIC
VENERATE.SEWA..
ORA.TRO.UES.TOA
WISHI.NETASSETS
ETHIC.EVIL.USTO
RUEDA.SWAL.OTSU
*/
On my machine, the above snippet runs in about 3 seconds.
Behind the scenes, this snippet loads an indexed wordlist, and iteratively fills the input with valid words.