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 surrounding 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("MAP",1,3,:vertical),
CrosswordWord("SIR",4,4,:horizontal)];
julia> CrosswordPuzzle(5,6,words)
1 2 3 4 5 6
┌──────────────────┐
1 │ ⋅ ⋅ M ⋅ ⋅ ⋅ │
2 │ ■ C A T ■ ⋅ │
3 │ ⋅ ⋅ P ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ■ S I R │
5 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
└──────────────────┘
julia> words = [CrosswordWord("CAT",2,2,:horizontal), CrosswordWord("MAP",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.
└ @ Enigmistics ...
┌ Error: Words intersections are not compatible.
└ @ Enigmistics ...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("MAP",1,3,:vertical),
CrosswordWord("SIR",4,4,:horizontal)];
julia> CrosswordPuzzle(words)
1 2 3 4 5 6
┌──────────────────┐
1 │ ⋅ ⋅ M ⋅ ⋅ ⋅ │
2 │ ■ C A T ■ ⋅ │
3 │ ⋅ ⋅ P ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ■ S I R │
└──────────────────┘
julia> words = [CrosswordWord("CAT",2,2,:horizontal), CrosswordWord("MAP",1,3,:vertical),
CrosswordWord("SIR",4,4,:horizontal), CrosswordWord("DOG",1,2,:vertical)];
julia> CrosswordPuzzle(words)
┌ Warning: Cannot place word 'DOG' at (1, 2) vertically due to conflict at cell (2, 2); found when checking the inner cells.
└ @ Enigmistics ...
┌ Error: Words intersections are not compatible.
└ @ Enigmistics ...Interface
Enigmistics.show_crossword — Functionshow_crossword(cw::CrosswordPuzzle;
words_details=true, black_cells_details=false, plot_details=true)Print the crossword grid, possibly along with the words details and black cells details.
Examples
julia> cw = example_crossword() # 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, black_cells_details=true) # 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 │
└──────────────────┘
Grid size: (6, 6)
Black cells density: 19.44%
Horizontal words:
- GOLDEN
- AN
- SOUR
- EVER
- IE
- WINDOW
Vertical words:
- GATE
- ON, VII
- DOOR
- NARROW
- SEEN
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)
Words length distribution
┌────────────────────────────────────────┐
2 │■■■■■■■■■■■■■■■■■■■■■■ 3 │
3 │■■■■■■■ 1 │
4 │■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 5 │
5 │ 0 │
6 │■■■■■■■■■■■■■■■■■■■■■■ 3 │
└────────────────────────────────────────┘ 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 │
└──────────────────┘Base.transpose — MethodBase.transpose(cw::CrosswordPuzzle)Transpose the crossword.
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> transpose(cw)
1 2 3 4 5 6
┌──────────────────┐
1 │ G A T E ■ W │
2 │ O N ■ V I I │
3 │ L ■ S E E N │
4 │ D O O R ■ D │
5 │ E ■ U ■ ■ O │
6 │ N A R R O W │
└──────────────────┘Enigmistics.enlarge! — Functionenlarge!(cw::CrosswordPuzzle, how::Symbol, times=1)
enlarge!(cw::CrosswordPuzzle, 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. If no direction is specified, the grid will be enlarged in all directions by the amount given by times.
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, 2); cw
1 2 3 4 5 6 7 8
┌────────────────────────┐
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); cw
1 2 3 4 5 6 7 8 9 10
┌──────────────────────────────┐
1 │ ⋅ ⋅ ⋅ ■ ■ ⋅ ■ ⋅ ■ ⋅ │
2 │ ⋅ ⋅ ■ G O L D E N ■ │
3 │ ⋅ ⋅ ■ A N ■ O ■ A ⋅ │
4 │ ⋅ ⋅ ⋅ T ■ S O U R ■ │
5 │ ⋅ ⋅ ■ E V E R ■ R ⋅ │
6 │ ⋅ ⋅ ⋅ ■ I E ■ ■ O ⋅ │
7 │ ⋅ ⋅ ■ W I N D O W ■ │
8 │ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ ⋅ ■ ⋅ │
└──────────────────────────────┘Enigmistics.shrink! — Functionshrink!(cw::CrosswordPuzzle)Reduce the crossword size to its minimal representation by removing useless rows/columns.
Note that it will not necessarily preserve the connectivity of the crossword. If you want to restore it, you can rely on the enlarge! function.
Examples
julia> cw = example_crossword(type="full"); enlarge!(cw); cw
1 2 3 4 5 6 7 8
┌────────────────────────┐
1 │ ⋅ ■ ■ ⋅ ■ ⋅ ■ ⋅ │
2 │ ■ G O L D E N ■ │
3 │ ■ A N ■ O ■ A ⋅ │
4 │ ⋅ T ■ S O U R ■ │
5 │ ■ E V E R ■ R ⋅ │
6 │ ⋅ ■ I E ■ ■ O ⋅ │
7 │ ■ W I N D O W ■ │
8 │ ⋅ ⋅ ■ ■ ⋅ ⋅ ■ ⋅ │
└────────────────────────┘
julia> shrink!(cw)
true
julia> 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, cword::CrosswordWord)Check if a word can be placed in the crossword puzzle cw at the given position and direction. Actually, this is more likely an internal/helper function, as further checks are also performed in the place_word! function.
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).
└ @ Enigmistics ...
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.
└ @ Enigmistics ...
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.
└ @ Enigmistics ...
false
julia> can_place_word(cw, "DEAR", 1, 4, :v)
trueEnigmistics.place_word! — Functionplace_word!(cw::CrosswordPuzzle, word::String, row, col, direction::Symbol; dictionary_check = true)
place_word!(cw::CrosswordPuzzle, cword::CrosswordWord; dictionary_check = true)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.
The argument dictionary_check, if set to true (as the default), allows to check if any new word delimited by the placement of the given word belongs to the dictionary, and if not then the given word will not be placed, as it will generate another word which is not valid according to the dictionary loaded.
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.
└ @ Enigmistics ...
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, cwords::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.
└ @ Enigmistics ...
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; symmetry=false, double_symmetry=false)
place_black_cell!(cw::CrosswordPuzzle, coords::Vector{<:Union{Tuple{Int, Int}, CartesianIndex{2}}}; kwargs...)
place_black_cell!(cw::CrosswordPuzzle, rows::OrdinalRange{Int64,Int64}, cols::OrdinalRange{Int64,Int64}; kwargs...)
place_black_cell!(cw::CrosswordPuzzle, row::Int64, col::Int64, direction::String, stripe_len::Int64; kwargs...)Place a black cell (or multiple black cells) in the crossword puzzle cw at the given position, and possibly also the ones given by the symmetry arguments (if set).
Return true if upon the request of placement(s) the grid has changed, 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 the given position since cell is not empty. No changes on the original grid.
└ @ Enigmistics ...
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_black_cell!(cw::CrosswordPuzzle, coords::Vector{<:Union{Tuple{Int, Int}, CartesianIndex{2}}}; kwargs...)
remove_black_cell!(cw::CrosswordPuzzle, rows::OrdinalRange{Int64,Int64}, cols::OrdinalRange{Int64,Int64}; kwargs...)Remove a black cell (or multiple black cells) from the crossword puzzle cw at the given position, and possibly also the ones given by the symmetry arguments (if set).
Return true if upon the request of removal(s) the grid has changed, false otherwise.
Examples
julia> cw = example_crossword(type="partial"); show_crossword(cw, words_details=false, black_cells_details=true, plot_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 │
└──────────────────┘
Grid size: (6, 6)
Black cells density: 19.44%
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.
└ @ Enigmistics ...
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=false)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, black_cells_details=true, plot_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 │
└──────────────────┘
Grid size: (6, 6)
Black cells density: 19.44%
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)
1 2 3 4 5 6
┌──────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
2 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
3 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
5 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
6 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
└──────────────────┘
julia> clear!(cw, deep=true)
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.14: 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)
[ Info: 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)
[ Info: 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)
[ Info: 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)
[ Info: 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)
[ Info: 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)
[ Info: 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.standard_pattern — Functionstandard_pattern(; style="NewYorkTimes", kwargs...)Generate a crossword puzzle according to a standard pattern.
Available styles are: "NewYorkTimes", "Diamond", "BritishCryptic", "Polar", "Parametric". For each style, the following optional arguments (with default values) are supported:
- Diamond:
grid_size=(13,13) - BritishCryptic:
grid_size=(15,15),max_density=0.24,seed=rand(Int) - Polar:
step_size=0.1,r_function=(θ->0.8*θ),max_theta=2π,round_method=RoundNearest(also RoundUp often produces good results),symmetry=false(argument for the black cell placement function),spread_by=1 - Parametric:
step_size=0.1,x_function=(θ->4*cos(θ)+1),y_function=(θ->4*sin(θ)+1),max_theta=2π,round_method=RoundNearest(also RoundUp often produces good results),symmetry=false(argument for the black cell placement function),spread_by=1
Example
julia> cw = standard_pattern(style="NewYorkTimes")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
┌─────────────────────────────────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
2 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
3 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
5 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ ■ │
6 │ ■ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ │
7 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
8 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
9 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
10 │ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ■ │
11 │ ■ ■ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
12 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
13 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
14 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
15 │ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ │
└─────────────────────────────────────────────┘
julia> cw = standard_pattern(style="Diamond", grid_size=(9,9))
1 2 3 4 5 6 7 8 9
┌───────────────────────────┐
1 │ ■ ■ ■ ■ ⋅ ■ ■ ■ ■ │
2 │ ■ ■ ■ ⋅ ⋅ ⋅ ■ ■ ■ │
3 │ ■ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ │
4 │ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ │
5 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
6 │ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ │
7 │ ■ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ │
8 │ ■ ■ ■ ⋅ ⋅ ⋅ ■ ■ ■ │
9 │ ■ ■ ■ ■ ⋅ ■ ■ ■ ■ │
└───────────────────────────┘
julia> cw = standard_pattern(style="BritishCryptic", grid_size=(11,13), seed=1234)
[ Info: seed = 1234
1 2 3 4 5 6 7 8 9 10 11 12 13
┌───────────────────────────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ │
2 │ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ │
3 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
4 │ ■ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ │
5 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
6 │ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ │
7 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
8 │ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ■ │
9 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
10 │ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ ■ ⋅ │
11 │ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
└───────────────────────────────────────┘
julia> cw = standard_pattern(style="Polar", r_function=(θ->0.8*θ), max_theta=2.8π)
1 2 3 4 5 6 7 8 9 10 11 12 13
┌───────────────────────────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
2 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ │
3 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ │
4 │ ⋅ ⋅ ⋅ ■ ■ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
5 │ ⋅ ⋅ ■ ■ ⋅ ■ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
6 │ ⋅ ■ ■ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
7 │ ⋅ ■ ⋅ ⋅ ⋅ ■ ■ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
8 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
9 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
10 │ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ │
11 │ ⋅ ⋅ ■ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ ⋅ │
12 │ ⋅ ⋅ ⋅ ⋅ ■ ■ ■ ■ ■ ⋅ ⋅ ⋅ ⋅ │
13 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
└───────────────────────────────────────┘
julia> cw = standard_pattern(style="Parametric", x_function=(θ->4*cos(θ)+1), y_function=(θ->4*sin(θ)+1), max_theta = 1.5π)
1 2 3 4 5 6 7 8 9 10 11
┌─────────────────────────────────┐
1 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
2 │ ⋅ ⋅ ⋅ ■ ■ ■ ■ ■ ⋅ ⋅ ⋅ │
3 │ ⋅ ⋅ ■ ■ ⋅ ⋅ ⋅ ■ ■ ⋅ ⋅ │
4 │ ⋅ ■ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ │
5 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
6 │ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
7 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ │
8 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ │
9 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ ⋅ │
10 │ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ ■ ⋅ ⋅ ⋅ │
11 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
└─────────────────────────────────┘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 in a visually readable format, matching the terminal output, complete with borders, row/column indicators, and clear symbols.
Example
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:
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.load_crossword — Functionload_crossword(path::String)Load a crossword puzzle from a text file specified by path.
The function is capable of parsing both the visually rich format (with borders and numbers) and the classic dense format.
Conventions recognized:
■or/represent a black cell⋅or.represent an empty cell- letters represent filled cells
Example
file ex2.txt:
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 ⋅ │
└──────────────────┘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)Enigmistics.to_latex — Functionto_latex(cw::CrosswordPuzzle, clues::Dict{String, String}=Dict{String, String}();
full_document = true, show_solution = true, solution_on_new_page = false,
inline_clues = false, title = "", subtitle = "", empty_borders_grid = false)Generate a string containing the LaTeX code which can be used (thanks to the cwpuzzle package) to represent the crossword puzzle cw.
Keyword Arguments
full_document=true: iftrue, includes the preamble and other requirements to generate a full latex output; set it tofalseif you want to paste the output into an existing LaTeX fileshow_solution=true: iftrue, generates a second grid at the bottom with the solutionsolution_on_new_page=false: iftrue, puts a\newpagebefore the solution, otherwise puts\vfillinline_clues=false: iftrue, formats the clues to run continuously on the same linetitle="": (if provided) the title to be printed above the crossword puzzlesubtitle="": (if provided) the subtitle to be printed below the titleempty_borders_grid=false: iftrue, any black cells touching the outer boundary (and contiguous clusters) will be rendered as empty cells ({}) instead of black squares (*)
Examples
julia> open("out.tex", "w") do file
write(file,
to_latex(
cw,
clues, # obtained through the derive_clues function
full_document=true, inline_clues=true,
title="Example Crossword",
subtitle="(this is a subtitle)"
)
)
end;Now we move the focus towards the automation capabilities implemented by this package.
Slots
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(cw::CrosswordPuzzle, s::Slot; verbose=false)Compute the number of fitting words for a slot s of the crossword cw 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(cw, slots[2], verbose=true)
- simple fitting, length: 6 => #options: 19
some are ["AIMLOW", "BIGWOW", "BILLOW", "JIGSAW", "KIDROW", "LIELOW", "MIDBOW", "MILDEW", "MINNOW", "PILLOW"]Enigmistics.compute_options_split — Functioncompute_options_split(cw::CrosswordPuzzle, s::Slot; verbose=false)Compute the number of fitting words for a slot s of the crossword cw 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(cw, slots[2], verbose=true)
- placing a black cell at (6, 1), pattern: /I...W => #options: 13
some are ["IDRAW", "IKNEW", "IKNOW", "INDOW", "INLAW", "INLOW", "INNEW", "INTOW", "ISHOW", "ISLEW"]
- placing a black cell at (6, 3), pattern: .I/..W => #options: 23/96
some are Left: ["AI", "BI", "CI", "DI", "EI", "GI", "HI", "II", "JI", "KI"]
some are Right: ["ALW", "AWW", "BBW", "BMW", "BOW", "BTW", "CAW", "CCW", "CEW", "COW"]
- placing a black cell at (6, 4), pattern: .I./.W => #options: 341/8
some are Left: ["AIA", "AIC", "AID", "AIG", "AIL", "AIM", "AIN", "AIR", "AIS", "AIT"]
they are Right: ["AW", "BW", "EW", "IW", "KW", "NW", "OW", "SW"]
- placing a black cell at (6, 5), pattern: .I../W => #options: 1136
some are ["AIAI", "AIBO", "AIDA", "AIDE", "AIDS", "AIDY", "AIEA", "AIGU", "AIKO", "AILE"]Enigmistics.compute_options_flexible — Functioncompute_options_flexible(cw::CrosswordPuzzle, s::Slot, k::Int=1; verbose=false)Compute the number of fitting words for a slot s of the crossword cw 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(cw, slots[2], 1, verbose=true)
- flexible start/end, increment: (0, 1) => pattern .I...W., length: 7 => #options: 43
some are ["AIMDOWN", "BIGNEWS", "BIGTOWN", "BIGYAWN", "BILLOWS", "BILLOWY", "DIALTWO", "DIDNTWE", "DIEDOWN", "DIGDOWN"]
- flexible start/end, increment: (1, 0) => pattern ..I...W, length: 7 => #options: 15
some are ["AWILLOW", "BRISTOW", "DOITNOW", "EXITROW", "HAIRBOW", "LAIDLOW", "LAINLOW", "RAINBOW", "SKIDROW", "SKILSAW"]
julia> compute_options_flexible(cw, slots[2], 2, verbose=true)
- flexible start/end, increment: (0, 2) => pattern .I...W.., length: 8 => #options: 59
some are ["AIRPOWER", "AISLEWAY", "AIWEIWEI", "BIDEAWEE", "BILLOWED", "BILLOWUP", "CIVILWAR", "DIEDAWAY", "DIESAWAY", "DIRTYWAR"]
- flexible start/end, increment: (1, 1) => pattern ..I...W., length: 8 => #options: 35
some are ["BOILDOWN", "CHICHEWA", "CHIPBOWL", "CHIPPEWA", "EDITDOWN", "EXITROWS", "FAIRLAWN", "GOINDOWN", "HAILDOWN", "HAIRBOWS"]
- flexible start/end, increment: (2, 0) => pattern ...I...W, length: 8 => #options: 19
some are ["ALLIKNOW", "BASICLAW", "BUYITNOW", "BYWINDOW", "CAPITALW", "CHAINSAW", "CIVILLAW", "DAVIDLOW", "DIPITLOW", "GETITNOW"]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) # with default english dictionary
Dict{Int64, Vector{String}} with 4 entries:
5 => ["PEACE", "PENCE", "PERCE", "PESCE"]
6 => ["PEARCE", "PEERCE", "PEIRCE"]
7 => ["PENANCE", "PETMICE"]
8 => ["PEEDANCE", "PENZANCE", "PERFORCE", "PETFENCE"]Dictionary
Enigmistics.set_dictionary — Functionset_dictionary(language="en", words_customization=(words->words);
allow_all_2chars_combinations=false,
limit_length=26,
score=nothing
)Load the dictionary associated to the specified language. Current supported ones are:
- "en", english
- "it", italian
- "bs", bresciano (italian dialect spoken in the province of Brescia, northern Italy)
- "lt", latin
Arguments
words_customization: see next sectionallow_all_2chars_combinations: whether to add all possible 2-characters combinations to the dictionary or just keep the ones which are contained in the file. This is useful e.g. for solving puzzles where 2-letter words are ignored (and so clues don't have to defined for them)limit_length: maximum length of the words to be included in the dictionaryscore: if the dictionary is in the format "WORD,SCORE" this parameter allows to filter out words whose score is strictly less than the specified value
Example
julia> set_dictionary("en")
Dictionary set to "en"
Loading file EN_/oxford/mix/WORDS.txt...
11083 words successfully loaded into DICTIONARY_WORDS
Maximum length: 18 letters
Words length distribution
┌────────────────────────────────────────┐
2 │■■■■■■■■ 396 │
3 │■■■■■■■■■■■■■■ 688 │
4 │■■■■■■■■■■■■■■■■■■■■■■■■ 1 197 │
5 │■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1 509 │
6 │■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1 676 │
7 │■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1 656 │
8 │■■■■■■■■■■■■■■■■■■■■■■■■■■ 1 345 │
9 │■■■■■■■■■■■■■■■■■■■■ 1 041 │
10 │■■■■■■■■■■■■■■ 718 │
11 │■■■■■■■■■ 440 │
12 │■■■■■ 239 │
13 │■■ 119 │
14 │■ 43 │
15 │ 11 │
16 │ 4 │
17 │ 0 │
18 │ 1 │
└────────────────────────────────────────┘Words customization
Words can be manipulated with the words_customization function, which acts on the whole dictionary (so a Vector of String), in order to create variants of the classic crosswords. Some examples could be:
- Stenographic Crosswords, where shorthand versions of the words, i.e. ignoring vowels, are used:
# "WORD" becomes "WRD"
words_customization = words -> unique([replace(w, r"[AEIOU]" => "") for w in words])- Two-faced (or Bifrontal) Crosswords, where words may be positioned either forwards or backwards:
# a clue defining "WORD" may result in having to write either "DORW" or "WORD"
words_customization = words -> unique([words; reverse.(words)])- Sorted (or Scrambled) Crosswords, where the grid isn't filled with the words themselves but with their letters sorted alphabetically:
# "WORD" becomes "DORW", "PEACE" becomes "ACEEP", etc
words_customization = words -> unique(map(w -> join(sort(collect(w))), words))- Alphanumeric Substitution (or Rebus, or "Poliscritte", in italian) Crosswords, where numbers occurring in words are replaced by their corresponding digits:
# "HEIGHT" becomes "H8"
words_customization = words -> begin
digit_map = [
"ZERO" => "0", "ONE" => "1",
"TWO" => "2", "THREE" => "3",
"FOUR" => "4", "FIVE" => "5",
"SIX" => "6", "SEVEN" => "7",
"EIGHT" => "8", "NINE" => "9"
]
# update alphabet to pass word-checks for words which include these new symbols
push!(ALPHABET, collect(only(x[2]) for x in values(digit_map))...)
return unique(map(w -> replace(w, digit_map...), words))
end
# or this one in italian and with more symbols translated
words_customization = words -> begin
digit_map = [
"ZERO" => "0", "UNO" => "1",
"DUE" => "2", "TRE" => "3",
"QUATTRO" => "4", "CINQUE" => "5",
"SEI" => "6", "SETTE" => "7",
"OTTO" => "8", "NOVE" => "9",
# for these ones, avoid symbols which have a regex meaning, so:
"PIU" => "+", # Unicode U+FF0B rather than '+' ASCII/Unicode U+002B
"MENO" => "−", # Unicode U+2212 rather than '-' ASCII/Unicode U+002D
"PER" => "×" # Unicode U+00D7 rather than '*' ASCII/Unicode U+002A
]
# update alphabet to pass word-checks for words which include these new symbols
push!(ALPHABET, collect(only(x[2]) for x in values(digit_map))...)
return unique(map(w -> replace(w, digit_map...), words))
endOther applications of this function could be to filter only some kind of words, for example:
- "only include words with no repeating letters" (e.g., "DIALECT" is okay, "DATABASE" is not).
words_customization = words -> filter(w -> allunique(w), words)How to setup a language
create a folder which will contain all the language files, for example
src/Crosswords/dictionaries/LATIN.create a file containing the words of the dictionary, for example
src/Crosswords/dictionaries/LATIN/latinlexicon/WORDS.txt. It should contain just a list of words, one per line. Lowercase or uppercase is indifferent.
AB
ABACTUS
ABACUS
ABALIENATIO
ABALIENO
ABANTEUS
ABANTIADES
ABAVUS
ABCIDO
...- optionally, create a file containing the clues for the dictionary words, for example
src/Crosswords/dictionaries/LATIN/latinlexicon/CLUES.csv. Note that the extension of the file is now.csv, not.txt, as it should be in the format "WORD,clue".
AB,[ab] {preposition} [space] from
ABACTUS,[abāctus] {adjective} driven away
ABACUS,[abacus] {noun} a table of precious material for the display of plate
ABALIENATIO,[abaliēnātiō] {noun} [in law] a transfer of property
ABALIENO,[abaliēnō] {verb} to convey away
ABANTEUS,[Abantēus] {adjective} of Abas (king of Argos)
ABANTIADES,[Abantiadēs] {noun} a son or descendant of Abas (king of Argos)
ABAVUS,[abavus] {noun} a grandfather's grandfather
ABCIDO,[abcīdō] {verb} to cut off
...- expand and adjust accordingly the references in the
DICTIONARIES_INFOdictionary fromsrc/Crosswords/dictionary.jl, which for example now is currently set to this:
DICTIONARIES_INFO = Dict(
"lt" => "LATIN/latinlexicon/WORDS.txt",
...
)- clues will be automatically derived from the clues file when calling
derive_cluesfunction, which is described in the next section.
Enigmistics.derive_clues — Functionderive_clues(cw::CrosswordPuzzle, language::String, words_customization=(wvec->wvec);
no_clues_placeholder=nothing, limit=3)Given a crossword puzzle and a language, return a dictionary mapping each word in the crossword to its clue.
A words_customization function may be provided to match any transformation applied to the words in the loading (set_dictionary) phase. If a word has multiple clues, they (up to the specified limit) will be concatenated with " / " as a separator. If a word has no clue, it will be assigned the value of no_clues_placeholder, if specified, otherwise it will simply be missing from the resulting dictionary.
Example
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> clues = derive_clues(cw, "src/Crosswords/dictionaries/EN_/xd/CLUES.csv", limit=2)
Dict{String, String} with 12 entries:
"ON" => "In contact with the upper part. / Upon"
"GATE" => "Stadium entrance / Airport area"
"WINDOW" => "Teller's place / Internet Explorer or Microsoft Word, say"
"EVER" => "Always / At any time"
"AN" => "One or any / Article before words starting with a vowel sound"
"SEEN" => "" ... __ and not heard." / Noticed"
"DOOR" => "It holds the lock / It may be a folding or a revolving"
"GOLDEN" => "Brilliant / Like silence"
"IE" => "That is, but in Latin"
"SOUR" => "Embitter / Ill-humored"
"VII" => ""QB __" (Uris novel) / Caesar's lucky number?"
"NARROW" => "Long and thin / Like Zion National Park's slot canyons"Moves
Enigmistics.Move — TypeMoveAbstract supertype for all move types in the crossword puzzle.
Enigmistics.Place — TypePlace <: MoveA move corresponding to the placement of a word in a slot.
Enigmistics.SplitAndPlace — TypeSplitAndPlace <: MoveA move corresponding to the placement of a black cell in a slot and one or two words in the resulting sub-slots.
Enigmistics.EnlargeAndPlace — TypeEnlargeAndPlace <: MoveA move corresponding to the placement of a word in a slot after having enlarged the crossword grid.
Enigmistics.apply! — Functionapply!(cw::CrosswordPuzzle, m::Place)
undo!(cw::CrosswordPuzzle, m::Place)Apply/undo a move of type Place.
Example
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> slot = find_constrained_slots(cw)[2]
Slot(6, 1, :horizontal, 6, ".I...W", true, true)
julia> out = generate_moves(cw, slot, allow_split=true, allow_enlarge=true);
julia> out[1]
Place(Slot(6, 1, :horizontal, 6, ".I...W", true, true), "BILLOW")
julia> apply!(cw, out[1])
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 │ B I L L O W │
└──────────────────┘
julia> undo!(cw, out[1])
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 │
└──────────────────┘apply!(cw::CrosswordPuzzle, m::SplitAndPlace)
undo!(cw::CrosswordPuzzle, m::SplitAndPlace)Apply/undo a move of type SplitAndPlace.
Example
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> slot = find_constrained_slots(cw)[2]
Slot(6, 1, :horizontal, 6, ".I...W", true, true)
julia> out = generate_moves(cw, slot, allow_split=true, allow_enlarge=true);
julia> out[1000]
SplitAndPlace(Slot(6, 1, :horizontal, 6, ".I...W", true, true), 4, "BIM", "XW")
julia> apply!(cw, out[1000])
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 │ B I M ■ X W │
└──────────────────┘
julia> undo!(cw, out[1000])
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 │
└──────────────────┘apply!(cw::CrosswordPuzzle, m::JustPlaceABlackCell)
undo!(cw::CrosswordPuzzle, m::JustPlaceABlackCell)Apply/undo a move of type JustPlaceABlackCell.
Example
julia> cw
1 2 3 4
┌────────────┐
1 │ ⋅ ⋅ T Z │
2 │ ⋅ ⋅ E E │
3 │ ⋅ ⋅ S T │
4 │ ⋅ ⋅ T A │
└────────────┘
julia> slot = find_constrained_slots(cw)[1]
Slot(1, 1, :horizontal, 4, "..TZ", true, true)
julia> move = JustPlaceABlackCell(slot,1)
JustPlaceABlackCell(Slot(1, 1, :horizontal, 4, "..TZ", true, true), 1)
julia> apply!(cw, move)
true
julia> cw
1 2 3 4
┌────────────┐
1 │ ■ ⋅ T Z │
2 │ ⋅ ⋅ E E │
3 │ ⋅ ⋅ S T │
4 │ ⋅ ⋅ T A │
└────────────┘
julia> undo!(cw, move)
true
julia> cw
1 2 3 4
┌────────────┐
1 │ ⋅ ⋅ T Z │
2 │ ⋅ ⋅ E E │
3 │ ⋅ ⋅ S T │
4 │ ⋅ ⋅ T A │
└────────────┘apply!(cw::CrosswordPuzzle, m::EnlargeAndPlace)
undo!(cw::CrosswordPuzzle, m::EnlargeAndPlace)Apply/undo a move of type EnlargeAndPlace.
Example
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> slot = find_constrained_slots(cw)[2]
Slot(6, 1, :horizontal, 6, ".I...W", true, true)
julia> out = generate_moves(cw, slot, allow_split=true, allow_enlarge=true);
julia> out[end]
EnlargeAndPlace(Slot(6, 1, :horizontal, 6, ".I...W", true, true), "WHIPSAW", 1, 0)
julia> apply!(cw, out[end])
true
julia> cw
1 2 3 4 5 6 7
┌─────────────────────┐
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 │ W H I P S A W │
└─────────────────────┘
julia> undo!(cw, out[end])
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.undo! — FunctionSolvers
Enigmistics.solve! — Functionsolve!(cw::CrosswordPuzzle; seed=rand(Int), kwargs...)Fill a crossword puzzle cw using a backtracking algorithm with Minimum Remaining Values (MRV) heuristic, considering all types of moves (simple placements, splits with black cells, enlargements).
Arguments:
max_trials_per_slot=10: maximum number of candidate words to try for each slot. It gets scaled as the completeness of the grid increases to allow more trials towards the end of the search, when the grid is almost full and therefore also more constrainedmin_options_to_allow_split=5: if a slot has more than this number of candidate words with the simple method, then we do not consider moves involving splits with black cells when filling that slotmin_options_to_allow_enlarge=0: if a slot has more than this number of candidate words with the simple method, then we do not consider moves involving enlarging the grid when filling that slotmax_enlarge=1: maximum number of cells by which to enlarge the grid when considering moves of typeEnlargeAndPlaceverbose=true: print the completeness percentage of the grid during the search (useful also for allowing interrupts through ctrl+c)see_evolution=false: display the status of the grid at each step of the algorithm, therefore allowing to see the grid filling in real time (note: this option will likely produce a slower execution time)
Example
julia> cw = random_pattern(10,14)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
┌──────────────────────────────────────────┐
1 │ ■ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ ⋅ │
2 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ │
3 │ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ■ ■ ⋅ ⋅ ⋅ ⋅ ⋅ │
4 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
5 │ ⋅ ■ ■ ⋅ ■ ⋅ ⋅ ■ ⋅ ⋅ ■ ⋅ ⋅ ⋅ │
6 │ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ■ ⋅ ⋅ ■ ⋅ ■ ■ ⋅ │
7 │ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
8 │ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ │
9 │ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ │
10 │ ⋅ ⋅ ■ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ■ │
└──────────────────────────────────────────┘
julia> solve!(cw, min_options_to_allow_split=0)
true
julia> cw
1 2 3 4 5 6 7 8 9 10 11 12 13 14
┌──────────────────────────────────────────┐
1 │ ■ ■ C A R A C A R A ■ ■ O W │
2 │ B L U E C U R A C A O ■ S R │
3 │ L A R I D ■ E ■ ■ S W A M I │
4 │ I M N O T F E E L I N G I T │
5 │ N ■ ■ U ■ C K ■ A N ■ U N E │
6 │ D K S ■ A K ■ N R ■ A ■ ■ A │
7 │ S C R A T C H A N D C L A W │
8 │ I A I N T ■ ■ I ■ A E C I A │
9 │ D M ■ C A T A R R H A L L Y │
10 │ E P ■ ■ R E D N O S E S ■ ■ │
└──────────────────────────────────────────┘solve!(sdk::Sudoku, solutions=Vector{Sudoku}())Solve the sudoku puzzle through recursion, returning all the found solutions.
Examples
julia> sdk = generate_sudoku(3, 3, holes_pct=0.4, seed=1, symmetry=true)
[ Info: seed = 1
┌───────────────────────┐
│ ⋅ 2 ⋅ │ 5 1 ⋅ │ ⋅ 3 ⋅ │
│ 6 ⋅ ⋅ │ 3 4 9 │ 8 2 ⋅ │
│ 9 ⋅ ⋅ │ 2 ⋅ ⋅ │ 1 ⋅ ⋅ │
│───────#───────#───────│
│ 8 9 ⋅ │ ⋅ 2 1 │ 5 ⋅ 4 │
│ 1 7 2 │ 6 5 4 │ 3 9 8 │
│ 4 ⋅ 5 │ 9 3 ⋅ │ ⋅ 7 1 │
│───────#───────#───────│
│ ⋅ ⋅ 7 │ ⋅ ⋅ 3 │ ⋅ ⋅ 2 │
│ ⋅ 8 9 │ 1 7 5 │ ⋅ ⋅ 3 │
│ ⋅ 4 ⋅ │ ⋅ 9 2 │ ⋅ 1 ⋅ │
└───────────────────────┘
julia> solve!(sdk)
1-element Vector{Sudoku}:
Sudoku([7 2 … 3 9; 6 5 … 2 7; … ; 2 8 … 4 3; 3 4 … 1 5], 3, 3)
julia> sdk = generate_sudoku(3, 3, holes_pct=0.5, seed=1, symmetry=true)
[ Info: seed = 1
┌───────────────────────┐
│ ⋅ 2 ⋅ │ ⋅ 1 ⋅ │ ⋅ ⋅ ⋅ │
│ 6 ⋅ ⋅ │ 3 4 9 │ 8 2 ⋅ │
│ ⋅ ⋅ ⋅ │ 2 ⋅ ⋅ │ ⋅ ⋅ ⋅ │
│───────#───────#───────│
│ 8 9 ⋅ │ ⋅ 2 1 │ 5 ⋅ 4 │
│ 1 7 2 │ 6 5 4 │ 3 9 8 │
│ 4 ⋅ 5 │ 9 3 ⋅ │ ⋅ 7 1 │
│───────#───────#───────│
│ ⋅ ⋅ ⋅ │ ⋅ ⋅ 3 │ ⋅ ⋅ ⋅ │
│ ⋅ 8 9 │ 1 7 5 │ ⋅ ⋅ 3 │
│ ⋅ ⋅ ⋅ │ ⋅ 9 ⋅ │ ⋅ 1 ⋅ │
└───────────────────────┘
julia> solve!(sdk)
2-element Vector{Sudoku}:
Sudoku([7 2 … 3 9; 6 5 … 2 7; … ; 2 8 … 4 3; 3 4 … 1 5], 3, 3)
Sudoku([9 2 … 3 6; 6 5 … 2 7; … ; 2 8 … 4 3; 3 4 … 1 5], 3, 3)Enigmistics.solve_guided! — Functionsolve_guided!(cw::CrosswordPuzzle; seed=rand(Int), kwargs...)Fill a crossword puzzle cw using a backtracking algorithm with Minimum Remaining Values (MRV) heuristic, considering all types of moves (simple placements, splits with black cells, enlargements), and allowing the user to have control (i.e. decide) which move to apply at each step.
Arguments are the same as in the solve! function, so look at its documentation for more details.
Example
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> solve_guided!(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 │
└──────────────────┘
Select the moves that you like; or none to backtrack with recursion; or quit)
[press: Enter=toggle, a=all, n=none, d=done, q=abort]
[ ] → quit
[X] [Place] DEER
[ ] [Place] DOPR
[ ] [Place] DURR
[ ] [Place] DIOR
[ ] [Place] DOUR
[ ] [Place] DCOR
[X] [Place] DOOR
> [X] [Place] DYER
v [ ] [Place] DERR
Applying move: [Place] DEER (moves yet to be tested: ["[Place] DOOR", "[Place] DYER"])
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ E ■ A │
3 │ T ■ ⋅ E ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ ⋅ I ⋅ ⋅ ⋅ W │
└──────────────────┘
Select the moves that you like; or none to backtrack with recursion; or quit)
[press: Enter=toggle, a=all, n=none, d=done, q=abort]
[ ] → quit
[ ] [Place] MIDBOW
[ ] [Place] PITSAW
[X] [Place] WINDOW
[ ] [Place] TILNOW
[ ] [Place] KIDROW
> [X] [Place] WINNOW
[ ] [Place] BIGWOW
[ ] [Place] AIMLOW
v [ ] [Place] PITROW
Applying move: [Place] WINDOW (moves yet to be tested: ["[Place] WINNOW"])
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ E ■ A │
3 │ T ■ ⋅ E ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ W I N D O W │
└──────────────────┘
Select the moves that you like; or none to backtrack with recursion; or quit)
[press: Enter=toggle, a=all, n=none, d=done, q=abort]
[ ] → quit
> [X] [Place] BEEN
[X] [Place] SEEN
[ ] [Place] DEEN
[ ] [Place] PEEN
[ ] [Place] WEEN
[X] [Place] TEEN
[ ] [Place] KEEN
Applying move: [Place] BEEN (moves yet to be tested: ["[Place] SEEN", "[Place] TEEN"])
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ E ■ A │
3 │ T ■ B E ⋅ R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ W I N D O W │
└──────────────────┘
Select the moves that you like; or none to backtrack with recursion; or quit)
[press: Enter=toggle, a=all, n=none, d=done, q=abort]
[ ] → quit
> [X] [Place] BEER
[ ] [Place] BEHR
[ ] [Place] BEOR
[X] [Place] BEAR
[ ] [SplitAndPlace] (BE.R → BE/R) BE/nothing
Applying move: [Place] BEER (moves yet to be tested: ["[Place] BEAR"])
true
julia> cw
1 2 3 4 5 6
┌──────────────────┐
1 │ G O L D E N │
2 │ A N ■ E ■ A │
3 │ T ■ B E E R │
4 │ E V E R ■ R │
5 │ ■ I E ■ ■ O │
6 │ W I N D O W │
└──────────────────┘