Closed nicolesimon13 closed 6 months ago
Another idea - load puzzle from json file. This way one can generate a file and later add words to it or work with it again / fix mispelling etc.
Hey, thanks for checking out the package. It is always nice to hear when someone is using it.
First, let me say I have a pretty large update coming that changes a lot of the core code. The functionality doesn't really change, the changes just make the package more extensible. There are changes in the word placement/generation code that cleans things up a bit, and you can also disable/enable word validators (and create your own).
Anyway, now on to your questions... There is a lot to cover so I'll probably tackle it in pieces as I have time.
The WordSearch
object has a show
method, that accepts two arguments solution
and hide_fillers
. If you set solution
to True
like below, the puzzle will be printed with the words highlighted in red (same as passing the -c
flag to the cli).
puzzle= WordSearch("cat bat rat")
puzzle.show(solution=True)
If you set hide_fillers
to True
the puzzle will be output with only the words shown (like below). The solution
argument is ignore if hide_fillers
is set to True
.
---------------------------
WORD SEARCH
---------------------------
B
A
T
C A T
R A T
Find these words: BAT, CAT, RAT
* Words can go SE, E, S, and, NE.
Answer Key: BAT SE @ (4, 3), CAT E @ (2, 6), RAT E @ (8, 8)
Yes. Originally it was called directions
, but was changed to allow for future extensibilty. Bascially, the simple levels keep working right-reading and the higher levels allow for words to be backward. The current options are below.
level_dirs: dict[int, set[Direction]] = {
-1: set(), # no valid directions
1: { # right or down
Direction.E,
Direction.S,
},
2: { # right-facing or down
Direction.NE,
Direction.E,
Direction.SE,
Direction.S,
},
3: { # any direction
Direction.N,
Direction.NE,
Direction.E,
Direction.SE,
Direction.S,
Direction.SW,
Direction.W,
Direction.NW,
},
4: { # no E or S for better hiding
Direction.N,
Direction.NE,
Direction.SE,
Direction.SW,
Direction.W,
Direction.NW,
},
5: { # no E
Direction.N,
Direction.NE,
Direction.SE,
Direction.S,
Direction.SW,
Direction.W,
Direction.NW,
},
7: { # diagonals only
Direction.NE,
Direction.SE,
Direction.NW,
Direction.SW,
},
8: { # no diagonals
Direction.N,
Direction.E,
Direction.W,
Direction.S,
},
}
more answers to come...
There are some substantial changes coming in relation to puzzle words and puzzle generation, so I'll talk about them since the current (<v4.0.0) way will be no more. The changes in a nutshell are, that I have extracted most of the functionality out into separate units because I have had quite a few people wanting to build other "word-based" games with this package. Now, all games derive from a base Game
object. Each Game
can have custom a Generator
for generating the puzzle, a Formatter
for displaying/outputting the puzzle, and custom word Validator
s (e.g. no palindromes, no punctuation, no single letter words, no subwords) for validating puzzle words.
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 a Validator()
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, palindromes, or words that are part of other words, as each of these situations could potentially lead to multiple solutions for the same puzzle. Also, there has been a --no-validators
flag added to the cli to disable default validators.
The key steps:
Generator
starts by trying to place all "regular hidden" (not secret) words in the puzzle. To do this, it picks a random empty starting position on the puzzle and checks if the word can actually fit there going in any of the available directions (level). If so, it picks a random valid direction, and starts placing the word character by character. For every character, the generator checks to make sure that by placing a particular character another word isn't created by coincidence. If all checks pass the word is placed. If not, it tries another valid direction. After it exhausts all valid directions at the random position, a WordFitError is raised and it tries everything again at another random position. This retry continues up to 1000 times. If no feasible position can be found by then the generator skips that word and moves on to the next.I'll continue on later when I have more time... And sorry in advance, I am typing this on my phone...
If you are wanting to ensure 30 words inside of a size 15 (15x15) puzzle, you could use a simple while loop like below...
from word_search_generator import WordSearch
words = "oven microwave fridge knife fork..." # string of 30 words
puzzle = WordSearch(words, size=15)
if puzzle.unplaced_hidden_words:
while puzzle.unplaced_hidden_words:
puzzle._generate()
The generator is pretty good at filling up a puzzle but 30 words in a 15x15 puzzle is a tight fit. Sometime it will fit them all but sometimes it won't so the while loop keeps running the generator until all 30 words are successfully placed.
When trying to fit 100 words (or any number) the generator does a pretty good job of calculating a good size that allows all words to be fit (without being too bunched up). It not 100% but it is pretty good.
If you need to ensure you have exactly 100 words, you could use the same while loop code as above. You would just need to either not set a puzzle size and make it larger than 15.
You can print a generated puzzle using either print
or the show
method.
puzzle= WordSearch("cat bat rat")
print(puzzle)
# or
puzzle.show()
puzzle= WordSearch("cat bat rat")
# puzzle
puzzle.puzzle
# words used
puzzle.placed_words
# directions
puzzle.directions # or p.level
# key
puzzle.key
puzzle= WordSearch("cat bat rat")
puzzle.show(hide_fillers=True)
This is not something that is implemented. You can preview the mask using the show
method.
def show(self, active_only: bool = False) -> None:
"""Pretty print the mask. When `active_only` is True only the masked
areas that lie within the bound of (0,0) and (`puzzle_size`, `puzzle_size`)
will be shown. Used for mask creation and testing.
Args:
active_only (bool, optional): Only output the masked areas
that lie within the bounds of `Mask.puzzle_size`. Used for
mask creation and testing. Defaults to False.
Raises:
MaskNotGenerated: Mask has not yet been generated.
"""
...
I assume you are looking to make crossword style puzzles. This is one of the main reasons while I did all of the extraction I mentioned above (quite a few people have requested crossword puzzles). The best way to go about this would be to write a new custom Crossword generator (and formatter as well). It is something I plan to do in the future, I jsut don't have a timeline for working on it.
This is a new feature (I described in detail in a previous comment) that disables all default word validators for the WordSearch game.
Not implemented but could be created pretty easily with a custom generator and formatter.
Not implemented but could be created pretty easily with a custom generator and formatter. Would honeslty be a bit hacky, since the base game is designed to work with words and letters but could defintely be done.
Not implemented but planned as I mentioned in a previous comment.
This is on my to-do list as it has been requested before. The only thing to note, any adjustments to words will generate an entirely different looing puzzle (meaning words will be in different places).
Link has been updated in the wiki. You can view config.py
using this link as well.
Fixed. Not sure where the -k
came from. You don't need any flags to redirect the puzzle output to a text file.
I think that answers everything you asked. If you have more questions or need help with anything, don't hesitate to ask. Cheers!
thank you for all your answers, will have to work through them!
Another idea - load puzzle from json file.
This is on my to-do list as it has been requested before. The only thing to note, any adjustments to words will generate an entirely different looing puzzle (meaning words will be in different places). I only had a quick look and I thought the json file had the whole puzzle in it?
This retry continues up to 1000 times. If no feasible position can be found by then the generator skips that word and moves on to the next. I have another tool which uses 100k or 200k retries. I am willing to let the thing run in the background for a while. I am not that good at math but based on the remaining empty spots and size of the grid should there not a determined limited number of tries?
And sorry in advance, I am typing this on my phone... people are crazy doing that on the phone! :)
You mentioned the update - any idea when that might be coming? 1 month, 6 months? ;) Also there should not be that many changes for the basic word search generation, right?
Just to clarify what I meant by "The only thing to note, any adjustments to words will generate an entirely different looking puzzle (meaning words will be in different places)."
Anytime a change is made to a puzzle object, add words, remove words, change size, add mask, etc., the puzzle gets regenerated. As you can see below, the simple puzzle completely changes when I add a new word.
from word_search_generator import WordSearch
p = WordSearch("cat dog ant", size=5)
p.show()
# -----------
# WORD SEARCH
# -----------
# X A C F K
# D I U A C
# R D O G T
# E A N T W
# S F J B F
#
# Find these words: ANT, CAT, DOG
# * Words can go SE, E, S, and, NE.
#
# Answer Key: ANT E @ (2, 4), CAT SE @ (3, 1), DOG E @ (2, 3)
p.add_words("rat")
p.show()
# -----------
# WORD SEARCH
# -----------
# P I C A T
# B G G N R
# N O I T A
# D E B S T
# R U W M N
#
# Find these words: ANT, CAT, DOG, RAT
# * Words can go SE, E, S, and, NE.
#
# Answer Key: ANT S @ (4, 1), CAT E @ (3, 1), DOG NE @ (1, 4), RAT S @ (5, 2)
So if you saved out a JSON representation of a puzzle, and then loaded it back in to change a word (like your example) the puzzle "layout" would be regenerated and words would be in different places.
The full puzzle is given in the via the JSON method as seen below.
p.json
{
"puzzle": [
["P", "I", "C", "A", "T"],
["B", "G", "G", "N", "R"],
["N", "O", "I", "T", "A"],
["D", "E", "B", "S", "T"],
["R", "U", "W", "M", "N"]
],
"words": ["RAT", "CAT", "ANT", "DOG"],
"key": {
"DOG": {
"start_row": 3,
"start_col": 0,
"direction": "NE",
"secret": false
},
"ANT": {
"start_row": 0,
"start_col": 3,
"direction": "S",
"secret": false
},
"CAT": {
"start_row": 0,
"start_col": 2,
"direction": "E",
"secret": false
},
"RAT": {
"start_row": 1,
"start_col": 4,
"direction": "S",
"secret": false
}
}
}
This limit is actually pretty high (probably higher than it needs to be). Rarely is this limit hit unless the word just won't fit into the current puzzle. I did some testing and with keeping speed and efficiency in mind, any higher limit just didn't make sense. However, that is the good thing about the new extraction layout of the package. You could easily write a new generator with a much higher limit if you need it.
I'm pretty sure I'll push the update live in the next few weeks. I have a few other things I want to add but nothing that should take a lot of time. And the function of the word search generation is the same as no changes were made there.
Hi Josh, thanks for making this, so far this has been my favorite ws tool! And the first real reason I have for installing python (I am more of awk kinda girl).
I have not worked much yet with the mask but it is awesome! :)
All the following questions are about accessing the tool from inside of python. The one answer I am really looking for is the first one. ;)
How to create the solution from inside python? Maybe I am blind but how do I create the solution sheet? I can see the CLI -c but not how I can call for that in python? Or do you mean by solution your direction thingy? That is helpful for many other things but a typical solution is the filled board without additional letters.
Difficulty Level is just directions? Also I was trying to figure out what determines a hard level versus an easy level? Is it just the direction list? (1)
Used / Unused Words Can you say a bit about how your algo about placing works?
We often want to produce a puzzle with a theme, not just a random word list. Lets say items from the kitchen.
But also always have f.e. 30 words in our 15x15 puzzle. The solution would be to have a 50 word list for the algo to choose from. It is my understanding that at the moment the code tries to place all the words in the submitted file? Is there a way to prepare the word list file to optimize the placement?
to tell it "use 30 words from this file until you have 30 words"? does the algo work better with longer / shorter words? Is there any reason to sort the word file by length?
Another reason I ask about the Algo are the 100 word challenges. For this it needs to be x words and knowing more about what works and what does not would help with this.
Other things I would like to do but dont know how to access them (again all inside, you already have these but I dont know how one would access them)
print out just the puzzle / the words used / answer key / direction list
access the array for each of those items
print out the puzzle but just the words for the solution
print out information for a mask. if I provide a mask, how may words will fit well into this mask?
create the puzzle but fill it with something else for missing letters (f.e. a space or a colored block) why? f.e. the number puzzle below, it makes more sense to block out remaining spaces to avoid duplication problems)
what is this cli option? --no-validators Disable default word validators.
Additional options which probably are already there but I was missing them in the documentation
fiy (1) "You can find all of the preset numeric levels listed in config.py." https://github.com/joshbduncan/word-search-generator/wiki/Puzzle-Object is a dead link. I have yet to find where windows stores the py file so I appreciate it in the documentation.
https://github.com/joshbduncan/word-search-generator/wiki/Puzzle-Object uses this line $ word-search dog, cat, pig, horse -k > puzzle.txt but there is no -k in https://github.com/joshbduncan/word-search-generator/wiki/Command-Line-Interface-(CLI)