From 57c8d050c7360dd8bd0b646f46a4ca3c50ea3d13 Mon Sep 17 00:00:00 2001 From: Ray <57572379+JustCallMeRay@users.noreply.github.com> Date: Sat, 4 Nov 2023 22:03:45 +0000 Subject: [PATCH 01/21] Create main.py --- 2023-Oct-18/Dave/main.py | 94 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 2023-Oct-18/Dave/main.py diff --git a/2023-Oct-18/Dave/main.py b/2023-Oct-18/Dave/main.py new file mode 100644 index 0000000..4f9d50e --- /dev/null +++ b/2023-Oct-18/Dave/main.py @@ -0,0 +1,94 @@ +import random +from word_grid import WordGrid +from point import Point +from direction import Direction +from placement import Placement + + +def main(): + word_list = ["MEOW", "CAT", "WOOF", "DOG"] + + word_search = generate_word_search( + rows=4, cols=4, word_list=word_list) + + if word_search: + print(word_search) + print("Find these words:") + print(", ".join(word_list)) + else: + print("Failed to generate word search.") + + +def generate_word_search(rows: int, cols: int, word_list: list[str]) -> WordGrid | None: + word_search = WordGrid(rows, cols) + + attempts = 0 + + 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: + filled_word_search.fill_blank_space() + return filled_word_search + else: + attempts += 1 + continue + else: + return None + + +def place_words(word_search: WordGrid, word_list=list[str]) -> WordGrid | None: + filled_word_search = word_search + + for word in word_list: + placements = get_all_legal_placements_for_word( + word_grid=filled_word_search, word=word + ) + if placements: + position, direction = random.choice(placements) + filled_word_search.write_line( + position=position, orientation=direction, data=word + ) + else: + return None + + return filled_word_search + + +def get_all_legal_placements_for_word( + word_grid: WordGrid, word: str +) -> list[Placement] | None: + legal_placements = [] + + # Iterate through all possible grid locations and orientations + for row_index, row in enumerate(word_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: + continue + + 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)) + + 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__": + main() From 3697ff90f33c498869a802fd6358d24e1a73955f Mon Sep 17 00:00:00 2001 From: Ray <57572379+JustCallMeRay@users.noreply.github.com> Date: Sat, 4 Nov 2023 22:07:12 +0000 Subject: [PATCH 02/21] Create direction.py --- 2023-Oct-18/Dave/direction.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 2023-Oct-18/Dave/direction.py diff --git a/2023-Oct-18/Dave/direction.py b/2023-Oct-18/Dave/direction.py new file mode 100644 index 0000000..1f54ace --- /dev/null +++ b/2023-Oct-18/Dave/direction.py @@ -0,0 +1,29 @@ +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): + value = value.value + 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) From 8bf41d3548cbb474ed2413fe54dd0caff3457953 Mon Sep 17 00:00:00 2001 From: Ray <57572379+JustCallMeRay@users.noreply.github.com> Date: Sat, 4 Nov 2023 22:08:49 +0000 Subject: [PATCH 03/21] Create placement.py --- 2023-Oct-18/Dave/placement.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 2023-Oct-18/Dave/placement.py diff --git a/2023-Oct-18/Dave/placement.py b/2023-Oct-18/Dave/placement.py new file mode 100644 index 0000000..7ea2f54 --- /dev/null +++ b/2023-Oct-18/Dave/placement.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from point import Point +from direction import Direction + + +@dataclass +class Placement: + position: Point + direction: Direction + + def __iter__(self): + yield self.position + yield self.direction From 426d9d7d3a257e24dc7d227a363671e282510a7a Mon Sep 17 00:00:00 2001 From: Ray <57572379+JustCallMeRay@users.noreply.github.com> Date: Sat, 4 Nov 2023 22:09:35 +0000 Subject: [PATCH 04/21] Create point.py --- 2023-Oct-18/Dave/point.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 2023-Oct-18/Dave/point.py diff --git a/2023-Oct-18/Dave/point.py b/2023-Oct-18/Dave/point.py new file mode 100644 index 0000000..156f3e3 --- /dev/null +++ b/2023-Oct-18/Dave/point.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass + + +@dataclass +class Point: + row: int + col: int + + # This allows us to unpack a point with the syntax: + # row, col = point + def __iter__(self): + yield self.row + yield self.col From 7ed053b35efcbbf932c646f14c193cd4b9d8b9d5 Mon Sep 17 00:00:00 2001 From: Ray <57572379+JustCallMeRay@users.noreply.github.com> Date: Sat, 4 Nov 2023 22:11:42 +0000 Subject: [PATCH 05/21] Create word_grid.py --- 2023-Oct-18/Dave/word_grid.py | 76 +++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 2023-Oct-18/Dave/word_grid.py diff --git a/2023-Oct-18/Dave/word_grid.py b/2023-Oct-18/Dave/word_grid.py new file mode 100644 index 0000000..04adf15 --- /dev/null +++ b/2023-Oct-18/Dave/word_grid.py @@ -0,0 +1,76 @@ +import random +import string +from point import Point +from direction import Direction + + +class WordGrid: + def __init__(self, rows: int, cols: int): + self.rows = rows + self.cols = cols + self.initialise_word_grid() + + def __repr__(self): + return self.get_stringified_word_grid() + + def __iter__(self): + for row in self.word_grid: + yield row + + def initialise_word_grid(self): + self.word_grid = [[" " for col in range( + self.cols)] for row in range(self.rows)] + + def get_stringified_word_grid(self) -> str: + output = "" + for row in self.word_grid: + for char in row: + output += char + " " + output += "\n" + return output + + def read_line( + self, position: Point, orientation: Direction, length: int + ) -> str | None: + result = "" + + current_row, current_col = position + next_row, next_col = orientation + grid_rows = len(self.word_grid) + grid_cols = len(self.word_grid[0]) + + for i in range(length): + if not 0 <= current_row < len(self.word_grid): + return None + + if not 0 <= current_col < len(self.word_grid[0]): + return None + + result += self.word_grid[current_row][current_col] + current_row += next_row + current_col += next_col + + return result + + def write_line(self, position: Point, orientation: Direction, data: str) -> bool: + can_write_line = self.read_line(position, orientation, len(data)) + if not can_write_line: + return False + + current_row, current_col = position + next_row, next_col = orientation + + for char in data: + self.word_grid[current_row][current_col] = char + current_row += next_row + current_col += next_col + + return True + + def fill_blank_space(self): + for row_index, row in enumerate(self.word_grid): + for col_index, char in enumerate(row): + if char == " ": + self.word_grid[row_index][col_index] = random.choice( + string.ascii_uppercase + ) From c22d826f4b161dc5f00f2886ac784b3f4f6e0750 Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 20:44:40 +0000 Subject: [PATCH 06/21] Add nodemon for easy reloading --- 2023-Oct-18/Dave/.gitignore | 1 + 2023-Oct-18/Dave/package.json | 16 +++ 2023-Oct-18/Dave/pnpm-lock.yaml | 239 ++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+) create mode 100644 2023-Oct-18/Dave/.gitignore create mode 100644 2023-Oct-18/Dave/package.json create mode 100644 2023-Oct-18/Dave/pnpm-lock.yaml diff --git a/2023-Oct-18/Dave/.gitignore b/2023-Oct-18/Dave/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/2023-Oct-18/Dave/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/2023-Oct-18/Dave/package.json b/2023-Oct-18/Dave/package.json new file mode 100644 index 0000000..8a81d93 --- /dev/null +++ b/2023-Oct-18/Dave/package.json @@ -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" + } +} diff --git a/2023-Oct-18/Dave/pnpm-lock.yaml b/2023-Oct-18/Dave/pnpm-lock.yaml new file mode 100644 index 0000000..2f5a95b --- /dev/null +++ b/2023-Oct-18/Dave/pnpm-lock.yaml @@ -0,0 +1,239 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +devDependencies: + nodemon: + specifier: ^3.0.1 + version: 3.0.1 + +packages: + + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /debug@3.2.7(supports-color@5.5.0): + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + supports-color: 5.5.0 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + + /ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: true + + /nodemon@3.0.1: + resolution: {integrity: sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + chokidar: 3.5.3 + debug: 3.2.7(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 7.5.4 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.0 + undefsafe: 2.0.5 + dev: true + + /nopt@1.0.10: + resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + dev: true + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + + /simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: true + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /touch@3.1.0: + resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} + hasBin: true + dependencies: + nopt: 1.0.10 + dev: true + + /undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true From 74ea0db238c33f54df9b66fdff4e7ce2241a838b Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 20:49:26 +0000 Subject: [PATCH 07/21] Remove reassignment in direction __getattribute__ override --- 2023-Oct-18/Dave/direction.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/2023-Oct-18/Dave/direction.py b/2023-Oct-18/Dave/direction.py index 1f54ace..153331a 100644 --- a/2023-Oct-18/Dave/direction.py +++ b/2023-Oct-18/Dave/direction.py @@ -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 From 3690887e7eb0b8cc5a393244d69a8f8bf4b99cb1 Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 20:50:26 +0000 Subject: [PATCH 08/21] Replace __repr__ with __str__ in WordGrid class --- 2023-Oct-18/Dave/word_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2023-Oct-18/Dave/word_grid.py b/2023-Oct-18/Dave/word_grid.py index 04adf15..320bab8 100644 --- a/2023-Oct-18/Dave/word_grid.py +++ b/2023-Oct-18/Dave/word_grid.py @@ -10,7 +10,7 @@ def __init__(self, rows: int, cols: int): self.cols = cols self.initialise_word_grid() - def __repr__(self): + def __str__(self): return self.get_stringified_word_grid() def __iter__(self): From 05fc53c93f42dc6a9cd98831a981d6f2092ea52e Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 20:55:50 +0000 Subject: [PATCH 09/21] Remove confusing __iter__ in WordGrid class --- 2023-Oct-18/Dave/main.py | 2 +- 2023-Oct-18/Dave/word_grid.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/2023-Oct-18/Dave/main.py b/2023-Oct-18/Dave/main.py index 4f9d50e..a4e3bc4 100644 --- a/2023-Oct-18/Dave/main.py +++ b/2023-Oct-18/Dave/main.py @@ -62,7 +62,7 @@ def get_all_legal_placements_for_word( 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) diff --git a/2023-Oct-18/Dave/word_grid.py b/2023-Oct-18/Dave/word_grid.py index 320bab8..f96c37d 100644 --- a/2023-Oct-18/Dave/word_grid.py +++ b/2023-Oct-18/Dave/word_grid.py @@ -13,9 +13,6 @@ def __init__(self, rows: int, cols: int): def __str__(self): return self.get_stringified_word_grid() - def __iter__(self): - for row in self.word_grid: - yield row def initialise_word_grid(self): self.word_grid = [[" " for col in range( From 85c3c93a41e5a7de01025c117c7b2fb4ac46e3d6 Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 20:56:47 +0000 Subject: [PATCH 10/21] Clarify and standardise conditional in is_legal_placement --- 2023-Oct-18/Dave/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2023-Oct-18/Dave/main.py b/2023-Oct-18/Dave/main.py index a4e3bc4..e2f41e4 100644 --- a/2023-Oct-18/Dave/main.py +++ b/2023-Oct-18/Dave/main.py @@ -85,7 +85,7 @@ def get_all_legal_placements_for_word( 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 is not " "): return False return True From 05320b472cfaed126d7e86f36704f2d99c822f5c Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 21:01:06 +0000 Subject: [PATCH 11/21] Clarify initialise_word_grid code --- 2023-Oct-18/Dave/word_grid.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/2023-Oct-18/Dave/word_grid.py b/2023-Oct-18/Dave/word_grid.py index f96c37d..c0ad093 100644 --- a/2023-Oct-18/Dave/word_grid.py +++ b/2023-Oct-18/Dave/word_grid.py @@ -13,10 +13,16 @@ def __init__(self, rows: int, cols: int): def __str__(self): return self.get_stringified_word_grid() + def initialise_grid(self): + grid = [] - def initialise_word_grid(self): - self.word_grid = [[" " for col in range( - self.cols)] for row in range(self.rows)] + for row in range(self.rows): + grid_row = [] + for col in range(self.cols): + grid_row.append(" ") + grid.append(grid_row) + + self.grid = grid def get_stringified_word_grid(self) -> str: output = "" From 4df03ff05077d91748d8590c9303c11f1d462631 Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 21:02:11 +0000 Subject: [PATCH 12/21] Use join() instead of += in get_stringified_word_grid --- 2023-Oct-18/Dave/word_grid.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/2023-Oct-18/Dave/word_grid.py b/2023-Oct-18/Dave/word_grid.py index c0ad093..a3b7d20 100644 --- a/2023-Oct-18/Dave/word_grid.py +++ b/2023-Oct-18/Dave/word_grid.py @@ -26,10 +26,9 @@ def initialise_grid(self): def get_stringified_word_grid(self) -> str: output = "" - for row in self.word_grid: - for char in row: - output += char + " " - output += "\n" + for row in self.grid: + row_string = " ".join(row) + output += f"{row_string}\n" return output def read_line( From 667ea7baf9cc7a1eb4cc75efc166c8ede92c8cd5 Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 21:05:26 +0000 Subject: [PATCH 13/21] Add clarifying comment to Placement class --- 2023-Oct-18/Dave/placement.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/2023-Oct-18/Dave/placement.py b/2023-Oct-18/Dave/placement.py index 7ea2f54..ce3f6ab 100644 --- a/2023-Oct-18/Dave/placement.py +++ b/2023-Oct-18/Dave/placement.py @@ -8,6 +8,8 @@ class Placement: position: Point direction: Direction + # This allows us to unpack a placement with the syntax: + # position, direction = placement def __iter__(self): yield self.position yield self.direction From f811b83ee589a750a3281b392b7b86da0acafa98 Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 21:07:12 +0000 Subject: [PATCH 14/21] Replace None and bool returns with exceptions --- 2023-Oct-18/Dave/exceptions.py | 14 +++++++ 2023-Oct-18/Dave/main.py | 70 ++++++++++++++++++---------------- 2023-Oct-18/Dave/word_grid.py | 35 +++++++---------- 3 files changed, 66 insertions(+), 53 deletions(-) create mode 100644 2023-Oct-18/Dave/exceptions.py diff --git a/2023-Oct-18/Dave/exceptions.py b/2023-Oct-18/Dave/exceptions.py new file mode 100644 index 0000000..e89c7f1 --- /dev/null +++ b/2023-Oct-18/Dave/exceptions.py @@ -0,0 +1,14 @@ +class FailedToGenerateWordSearchError(Exception): + ... + + +class FailedToPlaceAllWordsError(Exception): + ... + + +class NoLegalPlacementsError(Exception): + ... + + +class GridOverflowError(Exception): + ... diff --git a/2023-Oct-18/Dave/main.py b/2023-Oct-18/Dave/main.py index e2f41e4..ce7c26e 100644 --- a/2023-Oct-18/Dave/main.py +++ b/2023-Oct-18/Dave/main.py @@ -1,64 +1,66 @@ 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=position, orientation=direction, data=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 @@ -67,9 +69,10 @@ def get_all_legal_placements_for_word( for direction in Direction: position = Point(row_index, col_index) - target_line = word_grid.read_line( - position, direction, len(word)) - if not target_line: + try: + target_line = word_grid.read_line( + position, direction, len(word)) + except GridOverflowError: continue line_can_be_placed = is_legal_placement( @@ -80,7 +83,10 @@ 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: diff --git a/2023-Oct-18/Dave/word_grid.py b/2023-Oct-18/Dave/word_grid.py index a3b7d20..238d494 100644 --- a/2023-Oct-18/Dave/word_grid.py +++ b/2023-Oct-18/Dave/word_grid.py @@ -2,13 +2,14 @@ import string from point import Point from direction import Direction +from exceptions import GridOverflowError class WordGrid: def __init__(self, rows: int, cols: int): self.rows = rows self.cols = cols - self.initialise_word_grid() + self.initialise_grid() def __str__(self): return self.get_stringified_word_grid() @@ -31,48 +32,40 @@ def get_stringified_word_grid(self) -> str: output += f"{row_string}\n" return output - def read_line( - self, position: Point, orientation: Direction, length: int - ) -> str | None: + def check_for_grid_overflow(self, row: int, col: int) -> None: + if row < 0 or row >= len(self.grid) or col < 0 or col >= len(self.grid[0]): + raise GridOverflowError() + + def read_line(self, position: Point, direction: Direction, length: int) -> str: result = "" current_row, current_col = position - next_row, next_col = orientation - grid_rows = len(self.word_grid) - grid_cols = len(self.word_grid[0]) + next_row, next_col = direction for i in range(length): - if not 0 <= current_row < len(self.word_grid): - return None - - if not 0 <= current_col < len(self.word_grid[0]): - return None - - result += self.word_grid[current_row][current_col] + self.check_for_grid_overflow(current_row, current_col) + result += self.grid[current_row][current_col] current_row += next_row current_col += next_col return result def write_line(self, position: Point, orientation: Direction, data: str) -> bool: - can_write_line = self.read_line(position, orientation, len(data)) - if not can_write_line: - return False - current_row, current_col = position next_row, next_col = orientation for char in data: - self.word_grid[current_row][current_col] = char + self.check_for_grid_overflow(current_row, current_col) + self.grid[current_row][current_col] = char current_row += next_row current_col += next_col return True def fill_blank_space(self): - for row_index, row in enumerate(self.word_grid): + for row_index, row in enumerate(self.grid): for col_index, char in enumerate(row): if char == " ": - self.word_grid[row_index][col_index] = random.choice( + self.grid[row_index][col_index] = random.choice( string.ascii_uppercase ) From af63a195ce8dd88295f49eabeb93aea17c164574 Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 21:07:32 +0000 Subject: [PATCH 15/21] Prepare for adding tests --- 2023-Oct-18/Dave/pyproject.toml | 4 ++++ 2023-Oct-18/Dave/requirements.txt | 1 + 2 files changed, 5 insertions(+) create mode 100644 2023-Oct-18/Dave/pyproject.toml create mode 100644 2023-Oct-18/Dave/requirements.txt diff --git a/2023-Oct-18/Dave/pyproject.toml b/2023-Oct-18/Dave/pyproject.toml new file mode 100644 index 0000000..5a34bea --- /dev/null +++ b/2023-Oct-18/Dave/pyproject.toml @@ -0,0 +1,4 @@ +[tool.pytest.ini_options] +pythonpath = [ + "." +] diff --git a/2023-Oct-18/Dave/requirements.txt b/2023-Oct-18/Dave/requirements.txt new file mode 100644 index 0000000..e079f8a --- /dev/null +++ b/2023-Oct-18/Dave/requirements.txt @@ -0,0 +1 @@ +pytest From 34a8ec78c7214cd60a5a73a18631c078aa3ababa Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 21:15:46 +0000 Subject: [PATCH 16/21] Improve consistency in parameter names --- 2023-Oct-18/Dave/main.py | 3 +-- 2023-Oct-18/Dave/word_grid.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/2023-Oct-18/Dave/main.py b/2023-Oct-18/Dave/main.py index ce7c26e..5d2834c 100644 --- a/2023-Oct-18/Dave/main.py +++ b/2023-Oct-18/Dave/main.py @@ -50,8 +50,7 @@ def place_words(word_grid: WordGrid, word_list: list[str]) -> WordGrid: try: placements = get_all_legal_placements_for_word(word_search, word) position, direction = random.choice(placements) - word_search.write_line( - position=position, orientation=direction, data=word) + word_search.write_line(position, direction, word) except NoLegalPlacementsError: raise FailedToPlaceAllWordsError() diff --git a/2023-Oct-18/Dave/word_grid.py b/2023-Oct-18/Dave/word_grid.py index 238d494..5793eea 100644 --- a/2023-Oct-18/Dave/word_grid.py +++ b/2023-Oct-18/Dave/word_grid.py @@ -50,9 +50,9 @@ def read_line(self, position: Point, direction: Direction, length: int) -> str: return result - def write_line(self, position: Point, orientation: Direction, data: str) -> bool: + def write_line(self, position: Point, direction: Direction, data: str) -> bool: current_row, current_col = position - next_row, next_col = orientation + next_row, next_col = direction for char in data: self.check_for_grid_overflow(current_row, current_col) From efddf536dccf44a768b5315a42f77e26eab7b09e Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 22:29:45 +0000 Subject: [PATCH 17/21] Add check for legal read-write to avoid excessive exceptions --- 2023-Oct-18/Dave/main.py | 12 +++++++----- 2023-Oct-18/Dave/word_grid.py | 36 ++++++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/2023-Oct-18/Dave/main.py b/2023-Oct-18/Dave/main.py index 5d2834c..33f7a74 100644 --- a/2023-Oct-18/Dave/main.py +++ b/2023-Oct-18/Dave/main.py @@ -68,12 +68,14 @@ def get_all_legal_placements_for_word( for direction in Direction: position = Point(row_index, col_index) - try: - target_line = word_grid.read_line( - position, direction, len(word)) - except GridOverflowError: + line_can_be_written = word_grid.is_legal_read_or_write( + 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 ) @@ -90,7 +92,7 @@ def get_all_legal_placements_for_word( 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 is not target_char) and (target_char is not " "): + if (char_to_write is not target_char) and (target_char != " "): return False return True diff --git a/2023-Oct-18/Dave/word_grid.py b/2023-Oct-18/Dave/word_grid.py index 5793eea..3b8d9d2 100644 --- a/2023-Oct-18/Dave/word_grid.py +++ b/2023-Oct-18/Dave/word_grid.py @@ -32,18 +32,42 @@ def get_stringified_word_grid(self) -> str: output += f"{row_string}\n" return output - def check_for_grid_overflow(self, row: int, col: int) -> None: - if row < 0 or row >= len(self.grid) or col < 0 or col >= len(self.grid[0]): - raise GridOverflowError() + def is_legal_read_or_write( + self, position: Point, direction: Direction, length: int + ) -> bool: + initial_row_index = position.row + initial_col_index = position.col + max_row_index = len(self.grid) - 1 + max_col_index = len(self.grid[0]) - 1 + + # A single character would have length 1 but no motion, hence multiplying + # the direction amount by length - 1 + total_row_motion = direction.row * (length - 1) + total_col_motion = direction.col * (length - 1) + + final_char_row_index = initial_row_index + total_row_motion + final_char_col_index = initial_col_index + total_col_motion + + if ( + final_char_row_index > max_row_index + or final_char_row_index < 0 + or final_char_col_index > max_col_index + or final_char_col_index < 0 + ): + return False + + return True def read_line(self, position: Point, direction: Direction, length: int) -> str: + if not self.is_legal_read_or_write(position, direction, length): + raise GridOverflowError() + result = "" current_row, current_col = position next_row, next_col = direction for i in range(length): - self.check_for_grid_overflow(current_row, current_col) result += self.grid[current_row][current_col] current_row += next_row current_col += next_col @@ -51,11 +75,13 @@ def read_line(self, position: Point, direction: Direction, length: int) -> str: return result def write_line(self, position: Point, direction: Direction, data: str) -> bool: + if not self.is_legal_read_or_write(position, direction, len(data)): + raise GridOverflowError() + current_row, current_col = position next_row, next_col = direction for char in data: - self.check_for_grid_overflow(current_row, current_col) self.grid[current_row][current_col] = char current_row += next_row current_col += next_col From 00684709eef09cda45c5f618288efb507aeb0084 Mon Sep 17 00:00:00 2001 From: David Jordan Date: Mon, 6 Nov 2023 22:48:11 +0000 Subject: [PATCH 18/21] Change method name to something more sensible --- 2023-Oct-18/Dave/main.py | 2 +- 2023-Oct-18/Dave/word_grid.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/2023-Oct-18/Dave/main.py b/2023-Oct-18/Dave/main.py index 33f7a74..b2a6cef 100644 --- a/2023-Oct-18/Dave/main.py +++ b/2023-Oct-18/Dave/main.py @@ -68,7 +68,7 @@ def get_all_legal_placements_for_word( for direction in Direction: position = Point(row_index, col_index) - line_can_be_written = word_grid.is_legal_read_or_write( + line_can_be_written = word_grid.is_valid_line( position, direction, len(word) ) if not line_can_be_written: diff --git a/2023-Oct-18/Dave/word_grid.py b/2023-Oct-18/Dave/word_grid.py index 3b8d9d2..e1f5e4e 100644 --- a/2023-Oct-18/Dave/word_grid.py +++ b/2023-Oct-18/Dave/word_grid.py @@ -32,9 +32,7 @@ def get_stringified_word_grid(self) -> str: output += f"{row_string}\n" return output - def is_legal_read_or_write( - self, position: Point, direction: Direction, length: int - ) -> bool: + def is_valid_line(self, position: Point, direction: Direction, length: int) -> bool: initial_row_index = position.row initial_col_index = position.col max_row_index = len(self.grid) - 1 @@ -59,7 +57,7 @@ def is_legal_read_or_write( return True def read_line(self, position: Point, direction: Direction, length: int) -> str: - if not self.is_legal_read_or_write(position, direction, length): + if not self.is_valid_line(position, direction, length): raise GridOverflowError() result = "" @@ -75,7 +73,7 @@ def read_line(self, position: Point, direction: Direction, length: int) -> str: return result def write_line(self, position: Point, direction: Direction, data: str) -> bool: - if not self.is_legal_read_or_write(position, direction, len(data)): + if not self.is_valid_line(position, direction, len(data)): raise GridOverflowError() current_row, current_col = position From d71ac00e1aa008fef8ddef1cf66d269deacb84a5 Mon Sep 17 00:00:00 2001 From: David Jordan Date: Wed, 8 Nov 2023 15:55:50 +0000 Subject: [PATCH 19/21] Make equality operators consistent --- 2023-Oct-18/Dave/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/2023-Oct-18/Dave/main.py b/2023-Oct-18/Dave/main.py index b2a6cef..2b4f1f1 100644 --- a/2023-Oct-18/Dave/main.py +++ b/2023-Oct-18/Dave/main.py @@ -25,7 +25,7 @@ def main(): exit(1) -def generate_word_search(rows: int, cols: int, word_list: list[str]) -> WordGrid | None: +def generate_word_search(rows: int, cols: int, word_list: list[str]) -> WordGrid: word_grid = WordGrid(rows, cols) attempts = 0 @@ -92,7 +92,7 @@ def get_all_legal_placements_for_word( 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 is not target_char) and (target_char != " "): + if (char_to_write != target_char) and (target_char != " "): return False return True From 170032563a5a86341eabdfbf16dd57bd6bfb977d Mon Sep 17 00:00:00 2001 From: David Jordan Date: Wed, 8 Nov 2023 16:01:35 +0000 Subject: [PATCH 20/21] Remove unnecessary keyword argument --- 2023-Oct-18/Dave/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2023-Oct-18/Dave/main.py b/2023-Oct-18/Dave/main.py index 2b4f1f1..d526e8f 100644 --- a/2023-Oct-18/Dave/main.py +++ b/2023-Oct-18/Dave/main.py @@ -16,7 +16,7 @@ def main(): word_list = ["PYTHON", "DOJO", "CODEHUB", "BRISTOL"] try: - word_search = generate_word_search(rows=6, cols=9, word_list=word_list) + word_search = generate_word_search(rows=6, cols=9, word_list) print(word_search) print("Find these words:") print(", ".join(word_list)) From d71bf541489b9fe2bd034d48069ffceda582b596 Mon Sep 17 00:00:00 2001 From: David Jordan Date: Wed, 8 Nov 2023 18:30:14 +0000 Subject: [PATCH 21/21] Revert keyword argument removal because Python says no --- 2023-Oct-18/Dave/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2023-Oct-18/Dave/main.py b/2023-Oct-18/Dave/main.py index d526e8f..2b4f1f1 100644 --- a/2023-Oct-18/Dave/main.py +++ b/2023-Oct-18/Dave/main.py @@ -16,7 +16,7 @@ def main(): word_list = ["PYTHON", "DOJO", "CODEHUB", "BRISTOL"] try: - word_search = generate_word_search(rows=6, cols=9, word_list) + word_search = generate_word_search(rows=6, cols=9, word_list=word_list) print(word_search) print("Find these words:") print(", ".join(word_list))