-
Notifications
You must be signed in to change notification settings - Fork 2
Dave's Branch #3
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
base: main
Are you sure you want to change the base?
Changes from all commits
57c8d05
3697ff9
8bf41d3
426d9d7
7ed053b
c22d826
74ea0db
3690887
05fc53c
85c3c93
05320b4
4df03ff
667ea7b
f811b83
af63a19
34a8ec7
efddf53
0068470
d71ac00
1700325
d71bf54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| node_modules |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| from enum import Enum, EnumMeta | ||
| from point import Point | ||
|
|
||
|
|
||
| class DirectValueMeta(EnumMeta): | ||
| # This allows us to unpack enum members directly, in the format: | ||
| # row, col = Direction.RIGHT | ||
| def __getattribute__(cls, name): | ||
| value = super().__getattribute__(name) | ||
| if isinstance(value, cls): | ||
| return value.value | ||
| else: | ||
| return value | ||
|
|
||
| # This allows us to iterate through enum members in a for loop and access | ||
| # the Point value directly | ||
| def __iter__(cls): | ||
| for value in super().__iter__(): | ||
| yield value.value | ||
|
|
||
|
|
||
| class Direction(Enum, metaclass=DirectValueMeta): | ||
| RIGHT = Point(0, 1) | ||
| DOWN = Point(1, 0) | ||
| UP = Point(-1, 0) | ||
| LEFT = Point(0, -1) | ||
| UP_LEFT = Point(-1, -1) | ||
| UP_RIGHT = Point(-1, 1) | ||
| DOWN_LEFT = Point(1, -1) | ||
| DOWN_RIGHT = Point(1, 1) | ||
| 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): | ||
| ... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| 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 = ["PYTHON", "DOJO", "CODEHUB", "BRISTOL"] | ||
|
|
||
| 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)) | ||
| except FailedToGenerateWordSearchError: | ||
| print("Failed to generate word search.") | ||
| exit(1) | ||
|
|
||
|
|
||
| def generate_word_search(rows: int, cols: int, word_list: list[str]) -> WordGrid: | ||
| word_grid = WordGrid(rows, cols) | ||
|
|
||
| attempts = 0 | ||
| max_attempts = 10 | ||
|
|
||
| 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 | ||
| except FailedToPlaceAllWordsError: | ||
| attempts += 1 | ||
| else: | ||
| raise FailedToGenerateWordSearchError() | ||
|
|
||
|
|
||
| def place_words(word_grid: WordGrid, word_list: list[str]) -> WordGrid: | ||
| word_search = deepcopy(word_grid) | ||
|
|
||
| for word in word_list: | ||
| try: | ||
| placements = get_all_legal_placements_for_word(word_search, word) | ||
| position, direction = random.choice(placements) | ||
| word_search.write_line(position, direction, word) | ||
| except NoLegalPlacementsError: | ||
| raise FailedToPlaceAllWordsError() | ||
|
|
||
| return word_search | ||
|
|
||
|
|
||
| def get_all_legal_placements_for_word( | ||
| word_grid: WordGrid, word: str | ||
| ) -> list[Placement]: | ||
| legal_placements = [] | ||
|
|
||
| # Iterate through all possible grid locations and orientations | ||
| 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) | ||
|
|
||
| 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 | ||
| ) | ||
| if not line_can_be_placed: | ||
| continue | ||
|
|
||
| legal_placements.append(Placement(position, direction)) | ||
|
|
||
| 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 != " "): | ||
| return False | ||
| return True | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer if __name__ != "__main__":
raise someError("Running main as module is not supported")
main()or if __name__ != "__main__":
print("Running main as module is not supported")
exit(not_zero)
main()As they force the correct usage on the user but it's more just style so up to you. These (not the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting. Would this influence testing? As in would this not raise an error if I imported main for testing? Might be completely off-target here, but keen to know.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't require main in my tests as it is normally very small and a full end to end integration test you may want to do with command line instead. |
||
| main() | ||
| 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" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| from dataclasses import dataclass | ||
| from point import Point | ||
| from direction import Direction | ||
|
|
||
|
|
||
| @dataclass | ||
| class Placement: | ||
| position: Point | ||
| direction: Direction | ||
|
Comment on lines
+8
to
+9
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like the "prefer composition over inheritance"!
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you clarify?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
DaveDangereux marked this conversation as resolved.
Show resolved
Hide resolved
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Coming from a C style background I think this enum does too much but a python/java/not C/C++ engineer would probably think it's fine.
(See a later comment about this enum where it is used in
main.py)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be interested to hear a suggestion for an alternative. I definitely like the dot syntax. I had previously thought about using a named tuple, but something about that feels wrong, since there will only ever be one of these with fixed values and it seems odd to have the option to instantiate something that will only ever be one version of itself.
Open to suggestions, though.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The C solution is macros (not in python thank god) The solution in other languages might be an enum or a hashmap. C++ has compile time constants (constexpr) so you can create a constexpr map or even a runtime map as everything is so fast it's fine.