mirror of
https://codeberg.org/cblanken/boggler.git
synced 2026-07-25 19:19:20 -04:00
Add Boggle board and cell abstractions
WIP: Building subtrees of boggle board based on dictionary
This commit is contained in:
+132
-18
@@ -1,10 +1,63 @@
|
|||||||
'''Dictionary Utils'''
|
'''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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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:
|
class WordNode:
|
||||||
'''A node that indicates a single letter in a word. Used to populate a WordTree'''
|
'''A node that indicates a single letter in a word. Used to populate a WordTree'''
|
||||||
def __init__(self, char: str, is_word: bool, children: dict['WordNode'] = None) -> 'WordNode':
|
def __init__(self, char: str, is_word: bool, parent: WordNode = None, children: dict[WordNode] = None) -> WordNode:
|
||||||
self.char = char
|
self.char = char
|
||||||
self.is_word = is_word
|
self.is_word = is_word
|
||||||
self.children = children if children is not None else {}
|
self.children = children if children is not None else {}
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
def add_child_node(self, node):
|
def add_child_node(self, node):
|
||||||
'''Add child node to `children` dictionary, indexed by the nodes' `char`'''
|
'''Add child node to `children` dictionary, indexed by the nodes' `char`'''
|
||||||
@@ -17,38 +70,51 @@ class WordNode:
|
|||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
class WordTree:
|
class WordTree:
|
||||||
'''A tree populated by WordNode(s) to complete words from a given wordlist'''
|
'''A tree populated by WordNode(s) to complete words from a given root letter and wordlist'''
|
||||||
def __init__(self, wordlist: list[str], alphabet: str, max_word_len: int = 16) -> WordNode:
|
def __init__(self, alphabet: str, root: str, words: list[str] = None, max_word_len = 16) -> WordTree:
|
||||||
|
self.alphabet = alphabet
|
||||||
self.wordlist = wordlist
|
self.wordlist = words
|
||||||
|
self.root = root
|
||||||
|
self.active_node: WordNode = None
|
||||||
self.max_word_len = max_word_len
|
self.max_word_len = max_word_len
|
||||||
# Generate root nodes for each letter of the alphabet
|
|
||||||
self.tree = {}
|
self.tree = {}
|
||||||
for char in alphabet:
|
|
||||||
self.tree[char] = WordNode(char, False)
|
# Generate root node
|
||||||
|
self.tree[root] = WordNode(root, False)
|
||||||
|
|
||||||
# Populate tree from wordlist
|
# Populate tree from wordlist
|
||||||
for word in wordlist:
|
for word in words:
|
||||||
self.__insert_word(word)
|
self.__insert_word(word)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return ", ".join(self.wordlist)
|
return ", ".join(self.wordlist)
|
||||||
|
|
||||||
def __insert_word(self, word):
|
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]]
|
curr_node = self.tree[word[0]]
|
||||||
# print(word[0])
|
# print(word[0])
|
||||||
for letter in word[1:self.max_word_len]:
|
for letter in word[1:self.max_word_len]:
|
||||||
# Insert new WordNode for each letter that doesen't already exist in the tree
|
# Insert new WordNode for each letter that doesen't already exist in the tree
|
||||||
if letter not in curr_node.children:
|
if letter not in curr_node.children:
|
||||||
curr_node.children[letter] = WordNode(letter, False)
|
self.__insert_letter(letter, curr_node)
|
||||||
# print(letter)
|
# print(letter)
|
||||||
curr_node = curr_node.children[letter]
|
curr_node = curr_node.children[letter]
|
||||||
# Mark the last node of the word
|
# Mark the last node of the word
|
||||||
curr_node.is_word = len(word) <= self.max_word_len
|
curr_node.is_word = len(word) <= self.max_word_len
|
||||||
print(f"Added: {word[:self.max_word_len]}")
|
print(f"Added: {word[:self.max_word_len]}")
|
||||||
|
return True
|
||||||
|
|
||||||
def search(self, word) -> bool:
|
def search(self, word: str) -> bool:
|
||||||
'''Return True if a given word is in the tree otherwise return False'''
|
'''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]]
|
curr_node = self.tree[word[0]]
|
||||||
for char in word[1:]:
|
for char in word[1:]:
|
||||||
if char not in curr_node.children:
|
if char not in curr_node.children:
|
||||||
@@ -58,17 +124,65 @@ class WordTree:
|
|||||||
|
|
||||||
return curr_node.is_word
|
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.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__":
|
if __name__ == "__main__":
|
||||||
tree = WordTree(['anger',
|
sample_dict = [
|
||||||
|
'aero',
|
||||||
|
'anger',
|
||||||
|
'ant',
|
||||||
'ape',
|
'ape',
|
||||||
|
'argue',
|
||||||
|
'ash',
|
||||||
|
'assuage',
|
||||||
|
'aunt',
|
||||||
|
'auspice',
|
||||||
'bigger',
|
'bigger',
|
||||||
'bit',
|
'bit',
|
||||||
'bite',
|
'bite',
|
||||||
'biter',
|
'biter',
|
||||||
'cart',
|
'cart',
|
||||||
'cast',
|
'cast',
|
||||||
], "abcdefghijklmnopqrstuvwxyz")
|
]
|
||||||
print(tree)
|
|
||||||
print(tree.search('cast'))
|
tree = WordTree("abcdefghijklmnopqrstuvwxyz", "a", sample_dict)
|
||||||
print(tree.search('hype'))
|
print(tree.search('anger'))
|
||||||
|
print(tree.search('argues'))
|
||||||
|
|
||||||
|
board = [
|
||||||
|
"aucd",
|
||||||
|
"eegh",
|
||||||
|
"hios",
|
||||||
|
"trea"
|
||||||
|
]
|
||||||
|
|
||||||
|
boggle_board = BoggleBoard(board)
|
||||||
|
sub_tree = tree.generate_board_sub_tree(boggle_board, boggle_board.board[(3, 3)])
|
||||||
|
print(sub_tree.search("anger"))
|
||||||
|
|||||||
Reference in New Issue
Block a user