Skip to content

Commit

Permalink
Annotated main.py, begin developing cword_webapp
Browse files Browse the repository at this point in the history
`main.py` is now completely type annotated and intrinsically documented. The `HorizontalScrollFrame` class was removed and its functionality was replaced with a ctk.CTkScrollableFrame.

Added `requirements.txt`.

Began working on the interactive crossword webapp (using Flask and Jinja2) that will allow the user to fill in a generated crossword. Currently, only the html and css for viewing a grid of a variable side length has been written.
  • Loading branch information
tomasvana10 committed Jan 21, 2024
1 parent a1f0853 commit ef4288f
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 142 deletions.
8 changes: 8 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Babel==2.14.0
customtkinter==5.2.2
darkdetect==0.8.0
Flask==3.0.1
googletrans==3.0.0
Pillow==10.0.0
Pillow==10.2.0
regex==2023.6.3
2 changes: 1 addition & 1 deletion src/config.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[m]
scale = 1.0
appearance = dark
appearance = light
theme = dark-blue
language = en

Expand Down
1 change: 1 addition & 0 deletions src/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Paths:
CWORD_IMG_DARK_PATH = Path(__file__).resolve().parents[1] / "assets/images/cword_img_dark.png"
CWORDS_PATH = Path(__file__).resolve().parents[0] / "cwords"
ATTEMPTS_DB_PATH = Path(__file__).resolve().parents[0] / "data/attempts_db.json"
CWORD_REFERENCE_DATA = Path(__file__).resolve().parents[0] / "cword_webapp/cword_reference_data"


class Colour:
Expand Down
2 changes: 2 additions & 0 deletions src/custom_types.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'''Custom types for type annotation in the source code of crossword_puzzle.'''

from typing import List, Tuple, Union, TypedDict

