Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 2023-Oct-18/Dave/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
5 changes: 3 additions & 2 deletions 2023-Oct-18/Dave/direction.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ class DirectValueMeta(EnumMeta):
def __getattribute__(cls, name):
value = super().__getattribute__(name)
if isinstance(value, cls):
value = value.value
return value
return value.value
else:
return value

# This allows us to iterate through enum members in a for loop and access
# the Point value directly
Expand Down
14 changes: 14 additions & 0 deletions 2023-Oct-18/Dave/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class FailedToGenerateWordSearchError(Exception):
...


class FailedToPlaceAllWordsError(Exception):
...


class NoLegalPlacementsError(Exception):
...


class GridOverflowError(Exception):
...
75 changes: 41 additions & 34 deletions 2023-Oct-18/Dave/main.py
Original file line number Diff line number Diff line change
@@ -1,77 +1,81 @@
import random
from copy import deepcopy
from word_grid import WordGrid
from point import Point
from direction import Direction
from placement import Placement
from exceptions import (
FailedToGenerateWordSearchError,
FailedToPlaceAllWordsError,
NoLegalPlacementsError,
GridOverflowError,
)


def main():
word_list = ["MEOW", "CAT", "WOOF", "DOG"]
word_list = ["PYTHON", "DOJO", "CODEHUB", "BRISTOL"]

word_search = generate_word_search(
rows=4, cols=4, word_list=word_list)

if word_search:
try:
word_search = generate_word_search(rows=6, cols=9, word_list=word_list)
print(word_search)
print("Find these words:")
print(", ".join(word_list))
else:
except FailedToGenerateWordSearchError:
print("Failed to generate word search.")
exit(1)


def generate_word_search(rows: int, cols: int, word_list: list[str]) -> WordGrid | None:
word_search = WordGrid(rows, cols)
word_grid = WordGrid(rows, cols)

attempts = 0
max_attempts = 10

while attempts < 10:
word_search.initialise_word_grid()
filled_word_search = place_words(
word_search=word_search, word_list=word_list)
if filled_word_search:
while attempts < max_attempts:
word_grid.initialise_grid()
try:
filled_word_search = place_words(word_grid, word_list)
filled_word_search.fill_blank_space()
return filled_word_search
else:
except FailedToPlaceAllWordsError:
attempts += 1
continue
else:
return None
raise FailedToGenerateWordSearchError()


def place_words(word_search: WordGrid, word_list=list[str]) -> WordGrid | None:
filled_word_search = word_search
def place_words(word_grid: WordGrid, word_list: list[str]) -> WordGrid:
word_search = deepcopy(word_grid)

for word in word_list:
placements = get_all_legal_placements_for_word(
word_grid=filled_word_search, word=word
)
if placements:
try:
placements = get_all_legal_placements_for_word(word_search, word)
position, direction = random.choice(placements)
filled_word_search.write_line(
position=position, orientation=direction, data=word
)
else:
return None
word_search.write_line(position, direction, word)
except NoLegalPlacementsError:
raise FailedToPlaceAllWordsError()

return filled_word_search
return word_search


def get_all_legal_placements_for_word(
word_grid: WordGrid, word: str
) -> list[Placement] | None:
) -> list[Placement]:
legal_placements = []

# Iterate through all possible grid locations and orientations
for row_index, row in enumerate(word_grid):
for row_index, row in enumerate(word_grid.grid):
for col_index, col in enumerate(row):
for direction in Direction:
position = Point(row_index, col_index)

target_line = word_grid.read_line(
position, direction, len(word))
if not target_line:
line_can_be_written = word_grid.is_valid_line(
position, direction, len(word)
)
if not line_can_be_written:
continue

target_line = word_grid.read_line(
position, direction, len(word))
line_can_be_placed = is_legal_placement(
target_line=target_line, line_to_write=word
)
Expand All @@ -80,12 +84,15 @@ def get_all_legal_placements_for_word(

legal_placements.append(Placement(position, direction))

return legal_placements
if len(legal_placements) == 0:
raise NoLegalPlacementsError()
else:
return legal_placements


def is_legal_placement(target_line: str, line_to_write: str) -> bool:
for target_char, char_to_write in zip(target_line, line_to_write):
if char_to_write != target_char and target_char != " ":
if (char_to_write is not target_char) and (target_char != " "):
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A mix of is not and != here again.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, this was because I had intended to use is / is not with everything, but received a SyntaxWarning about using it with a literal.

Turns out this is because is / is not and == / != are not interchangeable and the former checks type as well, as far as I can tell.

Seems like the answer is to make everything == / !=.

That's enough backticks for now.

return False
return True

Expand Down
16 changes: 16 additions & 0 deletions 2023-Oct-18/Dave/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "Dave",
"version": "0.0.2",
"description": "",
"main": "main.py",
"scripts": {
"start": "nodemon main.py",
"test": "pytest"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"nodemon": "^3.0.1"
}
}
2 changes: 2 additions & 0 deletions 2023-Oct-18/Dave/placement.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class Placement:
position: Point
direction: Direction
Comment on lines +8 to +9
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like the "prefer composition over inheritance"!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a design pattern thing here is a good explanation but it's a very common phrase.


# This allows us to unpack a placement with the syntax:
# position, direction = placement
def __iter__(self):
yield self.position
yield self.direction
Comment thread
DaveDangereux marked this conversation as resolved.
Loading