copy archive from my github

This commit is contained in:
Jens Scheffler 2025-04-29 09:57:22 -07:00
parent 9251266040
commit 1d22febc98
47 changed files with 269474 additions and 1 deletions

163
.gitignore vendored
View file

@ -1 +1,162 @@
.env
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

53
chat.py Normal file
View file

@ -0,0 +1,53 @@
"""A wrapper around LLMs, customized for our config"""
from gemini import Gemini
from logging import getLogger
from typing import Optional
logger = getLogger()
class Chat:
"""Represents a configured interaction with an LLM"""
__GENAI_INITIALIZED = False
def __init__(self) -> None:
self.__system_message = None
def set_system_message(self, message: Optional[str]) -> None:
"""Append a non-empty system message to initialize the chat."""
self.__system_message = message
def chat(self, message: str) -> Optional[str]:
"""Asks a question and returns the response.
Mostly used for debugging.
The 'chat" method is usually more convenient.
"""
logger.debug("Sending chat to LLM: %s" % message)
response = Gemini().chat(message, self.__system_message)
if response.success:
logger.debug('LLM responded: %s' % response.text_response)
return response.text_response
else:
logger.error('LLM failed to respond.')
return None
def smoke_test(self) -> str:
""" Sends a test message to the LLM that we know the response to.
Returns:
str: Error string if the test failed, empty string otherwise
"""
response = Gemini().chat('respond with "OK"', None)
if response.success:
if response.text_response == 'OK':
return ''
else:
return ('Smoke test failed: llm was expected '
'to return "OK", got: "%s"'
% response.text_response)
else:
return 'LLM failed to respond'

20
content/misc/example.txt Normal file
View file

@ -0,0 +1,20 @@
Example word search
Puzzle
Hidden
Search
Words
Letters
Find
Diagonal
Vertical
Horizontal
Challenge
Fun
Brain
Game
Solve
Clues
Grid
Focus
Patience
Victory

BIN
content/misc/puzzle.pdf Normal file

Binary file not shown.

3123
content/seasons/fall.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
The settings in the Book Bolt Studio you picked are below, and the same must be used on KDP when creating your book
Create a book section:
PAPERBACK
Step 2 of 3 - Paperback Content under Print Options:
BLACK AND WHITE INTERIOR WITH WHITE PAPER
8.5 X 11
BLEED

BIN
content/seasons/seasons.pdf Normal file

Binary file not shown.

2092
content/seasons/spring.txt Normal file

File diff suppressed because it is too large Load diff

3368
content/seasons/summer.txt Normal file

File diff suppressed because it is too large Load diff

3205
content/seasons/winter.txt Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -0,0 +1,27 @@
Cinderella
BALL
SLIPPER
STEPSISTERS
PRINCE
MAGIC
FAIRY
GODMOTHER
CARRIAGE
PUMPKIN
MIDNIGHT
GLASS
KINDNESS
POVERTY
TRANSFORMATION
LOVE
BEAUTY
AMBITION
FORGIVENESS
DREAM
HOPE
COURAGE
HUMILITY
WORK
FAMILY
INHERITANCE
FATE

Binary file not shown.

View file

@ -0,0 +1,27 @@
Goldilocks and the Three Bears
GOLDILOCKS
BEARS
PORRIDGE
CHAIR
BED
HOUSE
FOREST
BOWL
TOO
HOT
COLD
JUSTRIGHT
LITTLE
MIDDLE
BIG
PAPA
MAMA
BABY
HAIR
WANDER
CURIOUS
NAUGHTY
LESSON
FAMILY
HOME
ADVENTURE

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

View file

@ -0,0 +1,27 @@
Hansel and Gretel
FOREST
GINGERBREAD
WITCH
CHILDREN
BREADCRUMBS
CANDY
HOUSE
OVEN
SIBLINGS
DANGER
ESCAPE
WOODS
MAGICAL
ADVENTURE
EVIL
TRAP
FEAR
COURAGE
GREED
CRUELTY
LOVE
HOME
FAMILY
SURVIVAL
HOPE
FREEDOM