class Placement(TypedDict):
Expand Down
42 changes: 23 additions & 19 deletions src/cword_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
from constants import (
CrosswordDirections, CrosswordStyle, DimensionsCalculation, Paths
)
from errors import ( # For the most part this isn't really necessary until, or if, custom crossword
# creation is implemented
from errors import (
AlreadyGeneratedCrossword, PrintingCrosswordObjectBeforeGeneration,
MakingUnfilledReplicaGridBeforeGeneration
)
Expand Down Expand Up @@ -45,7 +44,8 @@ class Crossword(object):
>>> print(crossword)
>>> pprint(crossword.unfilled_grid_replica) # Already made in this case
When inserting large amounts of words, fails with insertion may occur.
NOTE: When inserting large amounts of words, fails with insertion may occur.
'''

def __init__(self,
Expand All @@ -55,14 +55,15 @@ def __init__(self,
retry: bool = False
) -> None:
self.retry = retry
if self.retry:
if self.retry: # Reattempting insertion, randomise exisiting definitions
self.definitions = self._randomise_existing_definitions(definitions)
else:
else: # Randomly sample `word_count` amount of definitions and ensure they only contain
# language characters
self.definitions = DefinitionsParser._parse_definitions(definitions, word_count)

self.generated: bool = False
self.name = name
self.word_count = word_count
self.generated: bool = False
self.dimensions: int = self._find_dimensions()
self.clues = dict() # Presentable to end-user; expanded as words are inserted
''' example:
Expand All @@ -79,10 +80,11 @@ def __init__(self,
'''

def __str__(self) -> str:
'''Display crossword when printing an instance of this class.'''
'''Display crossword when printing an instance of this class, on which `.generate()` has been called'''
if not self.generated:
raise PrintingCrosswordObjectBeforeGeneration

# Name, word count (insertions), failed insertions, total intersections, crossword, clues
return \
f"\nCrossword name: {self.name}\n" + \
f"Word count: {self.inserts}, Failed insertions: {self.word_count - self.inserts}\n" + \
Expand All @@ -91,11 +93,11 @@ def __str__(self) -> str:
"\n".join(f"{k}: {v}" for k, v in self.clues.items())

def generate(self) -> None:
'''Create an "EMPTY" two-dimensional array then populate it.'''
'''Create a two-dimensional array (filled with CrosswordStyle.EMPTY characters) then populate it.'''
if not self.generated:
self.generated = True
self.grid: List[List[str]] = self._initialise_cword_grid()
self._populate_grid(list(self.definitions.keys()))
self._populate_grid(list(self.definitions.keys())) # Keys of definitions are the words
else:
raise AlreadyGeneratedCrossword

Expand All @@ -122,7 +124,7 @@ def _find_dimensions(self) -> int:
return dimensions

def _initialise_cword_grid(self) -> List[List[str]]:
'''Make a two-dimensional array of "EMPTY" characters.'''
'''Make a two-dimensional array of `CrosswordStyle.EMPTY` characters.'''
return [[CrosswordStyle.EMPTY for column in range(self.dimensions)] \
for row in range(self.dimensions)]

Expand All @@ -136,10 +138,12 @@ def _place_word(self,
if direction == CrosswordDirections.ACROSS:
for i in range(len(word)):
self.grid[row][column + i] = word[i]

if direction == CrosswordDirections.DOWN:
return

elif direction == CrosswordDirections.DOWN:
for i in range(len(word)):
self.grid[row + i][column] = word[i]
return

def _find_first_word_placement_position(self,
word: str
Expand All @@ -154,7 +158,7 @@ def _find_first_word_placement_position(self,
return {"word": word, "direction": CrosswordDirections.ACROSS,
"pos": (row, column), "intersections": list()}

if direction == CrosswordDirections.DOWN:
elif direction == CrosswordDirections.DOWN:
row = middle - len(word) // 2
column = middle
return {"word": word, "direction": CrosswordDirections.DOWN,
Expand All @@ -174,7 +178,7 @@ def _find_intersections(self,
if self.grid[row][column + i] == word[i]:
intersections.append(tuple([row, column + i]))

if direction == CrosswordDirections.DOWN:
elif direction == CrosswordDirections.DOWN:
for i in range(len(word)):
if self.grid[row + i][column] == word[i]:
intersections.append(tuple([row + i, column]))
Expand All @@ -191,7 +195,7 @@ def _can_word_be_inserted(self,
returning False include:
1. The word being too long for the dimensions of the grid
2. Other characters being in the way of the word (not intersecting)
3. The word intersects with another word of the same orientation at its final letter,
3. The word intersects with another word of the same orientation at its first or last letter,
e.x. ATHENSOFIA (Athens + Sofia)
'''
if direction == CrosswordDirections.ACROSS: # 1
Expand Down Expand Up @@ -255,7 +259,7 @@ def _prune_placements_for_readability(self,
if self.grid[row][column + i + 1] != CrosswordStyle.EMPTY:
readability_flags += 1

if placement["direction"] == CrosswordDirections.DOWN:
elif placement["direction"] == CrosswordDirections.DOWN:
check_above = row != 0
check_below = row + word_length < self.dimensions
check_left = column != 0
Expand Down Expand Up @@ -417,9 +421,9 @@ def find_best_crossword(crossword: Crossword) -> Crossword:
attempts: int = 0

reinsert_definitions: Dict[str, str] = crossword.definitions
try:
try:
crossword.generate()
except:
except:
... # ok buddy
best_crossword = crossword

Expand Down Expand Up @@ -463,7 +467,7 @@ def _load_attempts_db() -> Dict[str, int]:
if __name__ == "__main__": # Example usage
definitions = CrosswordHelper.load_definitions("capitals")

crossword = Crossword(definitions=definitions, word_count=3, name="Capitals")
crossword = Crossword(definitions=definitions, word_count=80, name="Capitals")
crossword = CrosswordHelper.find_best_crossword(crossword)

print(crossword)
11 changes: 11 additions & 0 deletions src/cword_webapp/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
return render_template("index.html", dimensions=dimensions)

if __name__ == "__main__":
dimensions = 10
app.run(debug=True)
Empty file added src/cword_webapp/script.js
Empty file.
73 changes: 73 additions & 0 deletions src/cword_webapp/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Crossword Puzzle - Game</title>
<style>
:root {
--dimensions: {{ dimensions }};
--dynamic_font_size: calc(120vmin / calc(var(--dimensions) * 1.65));
}

html {
font-family: Arial, Helvetica, sans-serif;
}

.title {
display: flex;
justify-content: center;
}

.container {
width: 120vmin; /* Square dimensions - 1.2x larger than minimum viewport dimension */
height: 120vmin;
}

table {
margin: 1em;
table-layout: fixed;
height: 100%;
width: 100%;
border-collapse: separate;
border-spacing: 1px;
background-color: black;
}

table td {
position: relative;
background: whitesmoke;
text-align: center;
text-transform: uppercase;
vertical-align: bottom;
font-size: var(--dynamic_font_size)
}

.num_label {
position: absolute;
top: 0;
left: 0;
font-size: calc(var(--dynamic_font_size) / 2); /* I LOVE CALC FUNCTION!! THANKS CSS! */
}

</style>
</head>
<body>
<h1 class="title">Crossword Puzzle - Game</h1>
<hr>
<div class="container">
<table class="table">
{% for row in range(dimensions) %}
<tr>
{% for column in range(dimensions) %}
<td data-row="{{ row }}" data-column="{{ column }}">T
<div class="num_label">1</div>
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</div>
</body>
</html>

4 changes: 3 additions & 1 deletion src/definitions_parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import regex # Similar to "re" module but with more functionality
import random
from typing import Dict

import regex # Similar to "re" module but with more functionality

from errors import (
EmptyDefinitions, InsufficientDefinitionsAndOrWordCount, ShorterDefinitionsThanWordCount,
InsufficientWordLength, EscapeCharacterInWord
Expand All @@ -15,6 +16,7 @@ def _parse_definitions(definitions: Dict[str, str],
) -> Dict[str, str]:
definitions = definitions

# Required error checking
if not definitions:
raise EmptyDefinitions
if len(definitions) < 3 or word_count < 3:
Expand Down
17 changes: 9 additions & 8 deletions src/locale_utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'''The `LocaleUtils` class uses `parse_langcodes` to parse `googletrans` langcodes to remove any
'''The `LocaleUtils` class uses `_parse_locales` to parse `googletrans` langcodes to remove any
inconsistencies with the locale naming conventions of Babel's `Locales` class.
The class uses `write_locales` to initialise all of the locale folders based on the parsed locales.
The class uses `_write_locales` to initialise all of the locale folders based on the parsed locales.
NOTE: `write_locales` only works on macOS currently.
NOTE: `_write_locales` only works on macOS currently.
'''

import subprocess
Expand All @@ -15,7 +15,7 @@

class LocaleUtils:
@staticmethod
def parse_langcodes(langcodes: Dict[str, str]) -> Dict[str, str]:
def _parse_locales(langcodes: Dict[str, str]) -> Dict[str, str]:
'''Replace all googletrans langcodes as specified by `LanguageReplacementsForPyBabel` if the
value of the langcode is a string. If it is a None value, it is removed entirely.
'''
Expand All @@ -30,7 +30,7 @@ def parse_langcodes(langcodes: Dict[str, str]) -> Dict[str, str]:
return parsed_langcodes

@staticmethod
def write_locales(langcodes: Dict[str, str]) -> None:
def _write_locales(langcodes: Dict[str, str]) -> None:
'''Runs the pybabel `init` command to create approximately 104 locale files within
`crossword_puzzle/locales` based on the parsed langcodes.
'''
Expand All @@ -41,8 +41,9 @@ def write_locales(langcodes: Dict[str, str]) -> None:
result = subprocess.run(['zsh', '-c', cmd], text=True)
except:
print(f"Failed to insert: {code}")



if __name__ == "__main__":
langcodes = list(googletrans.LANGCODES.values())
parsed_langcodes = LocaleUtils.parse_langcodes(langcodes)
LocaleUtils.write_locales(langcodes)
parsed_langcodes = LocaleUtils._parse_locales(langcodes)
LocaleUtils._write_locales(langcodes)
Loading

0 comments on commit ef4288f

Please sign in to comment.