-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement shape masks #20
Comments
Before I do any more work on this, what do you think of my Puzzle object and approach to the modification functions? My gut feeling suggests that there is some far easier way to wrap a |
Hey, I was out of town last weekend so I haven't had the time to review this. I definitely like the idea so I'll try to take a look at the implementation this weekend and get back to you. Thanks! |
Take your time: I’ll be traveling this weekend, so I won’t have time to poke at this again until next week. Once we decide on the data structure and filter function strategy, it should mostly be a straightforward plug-and-chug for me to update the existing generate.py code to use the new functionality and implement several more built-in filters to be available.
When you get to look at this, let me know if you think of any CLI ergonomics. As of now, I do not plan to implement CLI access to this functionality, but I have nothing against adding it if you have a suggestion ready by the time I start trying to implement this for real. If you instead implement the CLI for this after I’ve submitted my initial PR, that’s fine by me. My initial guess says that this feature set is more relevant when using the generator as a library rather than as an app.
— Chris
…On Oct 13, 2022, 10:33 -0400, Josh Duncan ***@***.***>, wrote:
Hey, I was out of town last weekend so I haven't had the time to review this. I definitely like the idea so I'll try to take a look at the implementation this weekend and get back to you. Thanks!
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
Yes, I agree about the CLI. Maybe it's something we can implement later but I think it's best to just get everything working in the API. |
This is a bit of a thought dump for when I go to do the implementation. Feel free to ignore this if you don't find it useful when giving my demo code an in-depth review. Data Structure of PuzzleFor 90% of what I want to do, Properties or just additional tasks for the setter?I highly doubt this would become a performance-sensitive issue. However, I'm unfamiliar with how Python caches its @properties—would they need to be recomputed every time they're accessed or just each time they've been accessed after the Puzzle has changed?
Structure of filter functionsI'm going to change up the signature of these functions. Namely, instead of some ad-hoc boolean about whether to blackout or clear the selection, separate out the selections and effects into separate functions. Typing this out, probably best to use the effect function as a param in the pattern function. Below are the four effect functions I've thought of. def mark_oob(_: chr) -> chr:
return config.OOB_CHR
def mark_clear(_: chr) -> chr:
return ""
def toggle_cell(c: chr) -> chr:
return "" if c == config.OOB_CHR else config.OOB_CHR
def random(c: chr, effect: Optional[Callable[[chr], chr]] = None, strength: float = 0.5) -> chr:
if not effect:
effect = random.choice([mark_oob, mark_clear, toggle_cell])
return effect(c) if random.random() < strength else c
List of patterns to implement:
There were probably some other items, but I started typing this over my lunch break on Friday and then returned to it after work this evening, so there's plenty I've forgotten. |
@duck57, if we are going to put in the work to build this out I want to clean up a few things to make the module a bit more robust before we start. So far, I've already done the work to create a word class that tracks the text, position, coordinates (for potential use), xy position for display. So far, it's def better and just keeping a set of words and a separate key. I just need to finish implementing this into the function. I'm also cleaning up the utility and generate function to use the actual puzzle object so that we don't have to pass around so many variables. I just pass the puzzle object and extract the variable I need to do the work from there. Next, I'm going to clean up the actual puzzle generator function. I bolted on checks as I implemented them so I need to go back in and clean things up. I hope to have this all done in the next few days. |
I see you've made some of the changes already. I've done similar refactors (RE: moving things to a class because of excessively redundant function signatures) on my own projects before. Is the plan to finish the cleanup and then merge #17? If so, I'll wait until after that has been merged to explore more so there will be a stable foundation to build from. |
Exactly! I've got the work done for creating the word class object and will commit it tomorrow. I think that's the last thing I'm going to squeeze into that PR. I wanted to have a more solid foundation and easier extensibility before we work to add these advanced features. It would be even harder to make the switch later. |
For the sake of consistent nomenclature, should this feature be called "masks" or "filters"? |
@duck57, I like masks. I feel like more people would understand that over filer. |
I would make them methods of the Puzzle object since they only interact with a "puzzle". |
I've never actually used functools.partial in a working program ( just doesn't come to mind)... But yes, it could certainly help to reduce nesting and clean up a function. |
So, on your proof of concept, I see that masks are applied by expanding the puzzle. What is your plan for making that work with puzzle size? Say I supply a puzzle size of 10h x 20w like below with some random masks. >>> p = Puzzle(
10,
20,
masks=[
expand(0, 4, True),
expand(0, -7, True),
expand(-3, 0, False),
expand(3, 0, False),
expand(0, 2, False),
expand(0, -2, False),
],
)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # . .
. . # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # . .
. . # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # . .
. . # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # . .
. . # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # . .
. . # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # . .
. . # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # . .
. . # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # . .
. . # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # . .
. . # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . If I now call p.width or p.height the values don't match what I originally set. >>> p.height
16
>>> p.width
35
>>> print(p) I understand what is happening but do you think the user will? Should we "remove" the masked areas from the actual puzzle size they specify like below?
Your original "I" shaped example wouldn't work in this case though as you are masking out 8 cols and the puzzle size is only 6 cols. This could throw the exception you have set up. But if you reduced the masks size to 2 cols and could control the height you would end up with the following... >>> print(
Puzzle(
6,
masks=[
remove_rectangle(row=2, col=0, width=2, height=2, True),
remove_rectangle(row=2, col=4, width=2, height=2, True),
],
)
)
. . . . . .
. . . . . .
# # . . # #
# # . . # #
. . . . . .
. . . . . . Just some thoughts as I try and wrap my head around the implementation... |
And I don't think it would be too hard to implement a bitmap mask using PIL... |
@duck57, FYI, I had to patch a small booboo so I just published v2.0.1. My check for all of the placed words properties when checking to see if the word had a 'position'. Well the way we have position setup it always return true making all words show up in those properties and the key no matter if they were placed on the board or not. Was easy to notice when I tried to fit 100 words in a 5x5 puzzle. 🤦♂️ |
I just pushed a commit to my fork's This commit (or series of commits, rather):
Some to-do items [this list is as much for me as for you]:
To address some of your comments,
If I have unexpected free time this weekend or early next week, I may take a stab at addressing some of the to-do items. If not, it can be a project for me in the spring. Feel free to use my chicken scratch as the base for your implementation if you want if you don't want to wait until later February. |
@duck57, so after talking it over with a few friends, I wanted to take a go at creating a different approach for masking. I'm a graphic designer by trade so masks are something I use a lot and this approach just works better with my brain. It needs some cleanup and has zero type hints at the moment but it should be pretty easy to understand. I have included a pretty extensive readme below. I plan to add more shapes, probably recalculate the heart, and check on some rounding issues, but most of the base is there. The bitmap mask could easily be expanded to work with PIL (which is already a requirement of the PDF generator) and allow user images to work as masks. This could pretty easily be implemented into the actual package. I'm on my lunch break and rambling at this point, so check it out if you have time, and let me know what you think. https://gist.github.com/joshbduncan/949caf7a6d0ae6d9d2ada564a0562f4f Puzzle MasksMasks allow you to "mask" areas of a WordSearch puzzle, making those areas inactive for placing characters. Mask() Base ClassAll puzzle masks are based on the base def __init__(self, method=1, static=True):
"""A puzzle mask object.
Args:
method (int, optional): Masking method. Defaults to 1.
1. Standard (Intersection)
2. Additive
3. Subtractive
static (bool, optional): Mask should not be recalculated
and reapplied after a size change. Defaults to True.
"""
self.puzzle_size = None
self.grid = None
self.method = method
self.static = static The base mask class only has a few key properties, Mask().method
To best understand >>> mask = Diamond()
>>> mask.generate(11)
>>> mask.show()
# # # # # * # # # # #
# # # # * * * # # # #
# # # * * * * * # # #
# # * * * * * * * # #
# * * * * * * * * * #
* * * * * * * * * * *
# * * * * * * * * * #
# # * * * * * * * # #
# # # * * * * * # # #
# # # # * * * # # # #
# # # # # * # # # # # As you can see in the output above, a mask (no matter the type) is made up of Method Types
>>> p = Puzzle(15)
>>> p.apply_mask(Ellipse(15, 7))
>>> p.show()
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # * * * * * * * # # # #
# * * * * * * * * * * * * * #
* * * * * * * * * * * * * * *
* * * * * * * * * * * * * * *
* * * * * * * * * * * * * * *
# * * * * * * * * * * * * * #
# # # # * * * * * * * # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
>>> p.apply_mask(Ellipse(7, 15, method=2))
>>> p.show()
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # * * * * * * * # # # #
# # # # * * * * * * * # # # #
# # # # * * * * * * * # # # #
# # # # * * * * * * * # # # #
# # # # * * * * * * * # # # #
# # # # * * * * * * * # # # #
# # # # * * * * * * * # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # Using the default standard method (
>>> p = Puzzle(15)
>>> p.apply_mask(Ellipse(15, 7))
>>> p.show()
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # * * * * * * * # # # #
# * * * * * * * * * * * * * #
* * * * * * * * * * * * * * *
* * * * * * * * * * * * * * *
* * * * * * * * * * * * * * *
# * * * * * * * * * * * * * #
# # # # * * * * * * * # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
>>> p.apply_mask(Ellipse(7, 15, method=2))
>>> p.show()
# # # # # # * * * # # # # # #
# # # # # * * * * * # # # # #
# # # # # * * * * * # # # # #
# # # # # * * * * * # # # # #
# # # # * * * * * * * # # # #
# * * * * * * * * * * * * * #
* * * * * * * * * * * * * * *
* * * * * * * * * * * * * * *
* * * * * * * * * * * * * * *
# * * * * * * * * * * * * * #
# # # # * * * * * * * # # # #
# # # # # * * * * * # # # # #
# # # # # * * * * * # # # # #
# # # # # * * * * * # # # # #
# # # # # # * * * # # # # # # Using the additive method (
>>> p = Puzzle(15)
>>> p.apply_mask(Ellipse(15, 7))
>>> p.show()
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # * * * * * * * # # # #
# * * * * * * * * * * * * * #
* * * * * * * * * * * * * * *
* * * * * * * * * * * * * * *
* * * * * * * * * * * * * * *
# * * * * * * * * * * * * * #
# # # # * * * * * * * # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
>>> p.apply_mask(Ellipse(7, 15, method=3))
>>> p.show()
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# * * * # # # # # # # * * * #
* * * * # # # # # # # * * * *
* * * * # # # # # # # * * * *
* * * * # # # # # # # * * * *
# * * * # # # # # # # * * * #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # Using the subtractive method ( Mask().staticA The reason for this property, is there are many Preset Masks that are calculated based on the puzzle size. These masks will easily scale if you change the puzzle size. But a problem arises when you create a custom Bitmap or Polygon mask that can't be easily recalculated to fit on a different puzzle size. In this case ( If you would like to remove all static masks from Masks can be applied to a Mask() Methods
Mask Shape CenteringPlease note, anytime a mask shape with a calculated center (Triangle, Diamond, Ellipse, Star, Heart) is applied to a puzzle with an even
>>> p = Puzzle(9)
>>> p.apply_mask(Ellipse(8, 4))
>>> p.show()
# # # # # # # # #
# # # # # # # # #
# * * * * * * # #
* * * * * * * * #
* * * * * * * * #
# * * * * * * # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
>>> p = Puzzle(10)
>>> p.apply_mask(Ellipse(9, 5))
>>> p.show()
# # # # # # # # # #
# # # # # # # # # #
# # * * * * * # # #
* * * * * * * * * #
* * * * * * * * * #
* * * * * * * * * #
# # * * * * * # # #
# # # # # # # # # #
# # # # # # # # # #
# # # # # # # # # # Preset MasksCurrent preset masks:
Bitmap MasksBitmap masks work similarly to bitmap images. Every point (grid square) specified in the Masks that inherit from Bitmap: EllipseDraw an ellipse at the specified >>> p = Puzzle(20)
>>> p.apply_mask(Ellipse(18,10))
>>> p.show()
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # * * * * * * * * # # # # # #
# # # # * * * * * * * * * * * * # # # #
# # * * * * * * * * * * * * * * * * # #
# * * * * * * * * * * * * * * * * * * #
# * * * * * * * * * * * * * * * * * * #
# * * * * * * * * * * * * * * * * * * #
# * * * * * * * * * * * * * * * * * * #
# # * * * * * * * * * * * * * * * * # #
# # # # * * * * * * * * * * * * # # # #
# # # # # # * * * * * * * * # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # CircleDraw a circle that fills the entire puzzle. And, since a circle is just an ellipse with width == height, the >>> p = Puzzle(10)
>>> p.apply_mask(Circle())
>>> p.show()
# # # * * * * # # #
# * * * * * * * * #
# * * * * * * * * #
* * * * * * * * * *
* * * * * * * * * *
* * * * * * * * * *
* * * * * * * * * *
# * * * * * * * * #
# * * * * * * * * #
# # # * * * * # # # 🍩 Donuts anyone? >>> p = Puzzle(21)
>>> e1 = Ellipse(21, 21)
>>> e2 = Ellipse(9, 9, method=3)
>>> p.apply_masks([e1, e2])
>>> p.show()
# # # # # # # * * * * * * * # # # # # # #
# # # # # * * * * * * * * * * * # # # # #
# # # # * * * * * * * * * * * * * # # # #
# # # * * * * * * * * * * * * * * * # # #
# # * * * * * * * * * * * * * * * * * # #
# * * * * * * * * * * * * * * * * * * * #
# * * * * * * * # # # # # * * * * * * * #
* * * * * * * # # # # # # # * * * * * * *
* * * * * * # # # # # # # # # * * * * * *
* * * * * * # # # # # # # # # * * * * * *
* * * * * * # # # # # # # # # * * * * * *
* * * * * * # # # # # # # # # * * * * * *
* * * * * * # # # # # # # # # * * * * * *
* * * * * * * # # # # # # # * * * * * * *
# * * * * * * * # # # # # * * * * * * * #
# * * * * * * * * * * * * * * * * * * * #
# # * * * * * * * * * * * * * * * * * # #
# # # * * * * * * * * * * * * * * * # # #
# # # # * * * * * * * * * * * * * # # # #
# # # # # * * * * * * * * * * * # # # # #
# # # # # # # * * * * * * * # # # # # # # Polygon MasksPolygon masks accept a list of at least 3 points. During mask generation those points will be connected using the Bresenham's line algorithm, then the shape will be filled using the >>> p = Puzzle(11)
>>> polygon = Polygon([(1,1), (7,4), (2,9)])
>>> p.apply_mask(polygon)
# # # # # # # # # # #
# * * # # # # # # # #
# * * * * # # # # # #
# * * * * * * # # # #
# * * * * * * * # # #
# # * * * * * # # # #
# # * * * * # # # # #
# # * * * # # # # # #
# # * * # # # # # # #
# # * # # # # # # # #
# # # # # # # # # # # Masks that inherit from Polygon RectangleDraw a rectangle mask from 4 points. The points should be specified as a list of (x, y) tuples. The default origin point of (0, 0) is at the top-left of the puzzle. >>> p = Puzzle(11)
>>> p.apply_mask(Rectangle(5,7))
>>> p.show()
* * * * * * * # # # #
* * * * * * * # # # #
* * * * * * * # # # #
* * * * * * * # # # #
* * * * * * * # # # #
# # # # # # # # # # #
# # # # # # # # # # #
# # # # # # # # # # #
# # # # # # # # # # #
# # # # # # # # # # #
# # # # # # # # # # # You can also specify a specific (x, y) origin >>> p = Puzzle(11)
>>> p.apply_mask(Rectangle(5,7, position=(3,4)))
>>> p.show()
# # # # # # # # # # #
# # # # # # # # # # #
# # # # # # # # # # #
# # # # * * * * * * *
# # # # * * * * * * *
# # # # * * * * * * *
# # # # * * * * * * *
# # # # * * * * * * *
# # # # # # # # # # #
# # # # # # # # # # #
# # # # # # # # # # # TriangleDraw a triangle that fills the entire puzzle.
>>> p = Puzzle(11)
>>> p.apply_mask(Triangle())
>>> p.show()
# # # # # * # # # # #
# # # # # * * # # # #
# # # # * * * # # # #
# # # # * * * * # # #
# # # * * * * * # # #
# # # * * * * * * # #
# # * * * * * * * # #
# # * * * * * * * * #
# * * * * * * * * * #
# * * * * * * * * * *
* * * * * * * * * * * DiamondDraw a diamond that fills the entire puzzle.
>>> p = Puzzle(10)
>>> p.apply_mask(Diamond())
>>> p.show()
# # # # * # # # # #
# # # * * * # # # #
# # * * * * * * # #
# * * * * * * * * #
* * * * * * * * * *
# * * * * * * * * #
# # * * * * * * # #
# # * * * * * # # #
# # # * * * # # # #
# # # # * # # # # #
>>> p = Puzzle(11)
>>> p.apply_mask(Diamond())
>>> p.show()
# # # # # * # # # # #
# # # # * * * # # # #
# # # * * * * * # # #
# # * * * * * * * # #
# * * * * * * * * * #
* * * * * * * * * * *
# * * * * * * * * * #
# # * * * * * * * # #
# # # * * * * * # # #
# # # # * * * # # # #
# # # # # * # # # # # If you want to generate an Equilateral Diamond (equal sides, with opposing sides parallel to each other) no matter the StarThe Star or Pentagram is regular is a regular 5-pointed star polygon. The points for the star are calculated using the >>> p = Puzzle(13)
>>> p.apply_mask(Heart())
>>> p.show()
# # # # # # * # # # # # #
# # # # # # * # # # # # #
# # # # # * * * # # # # #
# # # # # * * * # # # # #
* * * * * * * * * * * * *
# * * * * * * * * * * * #
# # # * * * * * * * # # #
# # # * * * * * * * # # #
# # # * * * * * * * # # #
# # # * * * # * * * # # #
# # * * * # # # * * * # #
# # * # # # # # # # * # #
# # # # # # # # # # # # # The star will fill as much of the puzzle as possible and can be rotated. >>> p = Puzzle(13)
>>> p.apply_mask(Heart(rotation=30))
>>> p.show()
# # # # # # # # # # # # #
# # # * # # # # # # # # #
# # # * * # # # # * * # #
# # # * * * # * * * # # #
# # # # * * * * * * # # #
# # # * * * * * * # # # #
# * * * * * * * * * # # #
* * * * * * * * * * * # #
# # # # * * * * * * * * #
# # # # * * * # # # # # #
# # # # # * * # # # # # #
# # # # # * # # # # # # #
# # # # # * # # # # # # # Heart>>> p = Puzzle(13)
>>> p.apply_mask(Heart())
>>> p.show()
# # * * # # # # # * * # #
# * * * * # # # * * * * #
# * * * * * # * * * * * #
* * * * * * * * * * * * *
* * * * * * * * * * * * *
* * * * * * * * * * * * *
* * * * * * * * * * * * *
# * * * * * * * * * * * #
# # * * * * * * * * * # #
# # # * * * * * * * # # #
# # # # * * * * * # # # #
# # # # # * * * # # # # #
# # # # # # * # # # # # # ConvexPolygonDraw a regular Convex Polygon mask with 3 or more sides. All points are calculated from the puzzle center and cover as much of the available puzzle area as possible. All ConvexPolygon masks accept two parameters, Masks that inherit from ConvexPolygon: * Are calculated as Regular Polygons EquilateralTriangleA Triangle in which all 3 sides have the same length and all three internal angles are also congruent to each other at 60°. # # # # # # * # # # # # #
# # # # # * * * # # # # #
# # # # # * * * # # # # #
# # # # * * * * * # # # #
# # # # * * * * * # # # #
# # # * * * * * * * # # #
# # # * * * * * * * # # #
# # * * * * * * * * * # #
# # * * * * * * * * * # #
# * * * * * * * * * * * #
# # # # # # # # # # # # #
# # # # # # # # # # # # #
# # # # # # # # # # # # # ♺ Want it rotated? >>> p = Puzzle(13)
>>> p.apply_mask(EquilateralTriangle(45))
>>> p.show()
# # # # # # # # # # # # #
# # # # # # # # # # # # #
# # * * * # # # # # # # #
# # * * * * * * * * # # #
# # * * * * * * * * * * *
# # # * * * * * * * * * #
# # # * * * * * * * * # #
# # # * * * * * * * # # #
# # # * * * * * * # # # #
# # # * * * * * # # # # #
# # # # * * * # # # # # #
# # # # * * # # # # # # #
# # # # * # # # # # # # # EquilateralDiamondA Diamond (rotated square) with 4 equal sides. # # # # # # * # # # # # #
# # # # # * * * # # # # #
# # # # * * * * * # # # #
# # # * * * * * * * # # #
# # * * * * * * * * * # #
# * * * * * * * * * * * #
* * * * * * * * * * * * *
# * * * * * * * * * * * #
# # * * * * * * * * * # #
# # # * * * * * * * # # #
# # # # * * * * * # # # #
# # # # # * * * # # # # #
# # # # # # * # # # # # # PentagonA simple Pentagon with 5 equal sides. # # # # # # * # # # # # #
# # # # * * * * * # # # #
# # # * * * * * * * # # #
# * * * * * * * * * * * #
* * * * * * * * * * * * *
* * * * * * * * * * * * *
# * * * * * * * * * * * #
# * * * * * * * * * * * #
# * * * * * * * * * * * #
# * * * * * * * * * * * #
# # * * * * * * * * * # #
# # * * * * * * * * * # #
# # # # # # # # # # # # # HexagonA [simple Hexagon] # # # # # # * # # # # # #
# # # # * * * * * # # # #
# # * * * * * * * * * # #
# * * * * * * * * * * * #
# * * * * * * * * * * * #
# * * * * * * * * * * * #
# * * * * * * * * * * * #
# * * * * * * * * * * * #
# * * * * * * * * * * * #
# * * * * * * * * * * * #
# # * * * * * * * * * # #
# # # # * * * * * # # # #
# # # # # # * # # # # # # Octagon# # # # # * * # # # # # #
# # # * * * * * * # # # #
# # * * * * * * * * * # #
# # * * * * * * * * * * #
# * * * * * * * * * * * #
# * * * * * * * * * * * *
* * * * * * * * * * * * *
* * * * * * * * * * * * #
# * * * * * * * * * * * #
# * * * * * * * * * * # #
# # * * * * * * * * * # #
# # # # * * * * * * # # #
# # # # # # * * # # # # #
❗️ Please note, there seems to be an off-by-1 miscalculation with the Octagon. It could be a rounding issue but I'm not sure at the moment. Hopefully, I can sort it out later. |
Some Fun Puzzle MasksI had a few free minutes tonight so I made some fun mask shapes. It's really easy to make these. I could easily create a library of them. Donut 🍩>>> p = Puzzle(21)
>>> p.apply_mask(Ellipse(21, 21))
>>> p.apply_mask(Ellipse(9, 9, method=3))
S W X O M K B
T C G B J G K B R G Y
Q C O Q T M O H B W R T L
M C B G X K L G D Z K X O M F
H O C T W Q F H Q J R U G Z F L H
T R W D R V R B B R B F E V E J B O U
T A J U W B Z L J M F B A I
C E G U H U L F O I T K P R
X U O A F A F F K G Y Y
L M O R I U K T W Z J H
V F Y N J V B N A D B V
W Y Y G H E C A L Z T S
R R R P Y N H H S G Z Z
L V C C F D B W T X C Z F C
F R Q O I P X T G B Y I H Z
Y I F C A H Q Z E Z R F V Z H D I C M
M C J V E Z H M F X N L G T R J W
J O L T T G T V R J A T F A G
K M H Y X D O C D L A J C
E C O P D X M J F J R
O I R J O J A Smiley Face>>> p = Puzzle(21)
>>> p.apply_mask(Rectangle(2,6, position=(6,4), method=3))
>>> p.apply_mask(Rectangle(2,6, position=(13,4), method=3))
>>> p.apply_mask(Rectangle(9,2, position=(6,14), method=3))
>>> p.apply_mask(Rectangle(2,2, position=(5,13), method=3))
>>> p.apply_mask(Rectangle(2,2, position=(14,13), method=3))
P T X L S G P
J N Y H M I I N A Z T
Y F W D U S N I D V S J J
C P X T K Z Z R L L H S H D E
Y B M X B S V J I A S Q P
N Z R Z M A L P R O I U S P H
L U L N L K H E Y H E O D Q D
S Q D I V O B P E E C O H D Y C J
B R B L I Y K K P K J T C I E L N
Y S Z T Q S Y Q P M G C M Q R N U
D J X A R V U F O R J H K D E N R E F N T
R Q C K N F F O L I S W A X K L U S U G D
A T B B J C O W P K U Q Q E U H Z Y T Y K
H O W J R C D F S P O O W K S H S
O Q M C B L O L
G A M A B F S N G K
Q B A D X E I Q M D H B P A L E V
J L B E Q I Q T Y H V V Q O Y
X U G E T H A S P P G C T
Q G N L V Y S S S Q J
V R R R B E D Tree>>> p = Puzzle(15)
>>> p.apply_mask(EquilateralTriangle())
>>> p.apply_mask(Rectangle(3,3, position=(6,12), method=2))
V
D E Y
M G E
U G K W R
F N B W D
Z J Q Y K P R
P E P A W X S
Z L K Q N P N T W
U D B M Z H R N I
I H Z V T X I H F I W
A E D X N B E U S V U
S Y K D R A T C V M L G K
F I Q
A A S
U Z M Six Pointed Star>>> p = Puzzle(21)
>>> p.apply_mask(EquilateralTriangle())
>>> p.apply_mask(EquilateralTriangle(rotation=180))
D
S H L
E O Y
E L L L H
A Z X C F
L Z W O J Y P F Y L Q C D Y R F O C E
O S F T K K R B P C X O O D O Y O
A B T K K L T X B J K J E Q T O Q
I G F P S O E O J X N C P W T
B Q P W G T S G L H N O O U K
D E E E W B H O Z J U D R
B X E M O K U F X A V Z T X D
H I D R C R B R S M R I F P R
G O H W N G Y P A J M D F F K K B
L B F T V R D T V V Q D G B U N C
R T M N X B H I I M I Q G X Z C Y D A
H S U L N
G Y E Y S
R Q P
G S E
N Cross>>> p = Puzzle(15)
>>> p.apply_mask(Rectangle(3,15, position=(6,0)))
>>> p.apply_mask(Rectangle(15,3, position=(0,6), method=2))
>>> p.invert_masking()
M B K U J J E E P O Z I
B K G V S K Q F S J H Z
Y G X S F P T E C V L Y
K T S R P B K C D N D Z
F U K Q N W P R K H C D
Q W L F W O R T A H J E
X K S C G E O U N J K K
R P F O U Y J L J L K H
L A P O A R K G U P C V
T J C Y K W U M W A J Y
I P M V T T I U Y G S K
Y J J R T V B T M L O D Inverted Cross In Circlep = Puzzle(21)
p.apply_mask(Circle())
p.apply_mask(Rectangle(31,3, position=(0,9), method=3))
p.apply_mask(Rectangle(3,31, position=(9,0), method=3))
H N S B
G K I F U C F V
K J F Z S R S H X U
O X C N D U L R Z O H K
I P P Q I L I Z H N A B I A
C M O P B P T E W A I P Y P I E
A P B S V D S J X N U E G O A M
P I O J D K T Z A D T K U A B I T I
S K S T G O Z M W I L E D T N J C T
L L N B I I Z W J M N Z H P K A O D
H I E Z E Y W A Q L N M Y L Y M O X
T G R W M C K D M E T I D P Q N
J M K K X U C Z X E G A X B B O
N U W C X M G U C L G F S Y
F M K C L S I M M V D L
A R G I B R A J I Q
L C X P S M P I
F U Q K "Hole-y" Cross 😉>>> p = Puzzle(21)
>>> p.apply_mask(Rectangle(7,7, method=3))
>>> p.apply_mask(Rectangle(7,7, position=(14,0), method=3))
>>> p.apply_mask(Rectangle(7,7, position=(0,14), method=3))
>>> p.apply_mask(Rectangle(7,7, position=(14,14), method=3))
>>> p.apply_mask(Ellipse(5,5, method=3))
K D G N K C N
S S H U S S K
S C H L N I N
M G A J U G E
C U Y U M H B
M R V I Z I R
I O Q Y H P J
K D G P X H X O K Q U R F K W K J U I A X
M F Y N J Y U T M M C Q T F E T Q T
C C H O E J U S I G L J U E Y E
E R I E T G S K K J C D Y H C Y
Z F D J N F I Y M X I C T Q R L
B Y H N D H P A E O X C C N O G L M
W A H A O H F Z D L Y Z U W E Q T G C Y G
X K A R X L P
H T T F I A F
I P M K V G A
G D Z S O Y G
T J X F P W O
Y S V E R X T
R F E Q U S B Checkboard>>> p = Puzzle(30)
>>> p.invert_masking()
>>> for x in range(0, 30, 5):
>>> for y in range(0, 30, 5):
>>> if (x//5) % 2 == 0 and (y//2) % 5 == 0:
>>> p.apply_mask(Rectangle(5,5, position=(x,y), method=2))
>>> if (x//5) % 2 != 0 and (y//2) % 5 != 0:
>>> p.apply_mask(Rectangle(5,5, position=(x,y), method=2))
O B W Z O B R W U B C J M N G
N J K D Y U X U I J K N W J I
E U R S H H R A A F J F L A F
Q T F U A O B E O H N Q L S O
I O S X N I A H N U K M U Z M
N D J W K W W W F S G Q Q M Y
D Q E F K O I L Z F J U T V S
E B B M Y O F Y B C V T Y Z K
B T I Y M O N A L C M T T A M
F W P I U R V E E R P J I F E
A P W M Q K M U J V I C S A B
K G I S H V P H O S V J I X A
K K B M F X X Y P J D S O H P
R D I R B V Z S E M X W U A F
E D G T O Q Z T L J E G T Y W
T X E H E H M F T N Q X A C T
A T H C Q J D B F V E A U M X
D S H S A A M U N E V J Q C C
E V H W T U O M T I A Q X T D
I L U Y M S P P A C R W X B Q
V T C F W Z D Y N K F P I K S
B C D I U D M I P Y P C G H T
O P F E U Q K R J Q M Q Y H Z
J Z D H Q L L G W G R N L V I
Z Q J S Z M I G Z P K X W D C
Y L U H I M R D J O K G Y H U
M L A U T E D P X Y N W Q V U
V P O N X T T H C S W J B A R
C U I O E G P I H S F J O F A
W G Q E N D C L A S L L V M V |
Those look amazing! Haven't had too much time to do some proper code review. |
Implemented in v3 |
I made a very basic proof of concept that's disconnected from the rest of the generator code this afternoon. I've put plenty of TODO items in the commit message. Some of the code I wrote seems quite duplicituous. Let's use this issue for higher-level design discussions and comments on the commit to discuss the implementation in its current code.
I was inspired to write this as well as #21 after stumbling across a different Python word search generator
I could implement this one, but if I don't get the time to finish it before Halloween, I may not be able to return until March [unlikely that it will be untouched for that entire time, but I expect to have much less availablilty to work on side projects over the winter].
The text was updated successfully, but these errors were encountered: