All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- OOP and ABC all the way down
- Most key parts of this package have been extracted out into separate units allowing for more extensibility.
- All games now derive from a base
Game
object. EachGame
can have custom aGenerator
for generating the puzzle, aFormatter
for displaying/outputting the puzzle, and custom wordValidator
s (e.g. no palindromes, no punctuation, no single letter words, no subwords) for validating puzzle words.
- Validators: Previously, all word validation was done during the
WordSearch
object initialization (and also after making any changes to the puzzle words). Now, the default validation (no single letter words, no palindromes, no words that fit inside of other words or encase other words) has been abstracted away. Each validator is now based on aValidator()
abstract base class, allowing users to create their own or disable the defaults. This thought has come up before but because of issue #45 I decided to tackle. Normally in a standard word search puzzle you don't want single-letter words, calindromes, or words that are part of other words, as each of these situations could potentially lead to multiple solutions for the same puzzle.validators
argument added toWordSearch
object--no-validators
added to cli arguments to disable default validators- Tests updated and added for new functionality
require_all_words
has been added toGame.init()
. When set toTrue
aMissingWordError
will be raised if all provided "hidden" words can't be placed successfully. This does not take into account "secret" words. Also added to CLI as-rall, --require-all-words
.lowercase
argument added toshow
andsave
methods which outputs all puzzle letters in lowercase (as opposed to the UPPERCASE default). Added-lc, --lowercase
flag to CLI as well. Issue #58- all words get assigned a random color on initialization (for solution)
- custom iPython profile
- to use first make a custom profile
ipython --profile word-search-generator
- then copy the include config file to your new profile
cp ipython_config.py ~/.ipython/profile_word-search-generator
- finally load iPython with the custom profile
ipython --profile word-search-generator
- to use first make a custom profile
- added pretty printed traceback via Rich
- custom alphabet can now be specified for generators (used for puzzle filler characters)
- added
-hk
,--hide-key
to cli andWordSearch.show()
, andWordSearch.save()
methods, allowing user to hide the answer key during output- only applies to cli output and saved PDF files
- the answer key will always be output on the solution page of a pdf
- Bug creating false negatives in
WordSearchGenerator.no_duped_words()
method that is used when placing new words and filler characters - Empty puzzle shown with the
show
method was called on a puzzle that has not been generated yet, or a puzzle with no placed/valid words.
- Minimum Python version updated to 3.10
- Updating all typing to use the new format (instead of importing from typing)
- Tox config
max_fit_tries
raised to 1000 to help more words fitting within smaller puzzlesget_random_words()
now accepts amax_length
argument, helpful when working with puzzles of a smaller size- Dependencies updated and tested on latest release
- Updated word search puzzle export to work with fpdf2 v2.7.5 changes
- None now allowed as
Game
orGenerator
validator. - PDF output now highlights puzzle words in color "bubbles" (Issue #54)
- Updated PDF layout and formatting to better work with the new solution highlighting
- "secret" words are now highlighted and included in the word list on the solution page
- add Rich to dependencies for cli highlighting
- CLI output now defaults to a 'pretty' version using rich (can be disabled with the
--no-pretty
flag)- solution flag now highlights puzzle words using same coloring as PDF output
- answer key text reversed to obfuscate (like PDF output) when not using '-c' flag
hide_fillers
argument added to the baseWordSearch.show()
method.
reset_size()
function no longer needed as it is included inside of_generate()
now- removed
__version__
from init. Version info is loaded from pyproject.toml usingimportlib.metadata.version
- Updated Pillow to v11.0.0 since it was breaking during install on Python 3.13
- Requirements
save()
method now also supports thesolution
argument for "CSV" and "JSON" formats. This will remove all filler characters from the saved output, leaving only characters from the placed puzzle words. Closes issues #41 and #44.- Testing for solution highlighting in PDF output
cropped_size
property which gives the size (in characters) of a cropped/masked puzzle as a (width, height) tuple
- Updated testing to use fixtures
- Standardized testing variable names
- Solution highlighting on exported PDFs
Word.offset_coordinates()
- Secret words correctly obey their directional constraints (by duck57). Fixes issue #42.
show()
method now accepts thehide_fillers
boolean argumentTrue
will hide all filler characters showing only the placed words (negatessolution=True
)
- New pre-built mask shapes: Club, Fish, Flower, and Spade
- Testing for built-in masks shapes based on known output
- 'tools/build_masks_output_dict.py' tool for generating known built-in shapes output dict for us in testing
- During mask generation, each class and subclass now refers to their own
build_mask()
method instead of the base class. - Incorrect horizontal center calculation for
Spade
andClub
masks on even sized masks.
- The
.random_words()
method default to NOT resetting the puzzle size. - Radius calculation for the
RegularPolygon
mask. - If the
random_words()
method is called on an emptyWordSearch()
object, an appropriate puzzle size is calculated. - Cleaned up variable naming a bit to make things clear
- cli.py
BUILTIN_SHAPES
->BUILTIN_MASK_SHAPES_OBJECTS
- shapes.py
MASK_SHAPES
->BUILTIN_MASK_SHAPES
- cli.py
- Cleaned up calculation of built-in shape objects
- Cleaned up imports for 'test/init.py'
- README and wiki mention puzzle masking.
make_header()
function no longer needed as header is created with f-strings now
- Moved build system from setup.cfg to pyproject.toml
- Added ruff to the dev tool set (plus tox, pre-commit)
- Fixed all ruff suggestions
- Moved flake8 settings to .flake8 file since pyproject.toml isn't supported
- Changed 'mode' on CSV and JSON open context managers to 'x' and let that handle any exceptions
export.validate_path()
PuzzleNotGenerated
is raised if a mask is applied to a puzzle that had yet to be generated, either because it doesn't yet have words or doesn't yet have a size.PuzzleSizeError
is now raised if the current puzzle size is smaller than the shortest words in the wordlist.- Additional tests to keep coverage at 100%.
WordSearch.random_words()
now accepts an "action" argument. This determines whether the random words are added ("ADD") to the current wordlist or replace ("REPLACE") the current wordlist. Defaults to "REPLACE".no_duped_words()
refactored to speed up puzzle generation especially on large puzzles and puzzles with large wordlists.fill_words()
refactored to stop adding words or secret words if placed words >config.max_puzzle_words
.try_to_fit_word()
refactored to no longer use deepcopy. While trying a new word, all changes are made to the actual puzzle, changes are also tracked inprevious_chars
. If a word ends up not fitting, the function backtracks and reset the puzzle to theprevious_chars
.calc_puzzle_size()
now maxes out atconfig.max_puzzle_words
no matter the caculation result.- Generation tests updated to work with above changes.
no_matching_neighbors()
capture_all_paths_from_position()
- Puzzle Masking
- Puzzle output (show(), save()) crops output to the puzzle mask so no dead space
- Key is offset by cropped bounding box
- CLI implementation
- -m, --mask Mask the puzzle to a particular shape (choices: circle, diamond, triangle, heart, hexagon, octagon, pentagon, star).
- -im, --image-mask Mask the puzzle shape to a provided image (accepts: BMP, JPEG, PNG).
- Puzzle output (show(), save()) crops output to the puzzle mask so no dead space
- Project documentation
random_words()
method to add random words to a puzzle.- Word class now tracks all coordinates of word (using this in place of keeping a separate 'solution' puzzle)
- Testing to make sure key only shows placed words in response to bug fixed in v2.0.1
- CSV export no longer includes the solution
- Moved all types from 'types.py' file to appropriate object files to help with type checking
- no_duped_words() function was edited to no longer accept a WordSearch object to make testing easier
.show()
,.save()
, and.json
now crop the puzzle to the masked active area (bounding box)utils.get_random_words()
now returns a list instead of a string
- All puzzle output (stdout, csv, pdf) all indicate that '' before a word in the key mean that word is secret (not listed in the word list) (e.g. 'Puzzle Key (= Secret Words): ...').
- Only shows if secret words are actually present.
- Answer Key and .placed_... properties were showing all words and not only words placed in the puzzle.
- JSON method returns correct word list.
- Test for csv output with solution
- Secret Words (contributed by Chris J.M. duck57)
- The WordSearch object can now accept a list of secret bonus words
- are included in the puzzle but not listed in the word list
- are included in the answer key and tagged with an
*
like '*word'. - A puzzle can consist of only secret words
- Implemented in the CLI as -x, --secret-words
- Introduced the Direction class
- Allows for specifying either a preset numeric level or accepted cardinal direction for puzzle words.
- Implemented in the CLI as -d, --difficulty
- Testing for all new secret word features and accompanying functionality
- Updated README with new features
- The WordSearch object can now accept a list of secret bonus words
- New
-rx, --random-secret-words
CLI argument
- Most all supplemental function now work the base WordSearch object, reducing the of arguments needing to be passed around, and also reducing memory usage
- Puzzle config settings
- min_puzzle_size == 5
- max_puzzle_size == 50
- max_puzzle_words == 100
- PDF generator updated to work with larger puzzles and word lists
- Removed the tty input from the cli as it's confusing for most users.
- CLI flag -l, --level was combined with -d, --difficulty (for backward compatibility)
- Lots of types refactoring by contributed by duck57
- clean up main call to cli
- Word class
WordSearch.words
property is now a set ofWord
objects- Allows unused words to be kept around in case they fit in re-generated version o the puzzle.
- Allows easier tracking of all words properties
- Allows easier access to words of different types
WordSearch.words
== all available puzzle wordsWordSearch.placed_words
== all words placed in the puzzle (no matter of word type)WordSearch.hidden_words
== all available "regular" wordsWordSearch.placed_hidden_words
== all "regular" words placed in the puzzleWordSearch.secret_words
== all available "secret" wordsWordSearch.placed_secret_words
== all "secret" words placed in the puzzle
- CLI with empty stdin causes error
- Corrected key coordinates to 1-based.
- You can now export the puzzle solution along with the puzzle using the
save()
method. Just specifysolution=True
after the path.- If you are exporting a PDF then a second page will be added with the puzzle words highlighted in red (just like the
show(solution=True)
method) - If you are exporting to CSV the puzzle solution will be included at the bottom of the csv below the answer key.
- If you are exporting a PDF then a second page will be added with the puzzle words highlighted in red (just like the
save()
method now accepts thesolution
argument.- -c, --cheat cli option now works with the -o, --output option to include the puzzle solution within the output file (csv or pdf)
- To clean things up a bit,
export_pdf_file()
andexport_csv_file()
now accept a WordSearch object as the main argument.
- -c, --cheat options for cli to show puzzle solution
- --version flag added to cli
- eq magic method for checking if puzzles are the same (mostly for testing repr)
- Export csv no uses the builtin csv module
- Removed colorama, now using ascii escape codes for solution highlighting
- Updated requirements.txt and setup.cfg
- Updated repr to include
level
andsize
- Rewrote the word fitting function to use a @retry wrapper
- Hat tip to Bob Belderbos, Twitter @bbelderbos (https://twitter.com/bbelderbos/status/1532347393009668098?s=20&t=1IOt6a8RGEohzkNKpTBrxg)
- Fixed max_puzzle_size spec in README. Was incorrectly listed as 30 when it is actually 25. Hat tip to dt215git for the heads-up
- New generator code to make sure no duplicated exists in the puzzle
- Generator checks during word insertion and random character fill-in stages
- JSON output property for @robguttman solver https://github.com/robguttman/word-search-solver
- Testing for new generator and export functions
- Any input words that are palindromes are removed
- add_words() method now checks old and new words together to make sure no single word is a subset of another word
- Removed extra newline from output.
WordSearch.show()
method- Allows you to show the puzzle (just like print(WordSearch)=> str).
- Enables a better "solution" view using colorama "dim" with
WordSearch.show(solution=True)
.
- Required install of colorama.
- Cleaned up some functions to work better with the new
.show()
method.
WordSearch.show_solution()
method was replaced by theWordSearch.show(solution=True)
.
- New/cleaned-up testing.
- More type hints.
- Tests were cleaned up and changed a bunch to pass on windows.
- Updated minimum version and testing version to Python 3.10.
- WordSearch.save() method now determines CSV or PDF export type from file name. Default to PDF is no extension if provided.
- Cleaned up variable names to match across entire project.
- Cleaned up type hints.
- Remove custom FilePath type and replace with
Union[str, Path]
.
- Format argument on WordSearch.save() method. The file type is now determined by the provided save path. If no extension is provided it defaults to pdf.
- Makes the -e, --export cli option no longer needed.
- app_name to init.py
- Lots of new tests to get closer to 100% coverage
- conftest.py for pytest
- Added temp_dir fixture for testing
- setup and configure tox for testing on github
- Added typing to correct exit status on cli
- main.py now passes app_name to cli so help title display correctly
- Changed str to print entire puzzle along with answer key
- Updated README.md with all additions, changes, fixes, and removals
- Reworked
cleanup_input()
function
- Puzzle solution wasn't showing correctly
- Removed
show()
function, now just use print(object) - Removed -k --key and -t --tabs from cli options
- Updated typing to simpler format
- Cleaned up all imports using isort
- Added black and isort settings to pyproject.toml
- Added optional [tests] and [dev] requirements to setup.cfg
pip install word-search-generator[dev]
- Added isort to run file linting
- Added typing to run file
- You can now generate puzzle from random dictionary words. No need to even supply your own words.
# just provide the -r or --random flag and the number of words you want
$ words-search -r 10
- Add error check for cli when no words are provided
- Updated PDF layout and font sizes
- Fully typed with mypy (for real this time)
- Corrected License type
- Updated docstrings
- Fully typed
- Changed all functions that took
row
andcol
to now take a tuple of (row, col) asposition
- moved cli to cli module and out of main.py
- switched from setup.py to setup.cfg
- Changed all vars
dir
to eitherdirection
ord
- Changed puzzle size calculation and min and max value errors
- Changed pdf font size calculation to min and max boards
- Updated cli to reflect changes
- Updated PDF export
- Now uses config variables for base font sizes
- Grid size, grid margin, and font sizes all based on puzzle size and puzzle words
- Implemented testing of all options to make sure all PDFs are 1-page (see also
tools/check_pdf_export_sizes.py
) - Installed PyPDF2 to check page count on test exports
- Swapped out some comprehensions to limit line length
- Added some cli testing
- Updated cli -p, --path flag to more standard -o, --output flag
- Updated cli to accept stdin of words instead of requiring -w or --words flag
# instead of using -w just list word as arguments
$ words-search some sample words to include
# all other flags work the same
$ words-search some sample words to include -l 3 -o pdf
# you can also pipe directly to word-search
$ cat some-sample-words.txt | word-search
# pipe a bunch of files for export
$ for f in tests/word*.txt; do word-search "$(cat $f)" -e pdf -o $f.pdf; done
- Added testing
.save()
now defaults to current directory if not specified- Changed puzzle font multiplier calculation
- Changed
.key
to 0-based index. For display.show()
and.save()
still output key in 1-based index.
- Refactored functions
- Updated README with real life example
# ---- from this ----
# if export flag make sure output path was specified
if args.export and args.path is None:
# parser.error("-e, --export requires -p, --path.")
# ---- to this ----
# if export flag and no path flag use current directory
if args.export and args.path is None:
args.path = pathlib.Path.cwd()
- First official release!