Enigmistics
Enigmistics.jl is a Julia package for exploring and analyzing language puzzles, in particular crosswords and wordgames. More precisely, this package provides utilities for
working with crosswords. We can create empty or patterned crosswords, edit them by adding or removing words and black cells, save/load crosswords to/from a file, enlarge/shrink the grid to expand an existing one and, most importantly, we can fill in the words of an empty or partially complete crossword. For now this functionality is available fully automatically, but I plan to implement a semi-automatic version where one can manually filter the to-be-inserted words. Words can be be taken from built-in dictionaries; current supported ones are english and italian.
efficiently finding wordgames in texts; current supported ones are abecedaries, anagrams, heterograms, lipograms, palindromes, pangrams, tautograms. For each of them, two functions are provided: one to check if a given text satisfies the wordgame constraints (e.g. function
is_pangram) and one to find all substrings from a given text which satisfy the wordgame constraints (e.g. functionscan_for_pangrams).
Crosswords use case
The most relevant utility of this package is automatic generation of crosswords. We can create a pattern of black cells either manually or through one of the implemented patterns functions, and then let the algorithm do the "hard work". This filling process can be strict, preserving the original structure of the crossword grid, or relaxed, allowing in this case the algorithm to perform some modifying actions (introducing new black cells, enlarging the size of the grid) if such moves can be useful.
This strict or relaxed choice can be easily set by setting appropriately the min_options_to_allow_split argument of the solve! function, as we can see in this quick video example. The possibility to enlarge the grid can also be granted by setting accordingly the other argument min_options_to_allow_enlarge.
Alternatively, we can also proceed in a semi-automatic approach by performing some manual steps. For example, we can create a crossword from a striped pattern:
julia> cw = striped_pattern(6, 8, seed=-53, min_stripe_dist=5)
1 2 3 4 5 6 7 8
┌────────────────────────┐
1 │ ■ . . . . . ■ . │
2 │ . . . . . ■ . . │
3 │ . . . ■ . . . . │
4 │ . . . . ■ . . . │
5 │ . . ■ . . . . . │
6 │ . ■ . . . . . ■ │
└────────────────────────┘maybe add some words of our choice:
julia> place_word!(cw, "Julia", 1, 8, :v)
true
julia> place_word!(cw, "Code", 4, 1, :h)
true
julia> cw
1 2 3 4 5 6 7 8
┌────────────────────────┐
1 │ ■ . . . . . ■ J │
2 │ . . . . . ■ . U │
3 │ . . . ■ . . . L │
4 │ C O D E ■ . . I │
5 │ . . ■ . . . . A │
6 │ . ■ . . . . . ■ │
└────────────────────────┘possibly saving it to a file, so that if we want to fill it in automatically we have a "checkpoint" from which we can try different seeds for the words filling algorithm:
julia> save_crossword(cw, "ex_docs.txt")and finally fill it automatically, obtaining something like this:
julia> solve!(cw, min_options_to_allow_split=0); cw
1 2 3 4 5 6 7 8
┌────────────────────────┐
1 │ ■ D R A W S ■ J │
2 │ M A I D Y ■ P U │
3 │ U G A ■ N A S L │
4 │ C O D E ■ C P I │
5 │ H N ■ P R A G A │
6 │ O ■ J A R N O ■ │
└────────────────────────┘Wordgames use case
Suppose we are a fan of John Milton's Paradise Lost poem and we would like to inspect it to find some interesting wordgames. We can start by loading the book through the clean_read function, which loads the text removing useless newlines or spaces which could reduce the clarity of the output from the wordgames scanning functions:
julia> text = clean_read("../Enigmistics/texts/paradise_lost.txt", newline_replace="/")
"PARADISE LOST / BOOK I. / Of Mans First Disobedience, and the Fruit / Of that Forbidden Tree, whose mortal tast / Brought Death into the World, and all our woe, / With loss of EDEN, till one greater Man / Restore us, and regain the blissful Seat, / Sing Heav'nly Muse, that on the secret top / Of OREB, or of SINAI, didst inspire / That Shepherd, who first taught the chosen Seed, / In the Beginning how the Heav'ns and Earth / Rose out of CHAOS: Or if SION Hill / Delight thee more, and SILOA" ⋯ 474211 bytes ⋯ "iff as fast / To the subjected Plaine; then disappeer'd. / They looking back, all th' Eastern side beheld / Of Paradise, so late thir happie seat, / Wav'd over by that flaming Brand, the Gate / With dreadful Faces throng'd and fierie Armes: / Som natural tears they drop'd, but wip'd them soon; / The World was all before them, where to choose / Thir place of rest, and Providence thir guide: / They hand in hand with wandring steps and slow, / Through EDEN took thir solitarie way. / THE END."We can start by looking for pangrams, i.e. sequence of words which contain all the letters of the alphabet. We can do it by calling the scan_for_pangrams function, which takes as input the text to be scanned and some optional parameters to filter the output (e.g. maximum length in letters, language, etc):
julia> scan_for_pangrams(text, max_length_letters=80, language="en")
1-element Vector{Any}:
(21698:21804, "Grazed Ox, / JEHOVAH, who in one Night when he pass'd / From EGYPT marching, equal'd with one stroke / Both")It seems that there is only one "interesting" (in the sense of not being too long) pangram in the whole text. Nice.
In a similar fashion we can now look for other wordgames. For example: are there sequences of words
- which all start with the same letter?
julia> scan_for_tautograms(text, min_length_words=5, max_length_words=20)
6-element Vector{Any}:
(20801:20830, "and ASCALON, / And ACCARON and")
(110257:110281, "Topaz, to the Twelve that")
(136170:136194, "to taste that Tree, / The")
(320005:320030, "her Husbands hand her hand")
(450274:450301, "Though to the Tyrant thereby")
(456113:456141, "Through the twelve Tribes, to")- where their initials are in alphabetical order?
julia> scan_for_abecedaries(text, min_length_words=4, max_length_words=5, language="en")
3-element Vector{Any}:
(102463:102490, "a boundless Continent / Dark")
(368827:368846, "raging Sea / Tost up")
(405485:405502, "and both confess'd")- where all letters are different?
julia> scan_for_heterograms(text, min_length_letters=15)
8-element Vector{Any}:
(16997:17015, "worth / Came singly")
(106504:106524, "of LUZ, / Dreaming by")
(113218:113235, "works, but chiefly")
(113218:113239, "works, but chiefly Man")
(142414:142431, "works, and chiefly")
(229224:229240, "and briefly touch")
(277449:277465, "lead thy ofspring")
(369900:369917, "scourg'd with many)- where letters E and T do not appear?
julia> scan_for_lipograms(text, "ET"; min_length_letters=34, max_length_letters=100)
8-element Vector{Any}:
(65052:65094, "foul in many a scaly fould / Voluminous and")
(143410:143454, "by morrow dawning I shall know. / So promis'd")
(242542:242583, "Rowld inward, and a spacious Gap disclos'd")
(442481:442523, "by his command / Shall build a wondrous Ark")
(442481:442527, "by his command / Shall build a wondrous Ark, as")
(442484:442527, "his command / Shall build a wondrous Ark, as")
(451977:452024, "God, who call'd him, in a land unknown. / CANAAN")
(457966:458011, "From ABRAHAM, Son of ISAAC, and from him / His")and so on.