View file

@ -0,0 +1,27 @@
Jack and the Beanstalk
JACK
BEANSTALK
GIANT
MAGIC
BEAN
CLIMB
CASTLE
COW
MONEY
MOTHER
AXE
HARP
GOOSE
GOLDEN
EGGS
HEN
GIANTESS
WIFE
ESCAPE
ADVENTURE
FAIRY
TALE
GREEDY
CLEVER
POOR
MAGIC

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -0,0 +1,120 @@
from pypdf import PdfReader, PdfWriter, PageObject
from pypdf.annotations import Link
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from PIL import Image
import io
def image_to_page(image_path: str,
x_position: int, y_position: int,
desired_width: int, desired_height: int) -> PageObject:
image_pdf = io.BytesIO()
c = canvas.Canvas(image_pdf, pagesize=letter)
c.drawImage(image_path, x_position, y_position,
width=desired_width, height=desired_height)
c.save()
image_pdf.seek(0)
return PdfReader(image_pdf).pages[0]
class Stamp:
def __init__(self,
image_path: str,
x_position: int, y_position: int,
desired_width: int, desired_height: int) -> None:
self._image_page = image_to_page(
image_path, x_position, y_position, desired_width, desired_height)
self._bounding_box = (x_position, y_position, x_position +
desired_width - 1,
y_position + desired_height - 1)
def stamp(self, page: PageObject) -> None:
page.merge_page(self._image_page)
def add_link(self, writer: PdfWriter, i: int, j: int):
annotation = Link(rect=self._bounding_box, target_page_index=j)
writer.add_annotation(page_number=i, annotation=annotation)
def main() -> None:
writer = PdfWriter()
# Add a title page and "how to play"
writer.add_page(image_to_page("grayscale-1.png", 25, 25, 550, 990))
writer.add_page(
PdfReader("Fairy tale puzzle book interior PRINT_VERSION.pdf").pages[0])
# Next, add the stamps to all puzzle pages
reader = PdfReader("Fairy tale puzzle book interior PRINT_VERSION.pdf")
stamp = Stamp("link_text.png", 495, 15, 100, 50)
back_arrows = [
Stamp("back.png", 278, 388, 20, 20),
Stamp("back.png", 490, 388, 20, 20),
Stamp("back.png", 278, 124, 20, 20),
Stamp("back.png", 490, 124, 20, 20)
]
# Add pages and merge the image to the specified page
skips = [0, 1, 23]
for i, page in enumerate(reader.pages):
if i in skips:
continue
if i < 22:
stamp.stamp(page)
elif i > 22:
for a in back_arrows:
a.stamp(page)
writer.add_page(page)
# Add forward and back annotations
for i in range(2, 22):
stamp.add_link(writer, i, 23 + (i - 2) // 4)
for i in range(23, 28):
offset = 4 * (i - 23) + 2
for j in range(4):
back_arrows[j].add_link(writer, i, offset + j)
# Add bookmarks
writer.add_outline_item("Cover", 0)
writer.add_outline_item("How to play", 1)
parent = writer.add_outline_item("Puzzles", 2)
for i, n in enumerate([
"Cinderella",
"Snow white",
"The emperor's new clothes",
"The Bremen town musicians",
"Little red riding hood",
"The three little pigs",
"The princcess and the pea",
"The ugly duckling",
"The little mermaid",
"Rumplestiltskin",
"Sleeping beauty",
"Hansel and Gretel",
"Rapunzel",
"The magic pen",
"The talking animals",
"Goldilocks and the three bears",
"The gingerbread man",
"The tortoise and the hare",
"Jack and the beanstalk",
"The frog prince",
]):
writer.add_outline_item(n, i + 2, parent)
writer.add_outline_item("Solution", 22)
# Add copyright page
writer.add_page(PdfReader('copyright.pdf').pages[0])
writer.add_outline_item("Copyright", len(writer.pages) - 1)
# Write the output
with open("Fairy tale puzzle book interior.pdf", "wb") as output_file:
writer.write(output_file)
print("DONE")
if __name__ == '__main__':
main()

View file

@ -0,0 +1,26 @@
Little Red Riding Hood
RED
HOOD
FOREST
WOLF
GRANDMOTHER
BASKET
PATH
WOODS
CAPE
BASKET
TRAIL
TRICKERY
DECEPTION
DANGER
FEAR
INNOCENCE
GRANNY
BASKET
STORY
FAIRY
TALE
MORAL
LESSON
EVIL
GOOD

Binary file not shown.

View file

@ -0,0 +1,27 @@
Rapunzel
TOWER
HAIR
PRINCESS
GOLDEN
LONG
TRAPPED
WITCH
MOTHER
ESCAPE
FLYNN
RIDER
KINGDOM
LOVE
FREEDOM
ADVENTURE
SUNLIGHT
LANTERN
SUNSHINE
MAGIC
HEALING
BETRAYAL
REDEMPTION
COURAGE
HOPE
YOUTH
BEAUTY

View file

@ -0,0 +1,26 @@
Rumpelstiltskin
SPINNING
STRAW
GOLD
GNOME
GREED
TRICKSTER
MILLER
DAUGHTER
BARGAIN
NAME
POWER
MAGIC
DECEIT
CUNNING
FATE
DESTINY
BETRAYAL
REDEMPTION
FEAR
LOVE
SACRIFICE
TRUTH
CONSEQUENCE
FREEDOM
HOPE

View file

@ -0,0 +1,27 @@
Sleeping Beauty
SLEEP
BEAUTY
PRINCESS
CURSE
SPINDLE
THORN
PRINCE
CASTLE
SLUMBER
DREAM
FAIRY
MAGIC
LOVE
KISS
AWAKEN
ENCHANTMENT
FOREST
CASTLE
GOWN
EVIL
GOOD
DRAGON
DRAGON
QUEEN
KING
LOVE

View file

@ -0,0 +1,27 @@
Snow White
SNOW
WHITE
QUEEN
MIRROR
POISON
APPLE
DWARF
FOREST
PRINCE
CASTLE
MAGIC
JEALOUSY
BEAUTY
EVIL
HUNTSMAN
WOODS
COTTAGE
SEVEN
KIND
GENTLE
FAIR
LOVE
TRUE
HAPPINESS
RESCUE
HOPE

View file

@ -0,0 +1,27 @@
The Bremen Town Musicians
DONKEY
DOG
CAT
ROOSTER
BREMEN
TOWN
MUSICIANS
JOURNEY
FRIENDSHIP
ADVENTURE
OLD
TIRED
ABANDONED
HOPE
COURAGE
MUSIC
ROBBERS
FEAR
ESCAPE
CLEVER
CUNNING
TRIUMPH
LAUGHTER
JOY
CAMARADERIE
RESILIENCE

View file

@ -0,0 +1,26 @@
The Emperor's New Clothes
EMPEROR
CLOTHES
DECEPTION
VANITY
ARROGANCE
WEAVERS
INVISIBLE
FRAUD
CHILD
TRUTH
HONESTY
NAKEDNESS
FEAR
CONFORMITY
POWER
SOCIETY
FABRIC
IMAGINATION
GULLIBLE
WISDOM
CRITICISM
SHAME
AUTHORITY
PERCEPTION
ILLUSION

View file

@ -0,0 +1,27 @@
The Frog Prince
FROG
PRINCE
PRINCESS
KISS
CURSE
MAGIC
POND
CASTLE
ROYAL
TOAD
TRANSFORMATION
LOVE
BEAUTY
UGLY
CHARM
TRUST
DECEPTION
ADVENTURE
COURAGE
ROYALTY
KINGDOM
ENCHANTMENT
FATE
DESTINY
CHOICE
HOPE

View file

@ -0,0 +1,27 @@
The Gingerbread Man
GINGERBREAD
MAN
RUN
CHASE
OVEN
BAKE
SWEET
SPICY
HOT
FAST
CUNNING
CLEVER
GINGERBREAD
DELICIOUS
GINGERBREAD
CRUMBLY
CRISPY
GINGERBREAD
BROWN
GINGERBREAD
GINGERBREAD
ESCAPE
GINGERBREAD
GINGERBREAD
CATCH
GINGERBREAD

View file

@ -0,0 +1,26 @@
The Little Mermaid
ARIEL
OCEAN
MERMAID
PRINCESS
SEA
URSULA
TRITON
SEBASTIAN
FLOUNDER
SCUTTLE
LOVE
HUMAN
DESIRE
VOICE
LEGS
MAGIC
ADVENTURE
FORBIDDEN
FAMILY
SACRIFICE
FREEDOM
MUSIC
SEAWEED
CASTLE
SHIP

View file

@ -0,0 +1,26 @@
The Magic Pen
INK
QUILL
SPELL
DREAM
WORDS
POWER
STORY
CREATION
WONDER
MYTH
ENCHANT
LEGEND
INKWELL
SCRIPT
FANTASY
IMAGINATION
INSPIRATION
SCRIBBLE
STORYTELLER
ETHEREAL
UNLEASH
ENCHANTMENT
TRANSCENDENT
BOUNDLESS
ETERNAL

View file

@ -0,0 +1,27 @@
The Princess and the Pea
PRINCESS
PEA
ROYAL
TRUE
LOVE
BED
MATTRESS
SENSITIVITY
GENTLE
DELICATE
PRINCE
KINGDOM
CASTLE
CHARM
ELEGANCE
GRACE
TEST
AUTHENTICITY
IDENTITY
TRUTH
NOBLE
WORTHY
LOVE
MARRIAGE
HAPPINESS
STORY

View file

@ -0,0 +1,26 @@
The Talking Animals.
ANIMAL
SPEECH
COMMUNICATION
LANGUAGE
BEASTS
CREATURES
FAUNA
VOICES
WHISPER
ROAR
BARK
CHIRP
MEOW
HOWL
HOOT
SQUEAK
CHATTER
DIALOGUE
CONVERSATION
UNDERSTANDING
WISDOM
MYTH
FABLE
FANTASY
REALITY

View file

@ -0,0 +1,27 @@
The Three Little Pigs
PIG
STRAW
STICK
BRICK
WOLF
HOUSE
HUFF
PUFF
BLOW
DOWN
GREEDY
CLEVER
CUNNING
SCARED
SAFE
SHELTER
BROTHER
SISTER
FAMILY
FOREST
HUNGRY
ESCAPE
ADVENTURE
LESSON
PERSEVERANCE
TEAMWORK

View file

@ -0,0 +1,26 @@
The Tortoise and the Hare
TORTOISE
HARE
RACE
SLOW
FAST
STEADY
ARROGANT
OVERCONFIDENT
PERSEVERANCE
DETERMINATION
PATIENCE
PRIDE
HUMILITY
LESSON
AESOP
FABLE
COMPETITION
WINNER
LOSER
SPEED
AGILITY
ENDURANCE
CONFIDENCE
EFFORT
OUTCOME

View file

@ -0,0 +1,27 @@
The Ugly Duckling
DUCKLING
UGLY
SWAN
OUTCAST
DIFFERENT
ACCEPTANCE
TRANSFORMATION
SELFDOUBT
BEAUTY
GRACE
FAMILY
BELONGING
JOURNEY
RESILIENCE
CONFIDENCE
LOVE
COMPASSION
PREJUDICE
KINDNESS
HOPE
CHANGE
GROWTH
IDENTITY
FEAR
COURAGE
DESTINY

118
gemini.py Normal file
View file

@ -0,0 +1,118 @@
"""A wrapper around gemini, customized for our config"""
from google.api_core.exceptions import ResourceExhausted
from google.generativeai import (
configure as genai_config, GenerationConfig, GenerativeModel)
from google.generativeai.types.safety_types import HarmCategory
from logging import getLogger
from time import sleep
from typedefs import Llm, ChatResult
from typing import Optional
logger = getLogger()
class Gemini(Llm):
"""Represents an interaction with Gemini"""
__GENAI_INITIALIZED = False
def __init__(self) -> None:
super().__init__()
self.__model = None
def __create_payload(self,
message: str,
system_message: Optional[str] = None)\
-> list[dict[str, str]]:
"""Creates a list of messages we can send to an LLM."""
assert message, 'Message must be non-empty'
messages = []
if system_message:
messages.append(
# TODO: in other LLMs, the role would be 'system'.
# What's the best fit in Gemini?
{'role': 'user', 'parts': [{'text': message}]})
messages.append({'role': 'user', 'parts': [{'text': message}]})
logger.debug('Message object to be sent:\n%s' % messages)
return messages
def __get_model(self) -> GenerativeModel:
if self.__model:
return self.__model
if not Gemini.__GENAI_INITIALIZED:
logger.debug('Calling genai_config()')
genai_config()
Gemini.__GENAI_INITIALIZED = True
gen_config = GenerationConfig(temperature=1.0)
self.__model = GenerativeModel('gemini-1.5-flash-latest',
generation_config=gen_config,
)
return self.__model
def __count_words(self, payload: list[dict[str, any]]) -> int:
count = 0
for i in payload:
if 'parts' not in i:
continue
for p in i['parts']:
if 'text' not in p:
continue
count += len(p['text'].split())
return count
def chat(
self,
message: str,
system_message: Optional[str])\
-> ChatResult:
payload = self.__create_payload(message, system_message)
# see https://www.googlecloudcommunity.com/\
# gc/AI-ML/Gemini-Pro-Quota-Exceeded/m-p/693185
sleep_count = 0
sleep_time = 2
words_sent = 0
words_received = 0
payload_wordcount = self.__count_words(payload)
while True:
try:
words_sent += payload_wordcount
response = self.__get_model().generate_content(payload)
if response and response.text:
words_received += len(response.text.split())
except ValueError as ve:
logger.warn("ValueError occurred, skipping topic: %s" % ve)
return ChatResult(
success=False,
text_response=None,
words_sent=words_sent,
words_received=words_received
)
except ResourceExhausted as re:
logger.warn(
'ResourceExhausted exception occurred '
'while talking to gemini: %s' % re)
sleep_count += 1
if sleep_count > 5:
logger.warn(
'ResourceExhausted exception occurred '
'5 times in a row. Exiting.')
return ChatResult(
success=False,
text_response=None,
words_sent=words_sent,
words_received=words_received
)
logger.info(
'Too many requests, backing off for %s seconds'
% sleep_time)
sleep(sleep_time)
sleep_time *= 2
else:
return ChatResult(
success=True,
text_response=response.text.rstrip(),
words_sent=words_sent,
words_received=words_received
)

37
generator.py Normal file
View file

@ -0,0 +1,37 @@
"""Generates content"""
from chat import Chat
from util import dedupe
class Generator():
def suggest_topics(self, theme: str, amount: int) -> list[str]:
chat = Chat()
chat.set_system_message(
'You are assisting a puzzle creator in generating '
'ideas for topics for word search puzzles.'
'Each topic should be unique and match a common theme.'
)
suggestions = chat.chat(
'Suggest %s topics for the theme \"%s\". ' % (amount, theme)
+ 'Return only the suggested words as a comma-separated list.'
)
suggestions = [s.strip() for s in suggestions.split(',')]
return dedupe(suggestions)
def suggest_words(self, topic: str, amount: int) -> list[str]:
chat = Chat()
chat.set_system_message(
'You are assisting a puzzle creator in generating '
'sets of words to be used in word searches.'
'Each word must be 14 characters or less.'
'Words may not repeat. Each suggestion must be '
'only a single word.'
)
suggestions = chat.chat(
'Suggest %s single words for the topic \"%s\". '
'Return only the words as a comma-separated list.'
% (amount, topic)).split(',')
if suggestions:
return dedupe([s.strip() for s in suggestions])
return []

15
make_puzzles.sh Normal file
View file

@ -0,0 +1,15 @@
PREFIX=$(echo $1 | sed 's/.pdf//')
TMPDIR=$(mktemp -d)
MYDIR=$PWD
cd $TMPDIR
cp $MYDIR/$PREFIX.pdf $TMPDIR/
pdftoppm -png -x 90 -y 100 -W 1120 -H 1500 $PREFIX.pdf $PREFIX
rm $PREFIX.pdf
for file in *
do
CMD="/usr/bin/convert $file -transparent white $file"
eval $CMD
done
cp * $MYDIR/
cd $MYDIR
rm -rf $TMPDIR

22
typedefs.py Normal file
View file

@ -0,0 +1,22 @@
"""Type definitions"""
from dataclasses import dataclass
from typing import Optional
@dataclass
class ChatResult:
success: bool
text_response: str
# TODO: switch stats to tokens instead of words!!!
words_sent: int
words_received: int
class Llm:
def chat(
self,
message: str,
system_message: Optional[str])\
-> ChatResult:
pass

34
util.py Normal file
View file

@ -0,0 +1,34 @@
"""Utilities"""
import re
def letters_only(s: str) -> str:
return re.compile('[^a-zA-Z]').sub('', s)
def words_only(s: str) -> str:
words = [letters_only(x) for x in s.split()]
r = []
for w in words:
if w:
r.append(w)
return ' '.join(r)
def file_name(s: str, ext='txt') -> str:
return '%s.%s' % (s.lower().replace(' ', '_'), ext)
def dedupe(strings: list[str]) -> list[str]:
seen = set()
r = []
for s in strings:
if s not in seen:
r.append(s)
seen.add(s)
return r
def header(s: str) -> str:
return words_only(s).lower().title()

35
word_lists.py Normal file
View file

@ -0,0 +1,35 @@
from generator import Generator
from util import letters_only, header
import dotenv
import logging
import random
logger = logging.getLogger(__name__)
def main() -> int:
dotenv.load_dotenv()
logging.basicConfig(level=logging.INFO, format='%(message)s')
lists = []
for topic in Generator().suggest_topics(
'Events, activities, and phenomena in winter',
105):
logger.info('Creating topic for "%s".', topic)
generated = Generator().suggest_words(topic, 24)
if generated and len(generated) >= 10:
lists.append((topic, generated))
random.shuffle(lists)
logger.info('Writing %s lists to output' % len(lists))
output = []
for topic, generated in lists:
output.append(header(topic))
output.extend([letters_only(c).upper() for c in generated])
output.append('')
output.pop() # remove last newline
print('\n'.join(output))
return 0
if __name__ == '__main__':
main()

42
wordsearch_img.py Normal file
View file

@ -0,0 +1,42 @@
import dotenv
import logging
from pysuchsel.Alphabet import Alphabet
from pysuchsel.Suchsel import Suchsel
from pysuchsel.RandomDist import RandomDist
from util import file_name
logger = logging.getLogger(__name__)
def main() -> int:
dotenv.load_dotenv()
logging.basicConfig(level=logging.INFO, format='%(message)s')
plcrule = RandomDist({
"lr": 1,
"tb": 1,
})
# w: 1275
# h: 1651
suchsel = Suchsel(10, 10, plcrule, 5)
words = ['TOWER', 'HAIR', 'PRINCESS', 'GOLDEN', 'RAPUNZEL']
unplaced = []
for word in words:
placed = suchsel.place(word, contiguous=False)
if not placed:
unplaced.append(word)
# TODO: optimize
print('UNPLACE: %s' % unplaced)
filler = Alphabet('en')
suchsel.fill(filler)
suchsel.write_svg(file_name('tangled', 'svg'))
return 0
if __name__ == '__main__':
main()