mirror of
https://codeberg.org/cblanken/boggler.git
synced 2026-07-26 11:35:10 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a764a41ff6 | ||
|
|
6dba9313d5 |
@@ -18,7 +18,7 @@ To use the script to solve a Boggle board, you'll need to do a few things first.
|
|||||||
d,f,e,y
|
d,f,e,y
|
||||||
n,m,e,qu
|
n,m,e,qu
|
||||||
```
|
```
|
||||||
2. Find or create a dictionary wordlist file
|
2. Find or create a dictionary wordlist file or create your own
|
||||||
|
|
||||||
The dictionary wordlist should have each word on a single line like so
|
The dictionary wordlist should have each word on a single line like so
|
||||||
```console
|
```console
|
||||||
@@ -44,7 +44,7 @@ To use the script to solve a Boggle board, you'll need to do a few things first.
|
|||||||
|
|
||||||
3. Split the dictionary wordlist into separate files based on the first letter of each word.
|
3. Split the dictionary wordlist into separate files based on the first letter of each word.
|
||||||
|
|
||||||
To split an English wordlist, use the `split_wordlist_alpha.sh` script like so:
|
To split an English wordlist the `split_wordlist_alpha.sh` script can be used like so:
|
||||||
```console
|
```console
|
||||||
$ split_wordlist_alpha.sh my_wordlist.txt .
|
$ split_wordlist_alpha.sh my_wordlist.txt .
|
||||||
```
|
```
|
||||||
@@ -97,6 +97,7 @@ BOARD
|
|||||||
+---------------+
|
+---------------+
|
||||||
| O | W | H | A |
|
| O | W | H | A |
|
||||||
+---------------+
|
+---------------+
|
||||||
|
|
||||||
Starting @ (0, 0)
|
Starting @ (0, 0)
|
||||||
╭──────────┬──────────────────────────────────────────────────────────────────╮
|
╭──────────┬──────────────────────────────────────────────────────────────────╮
|
||||||
│ Word │ Path │
|
│ Word │ Path │
|
||||||
@@ -141,7 +142,8 @@ BOARD
|
|||||||
│ aia │ [(0, 1), (0, 2), (1, 2)] │
|
│ aia │ [(0, 1), (0, 2), (1, 2)] │
|
||||||
│ aias │ [(0, 1), (0, 2), (1, 2), (2, 3)] │
|
│ aias │ [(0, 1), (0, 2), (1, 2), (2, 3)] │
|
||||||
╰────────┴──────────────────────────────────────────────────╯
|
╰────────┴──────────────────────────────────────────────────╯
|
||||||
Starting @ (0,2)
|
Starting @ (0,
|
||||||
|
2)
|
||||||
╭──────┬──────╮
|
╭──────┬──────╮
|
||||||
│ Word │ Path │
|
│ Word │ Path │
|
||||||
├──────┼──────┤
|
├──────┼──────┤
|
||||||
@@ -252,7 +254,8 @@ Starting @ (0,2)
|
|||||||
│ yap │ [(1, 3), (1, 2), (0, 3)] │
|
│ yap │ [(1, 3), (1, 2), (0, 3)] │
|
||||||
│ yas │ [(1, 3), (1, 2), (2, 3)] │
|
│ yas │ [(1, 3), (1, 2), (2, 3)] │
|
||||||
╰──────┴──────────────────────────╯
|
╰──────┴──────────────────────────╯
|
||||||
Starting @ (2, 0)
|
Starting @ (2,
|
||||||
|
0)
|
||||||
╭──────┬──────╮
|
╭──────┬──────╮
|
||||||
│ Word │ Path │
|
│ Word │ Path │
|
||||||
├──────┼──────┤
|
├──────┼──────┤
|
||||||
@@ -388,4 +391,4 @@ Navigate to the project root folder and run the following.
|
|||||||
Run `poetry build`
|
Run `poetry build`
|
||||||
|
|
||||||
# License
|
# License
|
||||||
The included [wordlists](boggler/wordlists) are covered by their respective licenses. All other files [MIT](LICENSE) © Cameron Blankenbuehler
|
The included [wordlists](src/boggler/wordlists) are covered by their respective licenses. All other files MIT © Cameron Blankenbuehler
|
||||||
|
|||||||
Executable
+135
@@ -0,0 +1,135 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
class BoardCell:
|
||||||
|
"""Boggle Board cell"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, row: int, col: int, letters: str, adjacent_cells: list[BoardCell] = None
|
||||||
|
) -> None:
|
||||||
|
self.__row: int = row
|
||||||
|
self.__col: int = col
|
||||||
|
self.__pos: tuple[int, int] = (self.__row, self.__col)
|
||||||
|
self.__letters: str = letters
|
||||||
|
self.__adjacent_cells: list[BoardCell] = adjacent_cells
|
||||||
|
|
||||||
|
@property
|
||||||
|
def row(self) -> int:
|
||||||
|
"""Getter for row property"""
|
||||||
|
return self.__row
|
||||||
|
|
||||||
|
@property
|
||||||
|
def col(self) -> int:
|
||||||
|
"""Getter for col property"""
|
||||||
|
return self.__col
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pos(self) -> tuple[int, int]:
|
||||||
|
"""Getter for pos property"""
|
||||||
|
return self.__pos
|
||||||
|
|
||||||
|
@property
|
||||||
|
def letters(self) -> str:
|
||||||
|
"""Getter for letter property"""
|
||||||
|
return self.__letters
|
||||||
|
|
||||||
|
@property
|
||||||
|
def adjacent_cells(self) -> list[BoardCell]:
|
||||||
|
"""Getter for adjacent_cells property"""
|
||||||
|
return self.__adjacent_cells
|
||||||
|
|
||||||
|
@adjacent_cells.setter
|
||||||
|
def adjacent_cells(self, value):
|
||||||
|
self.__adjacent_cells = value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"({self.__row}, {self.__col}): {self.__letters}"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"(BoardCell({self.__row}, {self.__col}): {self.__letters}"
|
||||||
|
|
||||||
|
|
||||||
|
class BoggleBoard:
|
||||||
|
"""Boggle board structure"""
|
||||||
|
|
||||||
|
def __init__(self, board: list[list[str]], max_word_len: int = 14) -> None:
|
||||||
|
self.__height: int = len(board)
|
||||||
|
self.__width: int = len(board[0]) if self.__height > 0 else 0
|
||||||
|
self.__board_list = board
|
||||||
|
self.__board: dict[tuple[int, int], BoardCell] = {}
|
||||||
|
|
||||||
|
# Max word length is limited by size of the board
|
||||||
|
self.__max_word_len = min(max_word_len, self.__width * self.__height)
|
||||||
|
|
||||||
|
# Generate BoardCell for each position on the board
|
||||||
|
for row in range(0, self.__height):
|
||||||
|
for col in range(0, self.__width):
|
||||||
|
self.__board[(row, col)] = BoardCell(row, col, board[row][col])
|
||||||
|
|
||||||
|
# Update adjacent cell references for each BoardCell
|
||||||
|
for cell in self.__board.values():
|
||||||
|
adjacent_indexes = self.__get_adjacent_indexes(cell.row, cell.col)
|
||||||
|
cell.adjacent_cells = [self.__board[(x[0], x[1])] for x in adjacent_indexes]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
flattened_board_list = [y for x in self.__board_list for y in x]
|
||||||
|
max_len = len(max(flattened_board_list, key=len))
|
||||||
|
max_len = (
|
||||||
|
max_len + 1 if max_len % 2 == 0 else max_len + 2
|
||||||
|
) # keep header_len odd
|
||||||
|
header_len = self.__width * (max_len + 1) - 1
|
||||||
|
head = "-" * header_len
|
||||||
|
header = f"+{head}+\n"
|
||||||
|
body = ""
|
||||||
|
for row in self.__board_list:
|
||||||
|
body += "|"
|
||||||
|
for col in row:
|
||||||
|
body += f"{col.upper(): ^{max_len}}|"
|
||||||
|
body += "\n"
|
||||||
|
body += header
|
||||||
|
return f"{header}{body}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self) -> int:
|
||||||
|
"""Getter for height property"""
|
||||||
|
return self.__height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self) -> int:
|
||||||
|
"""Getter for width property"""
|
||||||
|
return self.__width
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_word_len(self) -> int:
|
||||||
|
"""Getter for maximum word length property"""
|
||||||
|
return self.__max_word_len
|
||||||
|
|
||||||
|
@property
|
||||||
|
def board(self) -> dict[tuple[int, int], BoardCell]:
|
||||||
|
"""Getter for board property"""
|
||||||
|
return self.__board
|
||||||
|
|
||||||
|
def __get_adjacent_indexes(self, row, col):
|
||||||
|
"""Return adjecency list for board of size `row x col`"""
|
||||||
|
indexes = []
|
||||||
|
if row > 0:
|
||||||
|
indexes.append((row - 1, col)) # up
|
||||||
|
if col > 0:
|
||||||
|
indexes.append((row - 1, col - 1)) # up-left
|
||||||
|
if col < self.width - 1:
|
||||||
|
indexes.append((row - 1, col + 1)) # up-right
|
||||||
|
if row < self.height - 1:
|
||||||
|
indexes.append((row + 1, col)) # down
|
||||||
|
if col > 0:
|
||||||
|
indexes.append((row + 1, col - 1)) # down-left
|
||||||
|
if col < self.width - 1:
|
||||||
|
indexes.append((row + 1, col + 1)) # down-right
|
||||||
|
if col > 0:
|
||||||
|
indexes.append((row, col - 1)) # left
|
||||||
|
if col < self.width - 1:
|
||||||
|
indexes.append((row, col + 1)) # right
|
||||||
|
return indexes
|
||||||
|
|
||||||
|
def get_cell(self, row: int, col: int) -> BoardCell:
|
||||||
|
"""Return the value at the specified row x column"""
|
||||||
|
return self.__board[(row, col)]
|
||||||
@@ -1,14 +1,8 @@
|
|||||||
"""Module to generate random Boggle boards for testing"""
|
"""Module to generate random Boggle boards for testing"""
|
||||||
|
|
||||||
from sys import argv, stderr
|
from sys import argv, stderr
|
||||||
from random import randint, shuffle
|
from random import randint, shuffle
|
||||||
from math import sqrt, floor
|
from math import sqrt, floor
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, TypeAlias
|
|
||||||
|
|
||||||
|
|
||||||
Die: TypeAlias = List[str]
|
|
||||||
Dice: TypeAlias = List[Die]
|
|
||||||
|
|
||||||
|
|
||||||
def read_dice_file(dice_path: Path):
|
def read_dice_file(dice_path: Path):
|
||||||
@@ -17,17 +11,17 @@ def read_dice_file(dice_path: Path):
|
|||||||
return [line.rstrip().split(",") for line in file.readlines() if line[0] != "#"]
|
return [line.rstrip().split(",") for line in file.readlines() if line[0] != "#"]
|
||||||
|
|
||||||
|
|
||||||
def roll_die(die: Die):
|
def roll_die(die: str):
|
||||||
"""Return a face of the given die string to simulate rolling a die"""
|
"""Return a face of the given die string to simulate rolling a die"""
|
||||||
return str(die[randint(0, len(die) - 1)])
|
return str(die[randint(0, len(die) - 1)])
|
||||||
|
|
||||||
|
|
||||||
def roll_dice(dice: Dice):
|
def roll_dice(dice: list[str]):
|
||||||
"""Return a random roll for each die"""
|
"""Return a random roll for each die"""
|
||||||
return [roll_die(die) for die in dice]
|
return [roll_die(die) for die in dice]
|
||||||
|
|
||||||
|
|
||||||
def get_random_board(dice: Dice):
|
def get_random_board(dice: list[str]):
|
||||||
shuffle(dice)
|
shuffle(dice)
|
||||||
rolls = roll_dice(dice)
|
rolls = roll_dice(dice)
|
||||||
|
|
||||||
@@ -39,7 +33,7 @@ def get_random_board(dice: Dice):
|
|||||||
return board
|
return board
|
||||||
|
|
||||||
|
|
||||||
def get_random_board_csv(dice: Dice):
|
def get_random_board_csv(dice: list[str]):
|
||||||
board = get_random_board(dice)
|
board = get_random_board(dice)
|
||||||
board = [",".join(row) for row in board]
|
board = [",".join(row) for row in board]
|
||||||
return board
|
return board
|
||||||
@@ -55,4 +49,4 @@ if __name__ == "__main__":
|
|||||||
for r in range(0, int(floor(sqrt(len(dice))))):
|
for r in range(0, int(floor(sqrt(len(dice))))):
|
||||||
print(board[r])
|
print(board[r])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Argument must be a valid file path.", file=stderr)
|
print("Argument must be a valid file path!", file=stderr)
|
||||||
|
|||||||
+34
-155
@@ -1,145 +1,11 @@
|
|||||||
"""Boggler Utils"""
|
"""Boggler Utils"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
import logging as log
|
import logging as log
|
||||||
import functools
|
import functools
|
||||||
import operator
|
import operator
|
||||||
|
from .board import BoardCell, BoggleBoard
|
||||||
|
|
||||||
class BoardCell:
|
|
||||||
"""Boggle Board cell"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, row: int, col: int, letters: str, adjacent_cells: list[BoardCell] = None
|
|
||||||
) -> None:
|
|
||||||
self.__row: int = row
|
|
||||||
self.__col: int = col
|
|
||||||
self.__pos: tuple[int, int] = (self.__row, self.__col)
|
|
||||||
self.__letters: str = letters
|
|
||||||
self.__adjacent_cells: list[BoardCell] = adjacent_cells
|
|
||||||
|
|
||||||
@property
|
|
||||||
def row(self) -> int:
|
|
||||||
"""Getter for row property"""
|
|
||||||
return self.__row
|
|
||||||
|
|
||||||
@property
|
|
||||||
def col(self) -> int:
|
|
||||||
"""Getter for col property"""
|
|
||||||
return self.__col
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pos(self) -> tuple[int, int]:
|
|
||||||
"""Getter for pos property"""
|
|
||||||
return self.__pos
|
|
||||||
|
|
||||||
@property
|
|
||||||
def letters(self) -> str:
|
|
||||||
"""Getter for letter property"""
|
|
||||||
return self.__letters
|
|
||||||
|
|
||||||
@property
|
|
||||||
def adjacent_cells(self) -> list[BoardCell]:
|
|
||||||
"""Getter for adjacent_cells property"""
|
|
||||||
return self.__adjacent_cells
|
|
||||||
|
|
||||||
@adjacent_cells.setter
|
|
||||||
def adjacent_cells(self, value):
|
|
||||||
self.__adjacent_cells = value
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"({self.__row}, {self.__col}): {self.__letters}"
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"(BoardCell({self.__row}, {self.__col}): {self.__letters}"
|
|
||||||
|
|
||||||
|
|
||||||
class BoggleBoard:
|
|
||||||
"""Boggle board structure"""
|
|
||||||
|
|
||||||
def __init__(self, board: list[list[str]], max_word_len: int = 14) -> None:
|
|
||||||
self.__height: int = len(board)
|
|
||||||
self.__width: int = len(board[0]) if self.__height > 0 else 0
|
|
||||||
self.__board_list = board
|
|
||||||
self.__board: dict[tuple[int, int], BoardCell] = {}
|
|
||||||
|
|
||||||
# Max word length is limited by size of the board
|
|
||||||
self.__max_word_len = min(max_word_len, self.__width * self.__height)
|
|
||||||
|
|
||||||
# Generate BoardCell for each position on the board
|
|
||||||
for row in range(0, self.__height):
|
|
||||||
for col in range(0, self.__width):
|
|
||||||
self.__board[(row, col)] = BoardCell(row, col, board[row][col])
|
|
||||||
|
|
||||||
# Update adjacent cell references for each BoardCell
|
|
||||||
for cell in self.__board.values():
|
|
||||||
adjacent_indexes = self.__get_adjacent_indexes(cell.row, cell.col)
|
|
||||||
cell.adjacent_cells = [self.__board[(x[0], x[1])] for x in adjacent_indexes]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
flattened_board_list = [y for x in self.__board_list for y in x]
|
|
||||||
max_len = len(max(flattened_board_list, key=len))
|
|
||||||
max_len = (
|
|
||||||
max_len + 1 if max_len % 2 == 0 else max_len + 2
|
|
||||||
) # keep header_len odd
|
|
||||||
header_len = self.__width * (max_len + 1) - 1
|
|
||||||
head = "-" * header_len
|
|
||||||
header = f"+{head}+\n"
|
|
||||||
body = ""
|
|
||||||
for row in self.__board_list:
|
|
||||||
body += "|"
|
|
||||||
for col in row:
|
|
||||||
body += f"{col.upper(): ^{max_len}}|"
|
|
||||||
body += "\n"
|
|
||||||
body += header
|
|
||||||
return f"{header}{body}"
|
|
||||||
|
|
||||||
@functools.cached_property
|
|
||||||
def height(self) -> int:
|
|
||||||
"""Getter for height property"""
|
|
||||||
return self.__height
|
|
||||||
|
|
||||||
@functools.cached_property
|
|
||||||
def width(self) -> int:
|
|
||||||
"""Getter for width property"""
|
|
||||||
return self.__width
|
|
||||||
|
|
||||||
@functools.cached_property
|
|
||||||
def max_word_len(self) -> int:
|
|
||||||
"""Getter for maximum word length property"""
|
|
||||||
return self.__max_word_len
|
|
||||||
|
|
||||||
@functools.cached_property
|
|
||||||
def board(self) -> dict[tuple[int, int], BoardCell]:
|
|
||||||
"""Getter for board property"""
|
|
||||||
return self.__board
|
|
||||||
|
|
||||||
def __get_adjacent_indexes(self, row, col):
|
|
||||||
"""Return adjecency list for board of size `row x col`"""
|
|
||||||
indexes = []
|
|
||||||
if row > 0:
|
|
||||||
indexes.append((row - 1, col)) # up
|
|
||||||
if col > 0:
|
|
||||||
indexes.append((row - 1, col - 1)) # up-left
|
|
||||||
if col < self.width - 1:
|
|
||||||
indexes.append((row - 1, col + 1)) # up-right
|
|
||||||
if row < self.height - 1:
|
|
||||||
indexes.append((row + 1, col)) # down
|
|
||||||
if col > 0:
|
|
||||||
indexes.append((row + 1, col - 1)) # down-left
|
|
||||||
if col < self.width - 1:
|
|
||||||
indexes.append((row + 1, col + 1)) # down-right
|
|
||||||
if col > 0:
|
|
||||||
indexes.append((row, col - 1)) # left
|
|
||||||
if col < self.width - 1:
|
|
||||||
indexes.append((row, col + 1)) # right
|
|
||||||
return indexes
|
|
||||||
|
|
||||||
def get_cell(self, row: int, col: int) -> BoardCell:
|
|
||||||
"""Return the value at the specified row x column"""
|
|
||||||
return self.__board[(row, col)]
|
|
||||||
|
|
||||||
|
|
||||||
class WordNode:
|
class WordNode:
|
||||||
@@ -159,16 +25,11 @@ class WordNode:
|
|||||||
self.__parent = parent
|
self.__parent = parent
|
||||||
self.__board_pos = board_pos
|
self.__board_pos = board_pos
|
||||||
|
|
||||||
@functools.cached_property
|
@property
|
||||||
def letters(self) -> str:
|
def letters(self) -> str:
|
||||||
"""Getter for letter property"""
|
"""Getter for letter property"""
|
||||||
return self.__letters
|
return self.__letters
|
||||||
|
|
||||||
@functools.cached_property
|
|
||||||
def letters_count(self) -> int:
|
|
||||||
"""Getter for letters count"""
|
|
||||||
return len(self.__letters)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_word(self) -> bool:
|
def is_word(self) -> bool:
|
||||||
"""Getter for is_word property"""
|
"""Getter for is_word property"""
|
||||||
@@ -178,7 +39,7 @@ class WordNode:
|
|||||||
def is_word(self, value):
|
def is_word(self, value):
|
||||||
self.__is_word = value
|
self.__is_word = value
|
||||||
|
|
||||||
@functools.cached_property
|
@property
|
||||||
def children(self) -> dict[str, WordNode]:
|
def children(self) -> dict[str, WordNode]:
|
||||||
"""Getter for children property"""
|
"""Getter for children property"""
|
||||||
return self.__children
|
return self.__children
|
||||||
@@ -227,7 +88,7 @@ class WordTree:
|
|||||||
self,
|
self,
|
||||||
alphabet: list[str],
|
alphabet: list[str],
|
||||||
root: WordNode,
|
root: WordNode,
|
||||||
words: list[str] | None = None,
|
words: list[str] = None,
|
||||||
max_word_len=16,
|
max_word_len=16,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.__alphabet = alphabet
|
self.__alphabet = alphabet
|
||||||
@@ -246,27 +107,27 @@ class WordTree:
|
|||||||
for word in words:
|
for word in words:
|
||||||
self.__insert_word(word)
|
self.__insert_word(word)
|
||||||
|
|
||||||
@functools.cached_property
|
@property
|
||||||
def alphabet(self) -> list:
|
def alphabet(self) -> list:
|
||||||
"""Getter for alphabet property"""
|
"""Getter for alphabet property"""
|
||||||
return self.__alphabet
|
return self.__alphabet
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wordlist(self) -> list[str] | None:
|
def wordlist(self) -> list[str]:
|
||||||
"""Getter for wordlist property"""
|
"""Getter for wordlist property"""
|
||||||
return self.__wordlist
|
return self.__wordlist
|
||||||
|
|
||||||
@functools.cached_property
|
@property
|
||||||
def root(self) -> WordNode:
|
def root(self) -> WordNode:
|
||||||
"""Getter for root property"""
|
"""Getter for root property"""
|
||||||
return self.__root
|
return self.__root
|
||||||
|
|
||||||
@functools.cached_property
|
@property
|
||||||
def max_word_len(self) -> int:
|
def max_word_len(self) -> int:
|
||||||
"""Getter for max_word_len property"""
|
"""Getter for max_word_len property"""
|
||||||
return self.__max_word_len
|
return self.__max_word_len
|
||||||
|
|
||||||
@functools.cached_property
|
@property
|
||||||
def tree(self) -> dict[str, WordNode]:
|
def tree(self) -> dict[str, WordNode]:
|
||||||
"""Getter for tree property"""
|
"""Getter for tree property"""
|
||||||
return self.__tree
|
return self.__tree
|
||||||
@@ -295,6 +156,7 @@ class WordTree:
|
|||||||
board_pos=None,
|
board_pos=None,
|
||||||
):
|
):
|
||||||
"""Create WordNode for `letters` and into WordTree under `parent`"""
|
"""Create WordNode for `letters` and into WordTree under `parent`"""
|
||||||
|
log.debug("inserting node: %s", letters)
|
||||||
node = WordNode(letters, is_word, parent, children, board_pos)
|
node = WordNode(letters, is_word, parent, children, board_pos)
|
||||||
parent.add_child_node(node)
|
parent.add_child_node(node)
|
||||||
|
|
||||||
@@ -303,31 +165,41 @@ class WordTree:
|
|||||||
otherwise returns False"""
|
otherwise returns False"""
|
||||||
|
|
||||||
# Insert root node
|
# Insert root node
|
||||||
prefix = word[0 : self.root.letters_count] # prefix = first letter block
|
prefix = word[0 : len(self.root.letters)] # prefix = first letter block
|
||||||
if word is None or prefix != self.root.letters:
|
if word is None or prefix != self.root.letters:
|
||||||
return False
|
return False
|
||||||
curr_node = self.tree[prefix]
|
curr_node = self.tree[prefix]
|
||||||
word_len = len(word)
|
word_len = len(word)
|
||||||
|
|
||||||
i = len(prefix)
|
i = len(prefix)
|
||||||
i_max = min(self.max_word_len, word_len)
|
i_max = min(self.max_word_len, len(word))
|
||||||
while i < i_max:
|
while i < i_max:
|
||||||
letters = word[i]
|
letters = word[i]
|
||||||
|
log.debug(
|
||||||
|
"1 letter seq: %s %s %s", word, letters, letters in curr_node.children
|
||||||
|
)
|
||||||
if letters not in self.alphabet:
|
if letters not in self.alphabet:
|
||||||
# Check two letter sequences like ("Qu", "Th", etc.) at current index
|
# Check two letter sequences like ("Qu", "Th", etc.) at current index
|
||||||
letters = word[i : i + 2]
|
letters = word[i : i + 2]
|
||||||
|
log.debug(
|
||||||
|
"2 letter seq: %s %s %s",
|
||||||
|
word,
|
||||||
|
letters,
|
||||||
|
letters in curr_node.children,
|
||||||
|
)
|
||||||
if letters not in self.alphabet:
|
if letters not in self.alphabet:
|
||||||
return False
|
return False
|
||||||
# Insert node
|
# Insert node
|
||||||
if letters not in curr_node.children:
|
if letters not in curr_node.children:
|
||||||
self.insert_node(letters, curr_node)
|
self.insert_node(letters, curr_node)
|
||||||
|
log.debug("3 letter seq: %s %s", word, letters)
|
||||||
|
|
||||||
curr_node = curr_node.children[letters]
|
curr_node = curr_node.children[letters]
|
||||||
|
|
||||||
i += len(letters)
|
i += len(letters)
|
||||||
|
|
||||||
# Mark the last node as a word
|
# Mark the last node as a word
|
||||||
curr_node.is_word = word_len <= self.max_word_len
|
curr_node.is_word = len(word) <= self.max_word_len
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def search(self, word: str, curr_node=None) -> WordNode:
|
def search(self, word: str, curr_node=None) -> WordNode:
|
||||||
@@ -368,7 +240,7 @@ class WordTree:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if word_len > self.max_word_len or word_len >= board.max_word_len:
|
if word_len > self.max_word_len or word_len >= board.max_word_len:
|
||||||
# MAX WORD LENGTH REACHED!
|
log.debug(f"MAX WORD LENGTH REACHED! len = {word_len}")
|
||||||
subtree.active_node = subtree.active_node.parent
|
subtree.active_node = subtree.active_node.parent
|
||||||
self.active_node = self.active_node.parent
|
self.active_node = self.active_node.parent
|
||||||
return subtree
|
return subtree
|
||||||
@@ -377,15 +249,22 @@ class WordTree:
|
|||||||
if self.active_node.is_word and len(self.active_node.children) == 0:
|
if self.active_node.is_word and len(self.active_node.children) == 0:
|
||||||
word_path = subtree.active_node.path[::-1]
|
word_path = subtree.active_node.path[::-1]
|
||||||
subtree.word_paths.append((subtree.active_node.get_word(board), word_path))
|
subtree.word_paths.append((subtree.active_node.get_word(board), word_path))
|
||||||
|
log.debug(
|
||||||
# Word found
|
"1: WORD FOUND: %s %s",
|
||||||
|
"".join([board.board[x].letters for x in word_path]),
|
||||||
|
word_path,
|
||||||
|
)
|
||||||
self.active_node = self.active_node.parent
|
self.active_node = self.active_node.parent
|
||||||
subtree.active_node = subtree.active_node.parent
|
subtree.active_node = subtree.active_node.parent
|
||||||
return subtree
|
return subtree
|
||||||
elif self.active_node.is_word:
|
elif self.active_node.is_word:
|
||||||
# Word found
|
|
||||||
word_path = subtree.active_node.path[::-1]
|
word_path = subtree.active_node.path[::-1]
|
||||||
subtree.word_paths.append((subtree.active_node.get_word(board), word_path))
|
subtree.word_paths.append((subtree.active_node.get_word(board), word_path))
|
||||||
|
log.debug(
|
||||||
|
"2: WORD FOUND: %s %s",
|
||||||
|
"".join([board.board[x].letters for x in word_path]),
|
||||||
|
word_path,
|
||||||
|
)
|
||||||
|
|
||||||
# Branch for each adjacent board cell
|
# Branch for each adjacent board cell
|
||||||
for cell in board_cell.adjacent_cells:
|
for cell in board_cell.adjacent_cells:
|
||||||
|
|||||||
Generated
+12
-1
@@ -353,6 +353,17 @@ files = [
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
plugins = ["importlib-metadata"]
|
plugins = ["importlib-metadata"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygtrie"
|
||||||
|
version = "2.5.0"
|
||||||
|
description = "A pure Python trie data structure implementation."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "pygtrie-2.5.0-py3-none-any.whl", hash = "sha256:8795cda8105493d5ae159a5bef313ff13156c5d4d72feddefacaad59f8c8ce16"},
|
||||||
|
{file = "pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pylint"
|
name = "pylint"
|
||||||
version = "2.17.4"
|
version = "2.17.4"
|
||||||
@@ -609,4 +620,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "ebdcc3cb72dd9c6dc736b31d92471656c82dc96ec6b9e67bf2886d5ca1c4f499"
|
content-hash = "167dc1b59d6da393661109af959693c455aa06c40c18564e16684920f259ae95"
|
||||||
|
|||||||
+20
-9
@@ -1,23 +1,34 @@
|
|||||||
|
[project]
|
||||||
|
name = "boggler"
|
||||||
|
version = "2.0.1"
|
||||||
|
authors = [
|
||||||
|
{ name="Cameron Blankenbuehler", email="cameron.blankenbuehler@gmail.com" },
|
||||||
|
]
|
||||||
|
description = "Utilities for solving the Boggle word game."
|
||||||
|
readme="README.md"
|
||||||
|
license = { file="LICENSE" }
|
||||||
|
requires-python = ">=3.7"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
"Homepage" = "https://github.com/cblanken/boggler"
|
"Homepage" = "https://github.com/cblanken/boggler"
|
||||||
"Bug Tracker" = "https://github.com/cblanken/boggler/issues"
|
"Bug Tracker" = "https://github.com/cblanken/boggler/issues"
|
||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "boggler"
|
name = "boggler"
|
||||||
version = "2.0.4"
|
version = "2.0.1"
|
||||||
description = "Utilities for solving the Boggle word game."
|
description = "Utilities for solving the Boggle word game."
|
||||||
authors = ["Cameron Blankenbuehler <cameron.blankenbuehler@gmail.com>"]
|
authors = ["Cameron Blankenbuehler <cameron.blankenbuehler@protonmail.com>"]
|
||||||
license = "LICENSE"
|
|
||||||
classifiers = [
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"License :: OSI Approved :: MIT License",
|
|
||||||
"Operating System :: POSIX :: Linux",
|
|
||||||
]
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8"
|
python = "^3.11"
|
||||||
rich = "^13.4.1"
|
rich = "^13.4.1"
|
||||||
|
pygtrie = "^2.5.0"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
boggler = "boggler.__main__:main"
|
boggler = "boggler.__main__:main"
|
||||||
|
|||||||
Reference in New Issue
Block a user