Crosswords
Structures
Enigmistics.CrosswordWord — TypeCrosswordWordStructure for a word placed in the crossword.
Fields
word::String: the actual wordrow::Int: starting rowcol::Int: starting columndirection::Symbol: either:verticalor:horizontal
Enigmistics.CrosswordBlackCell — TypeCrosswordBlackCellStructure for a black cell placed in the crossword.
Fields
count::Float64: number of words that share that black cell (orInf64if it was manually placed)manual::Bool: if the cell was manually set by user or automatically derived based on surronding words
Enigmistics.CrosswordPuzzle — TypeCrosswordPuzzleStructure for a crossword puzzle.
Fields
grid::Matrix{Char}: the actual crossword gridwords::Vector{CrosswordWord}: vector containing all the wordsblack_cells::Dict{Tuple{Int,Int}, CrosswordBlackCell}: dictionary storing the black cells information
Here are some useful constructors:
Enigmistics.CrosswordPuzzle — MethodCrosswordPuzzle(rows::Int, cols::Int)Construct a crossword with an empty grid of the given dimensions.
Examples
julia> cw = CrosswordPuzzle(4,5)
1 2 3 4 5
┌───────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ │
2 │ ⋅ ⋅ ⋅ ⋅ ⋅ │
3 │ ⋅ ⋅ ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ⋅ ⋅ ⋅ │
└───────────────┘Enigmistics.CrosswordPuzzle — MethodCrosswordPuzzle(rows::Int, cols::Int, words::Vector{CrosswordWord})Construct a crossword with the given dimensions and words. Return an error if words intersections are not compatible.
Examples
julia> words = [CrosswordWord("CAT",2,2,:horizontal), CrosswordWord("BAT",1,3,:vertical),
CrosswordWord("SIR",4,4,:horizontal)];
julia> CrosswordPuzzle(5,6,words)
1 2 3 4 5 6
┌──────────────────┐
1 │ ⋅ ⋅ B ⋅ ⋅ ⋅ │
2 │ ■ C A T ■ ⋅ │
3 │ ⋅ ⋅ T ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ■ S I R │
5 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
└──────────────────┘
julia> words = [CrosswordWord("CAT",2,2,:horizontal), CrosswordWord("BAT",1,3,:vertical),
CrosswordWord("SIR",4,4,:horizontal), CrosswordWord("DOG",1,2,:vertical)];
julia> CrosswordPuzzle(5,6,words)
┌ Warning: Cannot place word 'DOG' at (1, 2) vertically due to conflict at cell (2, 2); found when checking the inner cells.
┌ Error: Words intersections are not compatible.Enigmistics.CrosswordPuzzle — MethodCrosswordPuzzle(words::Vector{CrosswordWord})Construct a crossword with the given words (dimensions are inferred from words positions). Return an error if words intersections are not compatible.
Examples
julia> words = [CrosswordWord("CAT",2,2,:horizontal), CrosswordWord("BAT",1,3,:vertical),
CrosswordWord("SIR",4,4,:horizontal)];
julia> CrosswordPuzzle(words)
1 2 3 4 5 6
┌──────────────────┐
1 │ ⋅ ⋅ B ⋅ ⋅ ⋅ │
2 │ ■ C A T ■ ⋅ │
3 │ ⋅ ⋅ T ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ■ S I R │
└──────────────────┘
julia> words = [CrosswordWord("CAT",2,2,:horizontal), CrosswordWord("BAT",1,3,:vertical),
CrosswordWord("SIR",4,4,:horizontal), CrosswordWord("DOG",1,2,:vertical)];
julia> CrosswordPuzzle(5,6,words)
┌ Warning: Cannot place word 'DOG' at (1, 2) vertically due to conflict at cell (2, 2); found when checking the inner cells.
┌ Error: Words intersections are not compatible.Interface
Enigmistics.show_crossword — Functionshow_crossword(cw::CrosswordPuzzle; words_details=true, black_cells_details=true)Print the crossword grid, possibly along with the words details and black cells details.
Examples
julia> cw = example_crossword(type="full") # normal output
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ O ■ A │
3 │ T ■ S O U R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ W I N D O W │
└──────────────────┘
julia> show_crossword(cw) # extended details output
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ O ■ A │
3 │ T ■ S O U R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ W I N D O W │
└──────────────────┘
Horizontal:
- 'GOLDEN' at (1, 1)
- 'AN' at (2, 1)
- 'SOUR' at (3, 3)
- 'EVER' at (4, 1)
- 'IE' at (5, 2)
- 'WINDOW' at (6, 1)
Vertical:
- 'GATE' at (1, 1)
- 'ON' at (1, 2)
- 'VII' at (4, 2)
- 'SEEN' at (3, 3)
- 'DOOR' at (1, 4)
- 'NARROW' at (1, 6)
Black cells:
- at (5, 5) was manually placed
- at (3, 2) was automatically derived (delimiting 3 words)
- at (4, 5) was automatically derived (delimiting 1 words)
- at (2, 5) was manually placed
- at (5, 1) was automatically derived (delimiting 2 words)
- at (2, 3) was automatically derived (delimiting 2 words)
- at (5, 4) was automatically derived (delimiting 2 words)Enigmistics.example_crossword — Functionexample_crossword(; type="full")Return an example crossword, useful e.g during testing. Available values for now are
- "full": a complete crossword with all words
- "partial": an incomplete crossword with some words missing
Examples
julia> cw = example_crossword(type="full")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ O ■ A │
3 │ T ■ S O U R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ W I N D O W │
└──────────────────┘
julia> example_crossword(type="partial")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘Enigmistics.enlarge! — Functionenlarge!(cw::CrosswordPuzzle, how::Symbol, times=1)Enlarge the crossword grid in the direction given by how (accepted values are :N, :S, :E, :O) by appropriately inserting times empty rows/columns.
Examples
julia> cw = example_crossword()
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ O ■ A │
3 │ T ■ S O U R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ W I N D O W │
└──────────────────┘
julia> enlarge!(cw, :O); cw
1 2 3 4 5 6 7
┌─────────────────────┐
1 │ ■ G O L D E N │
2 │ ■ A N ■ O ■ A │
3 │ ⋅ T ■ S O U R │
4 │ ■ E V E R ■ R │
5 │ ⋅ ■ I E ■ ■ O │
6 │ ■ W I N D O W │
└─────────────────────┘
julia> enlarge!(cw, :N, 2); cw
1 2 3 4 5 6 7
┌─────────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
2 │ ⋅ ■ ■ ⋅ ■ ⋅ ■ │
3 │ ■ G O L D E N │
4 │ ■ A N ■ O ■ A │
5 │ ⋅ T ■ S O U R │
6 │ ■ E V E R ■ R │
7 │ ⋅ ■ I E ■ ■ O │
8 │ ■ W I N D O W │
└─────────────────────┘Enigmistics.shrink! — Functionshrink!(cw::CrosswordPuzzle)Reduce the crossword size to its minimal representation by removing useless rows/columns.
Examples
julia> cw
1 2 3 4 5 6 7 8 9
┌───────────────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
2 │ ⋅ ■ ■ ⋅ ■ ⋅ ■ ⋅ ⋅ │
3 │ ■ G O L D E N ■ ⋅ │
4 │ ■ A N ■ O ■ A ⋅ ⋅ │
5 │ ⋅ T ■ S O U R ■ ⋅ │
6 │ ■ E V E R ■ R ⋅ ⋅ │
7 │ ⋅ ■ I E ■ ■ O ⋅ ⋅ │
8 │ ■ W I N D O W ■ ⋅ │
9 │ ⋅ ⋅ ■ ■ ⋅ ⋅ ■ ⋅ ⋅ │
└───────────────────────────┘
julia> shrink!(cw); cw
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ O ■ A │
3 │ T ■ S O U R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ W I N D O W │
└──────────────────┘Enigmistics.can_place_word — Functioncan_place_word(cw::CrosswordPuzzle, word::String, row, col, direction::Symbol)
can_place_word(cw::CrosswordPuzzle, cwword::CrosswordWord)Check if a word can be placed in the crossword puzzle cw at the given position and direction.
Return true if the word can be placed, false otherwise.
Examples
julia> cw = example_crossword(type="partial")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘
julia> can_place_word(cw, "PEARS", 3, 3, :h)
┌ Warning: Word 'PEARS' does not fit in the grid horizontally at (3, 3).
false
julia> can_place_word(cw, "BEAN", 3, 3, :h)
┌ Warning: Cannot place word 'BEAN' at (3, 3) horizontally due to conflict at cell (3, 6); found when checking the inner cells.
false
julia> can_place_word(cw, "TEA", 3, 3, :h)
┌ Warning: Cannot place word 'TEA' at (3, 3) horizontally due to conflict at cell (3, 6); found when checking the border cells.
false
julia> can_place_word(cw, "DEAR", 1, 4, :v)
trueEnigmistics.place_word! — Functionplace_word!(cw::CrosswordPuzzle, word::String, row, col, direction::Symbol)
place_word!(cw::CrosswordPuzzle, cword::CrosswordWord)Place a word in the crossword puzzle cw at the given position and direction, also checking if it can actually be placed.
Return true if the word was successfully placed, false otherwise.
Examples
julia> cw = example_crossword(type="partial")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘
julia> place_word!(cw, "cat", 6, 1, :h)
┌ Warning: Cannot place word 'CAT' at (6, 1) horizontally due to conflict at cell (6, 2); found when checking the inner cells.
false
julia> place_word!(cw, "pillow", 6, 1, :h)
true
julia> cw
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ P I L L O W │
└──────────────────┘Enigmistics.remove_word! — Functionremove_word!(cw::CrosswordPuzzle, word::String)
remove_word!(cw::CrosswordPuzzle, cword::CrosswordWord)
remove_word!(cw::CrosswordPuzzle, words::Vector{String})
remove_word!(cw::CrosswordPuzzle, words::Vector{CrosswordWord})Remove a word (or multiple words) from the crossword puzzle cw.
Return true if the word was found and removed, false otherwise.
Examples
julia> cw = example_crossword(type="partial")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘
julia> remove_word!(cw, "cat")
┌ Warning: Word 'CAT' not found in the crossword. No changes on the original grid.
false
julia> remove_word!(cw, "narrow")
true
julia> cw
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ ⋅ │
3 │ T ■ ⋅ ⋅ ⋅ ⋅ │
4 │ E V E R ■ ⋅ │
5 │ ■ I E ■ ■ ⋅ │
6 │ ⋅ I ⋅ ⋅ ⋅ ⋅ │
└──────────────────┘Enigmistics.place_black_cell! — Functionplace_black_cell!(cw::CrosswordPuzzle, row::Int, col::Int)Place a black cell in the crossword puzzle cw at the given position.
Return true if the black cell was successfully placed, false otherwise.
Examples
julia> cw = example_crossword(type="partial")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘
julia> place_black_cell!(cw, 6, 2)
┌ Warning: Cannot place black cell at position (6, 2) since cell is not empty. No changes on the original grid.
false
julia> place_black_cell!(cw, 6, 4)
true
julia> cw
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ■ ⋅ W │
└──────────────────┘Enigmistics.remove_black_cell! — Functionremove_black_cell!(cw::CrosswordPuzzle, row::Int, col::Int)Remove a black cell from the crossword puzzle cw at the given position.
Return true if the black cell was successfully removed, false otherwise.
Examples
julia> cw = example_crossword(type="partial"); show_crossword(cw, words_details=false)
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘
Black cells:
- at (5, 5) was manually placed
- at (4, 5) was automatically derived (delimiting 1 words)
- at (3, 2) was automatically derived (delimiting 2 words)
- at (2, 5) was manually placed
- at (5, 1) was automatically derived (delimiting 2 words)
- at (2, 3) was automatically derived (delimiting 1 words)
- at (5, 4) was automatically derived (delimiting 1 words)
julia> remove_black_cell!(cw, 3, 2)
┌ Warning: Cannot remove automatically placed black cell at position (3, 2) since it's needed as a word delimiter. No changes on the original grid.
false
julia> remove_black_cell!(cw, 5, 5)
true
julia> cw
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ⋅ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘Enigmistics.clear! — Functionclear!(cw::CrosswordPuzzle; deep=true)Clear all words, possibly preserving manually-placed black cells (with deep=false), from the crossword puzzle cw.
Examples
julia> cw = example_crossword(); show_crossword(cw, words_details=false)
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ O ■ A │
3 │ T ■ S O U R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ W I N D O W │
└──────────────────┘
Black cells:
- at (5, 5) was manually placed
- at (3, 2) was automatically derived (delimiting 3 words)
- at (4, 5) was automatically derived (delimiting 1 words)
- at (2, 5) was manually placed
- at (5, 1) was automatically derived (delimiting 2 words)
- at (2, 3) was automatically derived (delimiting 2 words)
- at (5, 4) was automatically derived (delimiting 2 words)
julia> clear!(cw, deep=false)
1 2 3 4 5 6
┌──────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
2 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
3 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
5 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
6 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
└──────────────────┘
julia> clear!(cw)
1 2 3 4 5 6
┌──────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
2 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
3 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
5 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
6 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
└──────────────────┘Patterns
Enigmistics.random_pattern — Functionrandom_pattern(nrows, ncols;
max_density=0.18, symmetry=true, double_symmetry=true,
seed=rand(Int))Generate a crossword puzzle with black cells placed according to a random pattern which can be totally random, symmetric, or doubly symmetric (i.e. specular).
Arguments
nrows::Int,ncols::Int: grid dimensionsmax_density::Float=0.18: maximum density of black cellssymmetry::Bool=true: whether to enforce symmetrydouble_symmetry::Bool=false: whether to enforce double symmetry (i.e. specularity)seed::Int=rand(Int): random seed for reproducibility
Examples
julia> random_pattern(12, 20, symmetry=false, seed=1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
┌────────────────────────────────────────────────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ■ ⋅ ⋅ ■ │
2 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
3 │ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ │
4 │ ■ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ■ ⋅ │
5 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ■ ⋅ ⋅ │
6 │ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
7 │ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ■ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
8 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
9 │ ⋅ ■ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
10 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ ■ ⋅ ⋅ ■ ⋅ │
11 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ ■ ⋅ ⋅ ■ │
12 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
└────────────────────────────────────────────────────────────┘
julia> random_pattern(12, 20, symmetry=true, seed=1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
┌────────────────────────────────────────────────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ │
2 │ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
3 │ ■ ⋅ ⋅ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
4 │ ■ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
5 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ■ ⋅ ⋅ │
6 │ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
7 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ■ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ │
8 │ ⋅ ⋅ ■ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
9 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ■ │
10 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ⋅ ⋅ ■ │
11 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ │
12 │ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
└────────────────────────────────────────────────────────────┘
julia> random_pattern(12, 20, symmetry=true, double_symmetry=true, seed=1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
┌────────────────────────────────────────────────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
2 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
3 │ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ │
4 │ ■ ⋅ ■ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ■ ⋅ ■ │
5 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
6 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ■ ■ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
7 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ■ ■ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
8 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
9 │ ■ ⋅ ■ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ■ ⋅ ■ │
10 │ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ │
11 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
12 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
└────────────────────────────────────────────────────────────┘Enigmistics.striped_pattern — Functionstriped_pattern(nrows, ncols;
max_density = 0.18,
min_stripe_dist = 4, keep_stripe_prob = 0.9,
symmetry = true, double_symmetry = false,
seed=rand(Int))Generate a crossword puzzle with black cells placed according to a striped pattern which can be random, symmetric, or doubly symmetric (i.e. specular).
Arguments
nrows::Int,ncols::Int: grid dimensionsmax_density::Float=0.18: maximum density of black cellsmin_stripe_dist::Int=4: minimum distance allowed between stripeskeep_stripe_prob::Float=0.8: probability of continuing a stripesymmetry::Bool=true: whether to enforce symmetrydouble_symmetry::Bool=false: whether to enforce double symmetry (i.e. specularity)seed::Int=rand(Int): random seed for reproducibility
Examples
julia> striped_pattern(12, 20, symmetry=false, seed=1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
┌────────────────────────────────────────────────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
2 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ │
3 │ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
5 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ │
6 │ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
7 │ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
8 │ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
9 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ │
10 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
11 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ │
12 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
└────────────────────────────────────────────────────────────┘
julia> striped_pattern(12, 20, symmetry=true, seed=1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
┌────────────────────────────────────────────────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
2 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ │
3 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
5 │ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ │
6 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
7 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
8 │ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ │
9 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ │
10 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
11 │ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
12 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ │
└────────────────────────────────────────────────────────────┘
julia> striped_pattern(12, 20, symmetry=true, double_symmetry=true, seed=1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
┌────────────────────────────────────────────────────────────┐
1 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
2 │ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ │
3 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
5 │ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ │
6 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
7 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
8 │ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ │
9 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
10 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
11 │ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ │
12 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
└────────────────────────────────────────────────────────────┘IO
Enigmistics.save_crossword — Functionsave_crossword(cw::CrosswordPuzzle, filename::String)Save the crossword puzzle cw to a text file specified by filename.
The grid is saved using the following conventions:
/represents a black cell.represents an empty cell- letters represent filled cells
Examples
julia> cw = example_crossword(type="full")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ O ■ A │
3 │ T ■ S O U R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ W I N D O W │
└──────────────────┘
julia> save_crossword(cw, "ex1.txt")file ex1.txt:
GOLDEN
AN/O/A
T/SOUR
EVER/R
/IE//O
WINDOWEnigmistics.load_crossword — Functionload_crossword(path::String)Load a crossword puzzle from a text file specified by path.
The grid should be written in the file using the following conventions:
/represents a black cell.represents an empty cell- letters represent filled cells
Examples
file ex2.txt:
GOLDEN
AN/O/.
T/SOUR
EVER/.
/IE/S.
.IN.O.julia> cw = load_crossword("ex2.txt"); show_crossword(cw)
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ O ■ ⋅ │
3 │ T ■ S O U R │
4 │ E V E R ■ ⋅ │
5 │ ■ I E ■ S ⋅ │
6 │ ⋅ I N ⋅ O ⋅ │
└──────────────────┘
Horizontal:
- 'GOLDEN' at (1, 1)
- 'AN' at (2, 1)
- 'SOUR' at (3, 3)
- 'EVER' at (4, 1)
- 'IE' at (5, 2)
Vertical:
- 'GATE' at (1, 1)
- 'ON' at (1, 2)
- 'VII' at (4, 2)
- 'SEEN' at (3, 3)
- 'DOOR' at (1, 4)
- 'SO' at (5, 5)
Black cells:
- at (3, 2) was automatically derived (count=3.0)
- at (4, 5) was automatically derived (count=2.0)
- at (2, 5) was manually placed (count=Inf)
- at (5, 1) was automatically derived (count=2.0)
- at (2, 3) was automatically derived (count=2.0)
- at (5, 4) was automatically derived (count=2.0)Automation
Enigmistics.Slot — TypeSlotStructure for a crossword slot, i.e. a place where a word can be placed.
Fields
row::Int,col::Int: slot positiondirection::Symbol: slot direction (:horizontalor:vertical)length::Int: slot lengthpattern::String: slot pattern, describing letters or blank spaces in its cellsflexible_start::Bool: can this slot be potentially expanded at the start (e.g. is it at the border of the grid)?flexible_end::Bool: can this slot be potentially expanded at the end (e.g. is it at the border of the grid)?
Enigmistics.find_constrained_slots — Functionfind_constrained_slots(cw::CrosswordPuzzle)Find all slots (horizontal and vertical) in the crossword puzzle cw that are constrained, i.e. that have at least one empty cell and length at least 2 characters.
Examples
julia> cw = example_crossword(type="partial")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘
julia> find_constrained_slots(cw)
4-element Vector{Slot}:
Slot(3, 3, :horizontal, 4, "...R", false, true)
Slot(6, 1, :horizontal, 6, ".I...W", true, true)
Slot(3, 3, :vertical, 4, ".EE.", false, true)
Slot(1, 4, :vertical, 4, "D..R", true, false)Enigmistics.compute_options_simple — Functioncompute_options_simple(s::Slot)Compute the number of fitting words for a slot s considering its pattern and length.
Return a tuple (n_options, candidates), where n_options is the number of fitting words and candidates is a vector containing the list of fitting words.
Examples
julia> cw = example_crossword(type="partial")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘
julia> slots = find_constrained_slots(cw); slots[2]
Slot(6, 1, :horizontal, 6, ".I...W", true, true)
julia> compute_options_simple(slots[2], verbose=true)
- simple fitting, length: 6 => #options: 18
some are ["billow", "dismaw", "disnew", "jigsaw", "killow", "kirmew", "mildew", "minnow", "pigmew", "pillow"]Enigmistics.compute_options_split — Functioncompute_options_split(s::Slot)Compute the number of fitting words for a slot s by simulating the placement of black cells at each internal position of the slot, i.e. possibly splitting the original slot into two smaller slots.
Return a tuple (n_options, candidates), where n_options is a dictionary mapping the internal position of the black cell to the number of fitting words, and candidates is a dictionary mapping that same key to a tuple of two lists of fitting words (left and right sub-slots).
Examples
julia> cw = example_crossword(type="partial")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘
julia> slots = find_constrained_slots(cw); slots[2]
Slot(6, 1, :horizontal, 6, ".I...W", true, true)
julia> compute_options_split(slots[2], verbose=true)
- placing a black cell at (6, 1), pattern: /I...W => #options: 7
they are ["ignaw", "immew", "inbow", "indew", "indow", "inlaw", "inmew"]
- placing a black cell at (6, 3), pattern: .I/..W => #options: 20/53
some are Left: ["ai", "bi", "di", "fi", "gi", "hi", "ii", "yi", "ji", "ki"]
some are Right: ["alw", "baw", "bow", "caw", "ccw", "ckw", "cow", "csw", "daw", "dew"]
- placing a black cell at (6, 4), pattern: .I./.W => #options: 212/10
some are Left: ["aid", "aik", "ail", "aim", "ain", "air", "ais", "ait", "aix", "bib"]
they are Right: ["aw", "ew", "fw", "hw", "iw", "kw", "mw", "ow", "sw", "xw"]
- placing a black cell at (6, 5), pattern: .I../W => #options: 852
some are ["aias", "aide", "aids", "aiel", "aile", "ails", "aims", "aine", "ains", "aint"]Enigmistics.compute_options_flexible — Functioncompute_options_flexible(s::Slot, k::Int)Compute the number of fitting words for a slot s considering its flexibility at the start and/or at the end, i.e. simulating the potential expansion of k cells in length which could happen by enlarging the grid.
Return a tuple (n_options, candidates), where n_options is a dictionary mapping the flexibility simulated (increment at the start and/or at the end) to the number of fitting words, and candidates is a dictionary mapping that same key to the list of fitting words.
Examples
julia> cw = example_crossword(type="partial")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘
julia> slots = find_constrained_slots(cw); slots[2]
Slot(6, 1, :horizontal, 6, ".I...W", true, true)
julia> compute_options_flexible(slots[2], 1, verbose=true)
- flexible start/end, increment: (0, 1) => pattern .I...W., length: 7 => #options: 19
some are ["billowy", "billows", "disgown", "jigsawn", "jigsaws", "midtown", "mildewy", "mildews", "minnows", "pillowy"]
- flexible start/end, increment: (1, 0) => pattern ..I...W, length: 7 => #options: 9
they are ["pristaw", "rainbow", "thishow", "trishaw", "uniflow", "whincow", "whipsaw", "whitlow", "whittaw"]
julia> compute_options_flexible(slots[2], 2, verbose=true)
- flexible start/end, increment: (0, 2) => pattern .I...W.., length: 8 => #options: 33
some are ["bilgeway", "billywix", "billowed", "disbowel", "giveaway", "hideaway", "jigsawed", "midtowns", "mildewed", "mildewer"]
- flexible start/end, increment: (1, 1) => pattern ..I...W., length: 8 => #options: 11
some are ["boildown", "chippewa", "gairfowl", "muirfowl", "rainbowy", "rainbows", "rainfowl", "thindown", "whipsawn", "whipsaws"]
- flexible start/end, increment: (2, 0) => pattern ...I...W, length: 8 => #options: 3
they are ["embillow", "splitnew", "splitsaw"]Enigmistics.set_dictionary — Functionset_dictionary(language="en")Load the dictionary associated to the specified language.
Current supported languages are
- "en", english
- "it", italian.
Enigmistics.fitting_words — Functionfitting_words(pattern::Regex, min_len::Int, max_len::Int)
fitting_words(pattern::String, min_len::Int, max_len::Int)Given a pattern and a range of word lengths, return a dictionary mapping each length to the list of matching words having that pattern and length.
Examples
julia> pattern="^pe.*ce$"
"^pe.*ce$"
julia> fitting_words(pattern, 5, 8)
Dict{Int64, Vector{String}} with 4 entries:
5 => ["peace", "pence"]
6 => ["pearce"]
7 => ["penance", "pentace", "pentice"]
8 => ["perforce"]
julia> set_dictionary("it");
julia> fitting_words(pattern, 5, 8)
Dict{Int64, Vector{String}} with 4 entries:
5 => ["pence", "pesce"]
6 => ["pedace", "pedice", "penice"]
7 => ["pendice", "pennace", "perisce", "pernice"]
8 => ["pellacce", "pellicce", "pennucce", "pezzacce"]Base.fill! — Methodfill!(cw::CrosswordPuzzle; seed=rand(Int), verbose=false)Fill the crossword puzzle cw using a backtracking algorithm with Minimum Remaining Values (MRV) heuristic.
For now it only uses the simple fitting method (compute_options_simple) to compute candidate words for each slot; more advanced methods (flexibility, splitting) will soon be added.
Examples
julia> cw = example_crossword(type="partial")
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ ⋅ ■ A │
3 │ T ■ ⋅ ⋅ ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘
julia> fill!(cw, seed=80)
true
julia> cw
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ Y ■ A │
3 │ T ■ P E E R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ P I L L O W │
└──────────────────┘