Files
boggler/boggler_utils.py
T

230 lines
7.8 KiB
Python

'''Boggler Utils'''
from __future__ import annotations
class BoardCell:
'''Boggle Board cell'''
def __init__(self, row: int, col: int, letter: str, adjacent_indexes: list[list[str]]) -> BoardCell:
self.__row = row
self.__col = col
self.__letter = letter
self.__adjacent_indexes = adjacent_indexes
@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 letter(self) -> str:
'''Getter for letter property'''
return self.__letter
@property
def adjacent_indexes(self) -> list[list[str]]:
'''Getter for adjacent_indexes property'''
return self.__row
def __str__(self):
return f"({self.__row}, {self.__col}: {self.__letter}. Adjacent nodes: {self.__adjacent_indexes})"
class BoggleBoard:
'''Boggle board structure'''
def __init__(self, board: list[list[str]], max_word_len: int = 14) -> BoggleBoard:
self.__height: int = len(board)
self.__width: int = len(board[0]) if self.__height > 0 else 0
self.__max_word_len = min(max_word_len, self.__width * self.__height) # max word length is determined by size of the board
self.__board = {}
for row in range(0, self.__height):
for col in range(0, self.__width):
#self.adjacency_dict[(row, col)] = self.__get_adjacent_indexes(row, col)
adjacent_indexes = self.__get_adjacent_indexes(row, col)
self.__board[(row, col)] = BoardCell(row, col, board[row][col], adjacent_indexes)
@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((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:
indexes.append((row-1, col+1)) # up-right
if row < self.__height:
indexes.append((row+1, col)) # down
if col > 0:
indexes.append((row+1, col-1)) # down-left
if col < self.__width:
indexes.append((row+1, col+1)) # down-right
if col > 0:
indexes.append((row, col-1)) # left
if col < self.__width:
indexes.append((row, col+1)) # right
return indexes
def get_letter_at(self, row: int, col: int) -> str:
'''Return the value at the specified row x column'''
return self.__board[row][col]
class WordNode:
'''A node that indicates a single letter in a word. Used to populate a WordTree'''
def __init__(self, char: str, is_word: bool, parent: WordNode = None, children: dict[WordNode] = None, board_pos = None) -> WordNode:
self.char = char
self.is_word = is_word
self.children = children if children is not None else {}
self.parent = parent
self.board_pos = board_pos
def add_child_node(self, node):
'''Add child node to `children` dictionary, indexed by the nodes' `char`'''
self.children[node.char] = node
def __str__(self):
return f"WordNode: {self.char}, {self.is_word}, {self.children}"
def __repr__(self):
return self.__str__()
class WordTree:
'''A tree populated by WordNode(s) to complete words from a given root letter and wordlist'''
def __init__(self, alphabet: str, root: str, words: list[str] = None, max_word_len = 16) -> WordTree:
self.alphabet = alphabet
self.wordlist = words
self.root = root
self.active_node: WordNode = None
self.max_word_len = max_word_len
self.tree = {}
# Generate root node
self.tree[root] = WordNode(root, False)
# Populate tree from wordlist
for word in words:
self.__insert_word(word)
def __str__(self):
return ", ".join(self.wordlist)
def insert_letter(self, letter: str, parent: WordNode):
'''Create WordNode for `letter` and into WordTree under `parent`'''
parent.children[letter] = WordNode(letter, False, parent)
def __insert_word(self, word: str) -> bool:
'''Returns True if word was inserted into the tree, otherwise False'''
if word is None or word[0] != self.root:
return False
curr_node = self.tree[word[0]]
# print(word[0])
for letter in word[1:self.max_word_len]:
# Insert new WordNode for each letter that doesen't already exist in the tree
if letter not in curr_node.children:
self.insert_letter(letter, curr_node)
# print(letter)
curr_node = curr_node.children[letter]
# Mark the last node of the word
curr_node.is_word = len(word) <= self.max_word_len
print(f"Added: {word[:self.max_word_len]}")
return True
def search(self, word: str) -> bool:
'''Return True if a given word is in the tree otherwise return False'''
if len(word) == 0 or word is None:
return False
curr_node = self.tree[word[0]]
for char in word[1:]:
if char not in curr_node.children:
return False
curr_node = curr_node.children[char]
return curr_node.is_word
def generate_board_sub_tree(self, boggle_board: BoggleBoard, start_cell: BoardCell) -> WordTree:
'''Return subtree of board given a particular root (first letter)'''
if start_cell.letter != self.root:
print("ERROR: the start_cell and WordTree root must match!")
return None
sub_tree = WordTree(self.alphabet, self.root)
root = self.root
used_nodes = {}
sub_tree.active_node = start_cell
dict_ptr = self.tree[root]
# Build WordTree to max depth
for _ in range(0, min(boggle_board.max_word_len, self.max_word_len)):
adjacent_letters = [boggle_board.board[pos] for pos in sub_tree.active_node.adjacent_indexes]
for pos in sub_tree.active_node.adjacent_indexes:
letter = boggle_board.board[pos]
if pos not in used_nodes and letter in dict_ptr.children:
sub_tree.insert_letter(letter, sub_tree.active_node)
#sub_tree.active_node.children[letter] = dict_ptr.children[letter]
# TODO: find way to track used_nodes to avoid loops
# either check the word path by recursing up via the parents
# or pass it along, probably rework WordNode or BoardCell or make
# an intermediate object
# used_nodes
return sub_tree
if __name__ == "__main__":
sample_dict = [
'aero',
'anger',
'ant',
'ape',
'argue',
'ash',
'assuage',
'aunt',
'auspice',
'bigger',
'bit',
'bite',
'biter',
'cart',
'cast',
]
tree = WordTree("abcdefghijklmnopqrstuvwxyz", "a", sample_dict)
print(tree.search('anger'))
print(tree.search('argues'))
b1 = [
"aucd",
"eegh",
"hios",
"trea"
]
b1_board = BoggleBoard(b1)
sub_tree = tree.generate_board_sub_tree(b1_board, b1_board.board[(3, 3)])
print(sub_tree.search("anger"))