mirror of
https://codeberg.org/cblanken/boggler.git
synced 2026-07-25 19:19:20 -04:00
Track words found in WordTree(s) for easier display
This commit is contained in:
+16
-7
@@ -8,16 +8,25 @@ if __name__ == '__main__':
|
|||||||
print('Usage: python3 boggler.py <BOARD_FILE> <WORDLISTS_DIR> [MAX_WORD_LENGTH]')
|
print('Usage: python3 boggler.py <BOARD_FILE> <WORDLISTS_DIR> [MAX_WORD_LENGTH]')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
b1 = read_boggle_file(Path(sys.argv[1]))
|
board = read_boggle_file(Path(sys.argv[1]))
|
||||||
if len(sys.argv) == 3:
|
if len(sys.argv) == 3:
|
||||||
b1_board = BoggleBoard(b1)
|
boggle_board = BoggleBoard(board)
|
||||||
print(b1_board)
|
print(boggle_board)
|
||||||
boggle_tree = build_full_boggle_tree(b1_board, Path(sys.argv[2]))
|
boggle_tree = build_full_boggle_tree(boggle_board, Path(sys.argv[2]))
|
||||||
elif len(sys.argv) == 4:
|
elif len(sys.argv) == 4:
|
||||||
try :
|
try :
|
||||||
b1_board = BoggleBoard(b1, int(sys.argv[3]))
|
boggle_board = BoggleBoard(board, int(sys.argv[3]))
|
||||||
print(b1_board)
|
print(boggle_board)
|
||||||
boggle_tree = build_full_boggle_tree(b1_board, Path(sys.argv[2]))
|
boggle_tree = build_full_boggle_tree(boggle_board, Path(sys.argv[2]))
|
||||||
|
|
||||||
|
print("\nBOARD")
|
||||||
|
print(boggle_board)
|
||||||
|
|
||||||
|
for start_pos, tree in boggle_tree.items():
|
||||||
|
print(f"\nStarting @ {start_pos}...")
|
||||||
|
for word in tree.word_paths:
|
||||||
|
print(f"{word[0]: <{boggle_board.max_word_len}}: {word[1]}")
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print("The [MAX_WORD_LENGTH] argument must be an integer.")
|
print("The [MAX_WORD_LENGTH] argument must be an integer.")
|
||||||
print("Please try again.")
|
print("Please try again.")
|
||||||
|
|||||||
+44
-36
@@ -6,7 +6,8 @@ import sys
|
|||||||
|
|
||||||
class BoardCell:
|
class BoardCell:
|
||||||
'''Boggle Board cell'''
|
'''Boggle Board cell'''
|
||||||
def __init__(self, row: int, col: int, letters: str, adjacent_cells: list[BoardCell] = None) -> BoardCell:
|
def __init__(self, row: int, col: int, letters: str,
|
||||||
|
adjacent_cells: list[BoardCell] = None) -> BoardCell:
|
||||||
self.__row: int = row
|
self.__row: int = row
|
||||||
self.__col: int = col
|
self.__col: int = col
|
||||||
self.__pos: (int, int) = (self.__row, self.__col)
|
self.__pos: (int, int) = (self.__row, self.__col)
|
||||||
@@ -46,7 +47,7 @@ class BoardCell:
|
|||||||
return f"({self.__row}, {self.__col}): {self.__letters}"
|
return f"({self.__row}, {self.__col}): {self.__letters}"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"(BoardCell({self.__row}, {self.__col}): {self.__letters}; {self.__adjacent_cells})"
|
return f"(BoardCell({self.__row}, {self.__col}): {self.__letters}"
|
||||||
|
|
||||||
class BoggleBoard:
|
class BoggleBoard:
|
||||||
'''Boggle board structure'''
|
'''Boggle board structure'''
|
||||||
@@ -101,7 +102,7 @@ class BoggleBoard:
|
|||||||
return self.__max_word_len
|
return self.__max_word_len
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def board(self) -> dict((int, int), BoardCell):
|
def board(self) -> dict[(int, int), BoardCell]:
|
||||||
'''Getter for board property'''
|
'''Getter for board property'''
|
||||||
return self.__board
|
return self.__board
|
||||||
|
|
||||||
@@ -132,7 +133,8 @@ class BoggleBoard:
|
|||||||
|
|
||||||
class WordNode:
|
class WordNode:
|
||||||
'''A node describing a single letter in a WordTree.'''
|
'''A node describing a single letter in a WordTree.'''
|
||||||
def __init__(self, letters: str, is_word: bool = False, parent: WordNode = None, children: dict[str, WordNode] = None, board_pos = None) -> WordNode:
|
def __init__(self, letters: str, is_word: bool = False, parent: WordNode = None,
|
||||||
|
children: dict[str, WordNode] = None, board_pos = None) -> WordNode:
|
||||||
self.__letters = letters
|
self.__letters = letters
|
||||||
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 {}
|
||||||
@@ -184,21 +186,26 @@ class WordNode:
|
|||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
def get_word(self, board: BoggleBoard) -> str:
|
||||||
|
'''Return word or word fragment based on the WordNode's path property'''
|
||||||
|
return "".join([board.board[x].letters for x in self.path[::-1]])
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"WordNode: {self.letters}, {self.is_word}"
|
return f"WordNode: {self.letters}, {self.is_word}, {self.board_pos}"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"WordNode: {self.letters}, {self.is_word}, {self.children}"
|
return f"WordNode: {self.letters}, {self.is_word}, {self.children}"
|
||||||
|
|
||||||
class WordTree:
|
class WordTree:
|
||||||
'''A tree populated by WordNode(s) to complete words from a given root letter and wordlist'''
|
'''A tree populated by WordNode(s) to complete words from a given root letter and wordlist'''
|
||||||
def __init__(self, alphabet: str, root: WordNode, words: list[str] = None, max_word_len = 16) -> WordTree:
|
def __init__(self, alphabet: str, root: WordNode, words: list[str] = None,
|
||||||
|
max_word_len = 16) -> WordTree:
|
||||||
self.__alphabet: str = alphabet
|
self.__alphabet: str = alphabet
|
||||||
self.__wordlist: list[str] = words
|
self.__wordlist: list[str] = words
|
||||||
self.__root: WordNode = root
|
self.__root: WordNode = root
|
||||||
self.__max_word_len: int = max_word_len
|
self.__max_word_len: int = max_word_len
|
||||||
self.__tree: dict[str, WordNode] = {}
|
self.__tree: dict[str, WordNode] = {}
|
||||||
self.__leaf_nodes: dict[str, list[WordNode]] = []
|
self.__word_paths: dict[str, list[WordNode]] = []
|
||||||
|
|
||||||
# Generate root node
|
# Generate root node
|
||||||
self.__tree[root.letters] = root
|
self.__tree[root.letters] = root
|
||||||
@@ -235,27 +242,29 @@ class WordTree:
|
|||||||
return self.__tree
|
return self.__tree
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def leaf_nodes_by_word(self):
|
def word_paths(self) -> dict[(int, int), WordNode]:
|
||||||
'''Getter for leaf_nodes property'''
|
'''Getter for leaf_nodes property'''
|
||||||
return self.__leaf_nodes
|
return self.__word_paths
|
||||||
|
|
||||||
@leaf_nodes_by_word.setter
|
@word_paths.setter
|
||||||
def leaf_nodes_by_word(self, value):
|
def word_paths(self, value):
|
||||||
self.__leaf_nodes = value
|
self.__word_paths = value
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return ", ".join(self.wordlist)
|
return ", ".join(self.wordlist)
|
||||||
|
|
||||||
def insert_node(self, letters: str, parent: WordNode, is_word: bool = False, children: dict[str, WordNode] = None, board_pos = None):
|
def insert_node(self, letters: str, parent: WordNode, is_word: bool = False,
|
||||||
|
children: dict[str, WordNode] = None, board_pos = None):
|
||||||
'''Create WordNode for `letters` and into WordTree under `parent`'''
|
'''Create WordNode for `letters` and into WordTree under `parent`'''
|
||||||
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)
|
||||||
|
|
||||||
def __insert_word(self, word: str) -> bool:
|
def __insert_word(self, word: str) -> bool:
|
||||||
'''Returns True if the word could be inserted into the tree with the given alphabet, otherwise returns False'''
|
'''Returns True if the word could be inserted into the tree with the given alphabet,
|
||||||
|
otherwise returns False'''
|
||||||
|
|
||||||
# Insert root node
|
# Insert root node
|
||||||
prefix = word[0:len(self.root.letters)] # prefix is equivalent to the first letter block in a word
|
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]
|
||||||
@@ -275,7 +284,7 @@ class WordTree:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Insert letter (single)
|
# Insert letter (single)
|
||||||
if letter == self.alphabet[alpha_index] and letter not in curr_node.children: # letter == letter_group implies single letter
|
if letter == self.alphabet[alpha_index] and letter not in curr_node.children:
|
||||||
self.insert_node(letter, curr_node)
|
self.insert_node(letter, curr_node)
|
||||||
curr_node = curr_node.children[letter]
|
curr_node = curr_node.children[letter]
|
||||||
# Letter already exists
|
# Letter already exists
|
||||||
@@ -296,7 +305,7 @@ class WordTree:
|
|||||||
#print(f"LONG GROUP: {alpha}; {word}")
|
#print(f"LONG GROUP: {alpha}; {word}")
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Mark the last node as a word
|
# Mark the last node as a 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]}")
|
||||||
@@ -318,10 +327,8 @@ class WordTree:
|
|||||||
return curr_node
|
return curr_node
|
||||||
|
|
||||||
# TODO: implement tree walk
|
# TODO: implement tree walk
|
||||||
# def walk_tree(self):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
def build_boggle_tree(self, board: BoggleBoard, board_cell: BoardCell, subtree: WordTree, depth: int = 0) -> WordTree:
|
def build_boggle_tree(self, board: BoggleBoard, board_cell: BoardCell, subtree: WordTree, depth: int = 1) -> WordTree:
|
||||||
'''Return subtree of board given a particular root (first letter).
|
'''Return subtree of board given a particular root (first letter).
|
||||||
|
|
||||||
Keyword arguments:
|
Keyword arguments:
|
||||||
@@ -334,20 +341,20 @@ class WordTree:
|
|||||||
# TODO rework active_node refs for recursion so don't have to be reset to parent at every point of return
|
# TODO rework active_node refs for recursion so don't have to be reset to parent at every point of return
|
||||||
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]
|
||||||
#print("WORD FOUND:", "".join([board.board[x].letters for x in word_path]), word_path)
|
subtree.word_paths.append((subtree.active_node.get_word(board), word_path))
|
||||||
|
#print("1: WORD FOUND:", "".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
|
||||||
#subtree.leaf_nodes_by_word.append(self.active_node)
|
|
||||||
return
|
return
|
||||||
elif self.active_node.is_word:
|
elif self.active_node.is_word:
|
||||||
word_path = subtree.active_node.path[::-1]
|
word_path = subtree.active_node.path[::-1]
|
||||||
#print("WORD FOUND:", "".join([board.board[x].letters for x in word_path]), word_path)
|
subtree.word_paths.append((subtree.active_node.get_word(board), word_path))
|
||||||
#subtree.leaf_nodes_by_word.append(self.active_node)
|
#print("2: WORD FOUND:", "".join([board.board[x].letters for x in word_path]), word_path)
|
||||||
|
|
||||||
if depth > self.max_word_len:
|
if depth >= self.max_word_len or depth >= board.max_word_len:
|
||||||
print(f"MAX DEPTH REACHED! Depth = {depth}")
|
#print(f"MAX DEPTH REACHED! Depth = {depth}")
|
||||||
self.active_node = self.active_node.parent
|
|
||||||
subtree.active_node = subtree.active_node.parent
|
subtree.active_node = subtree.active_node.parent
|
||||||
|
self.active_node = self.active_node.parent
|
||||||
return
|
return
|
||||||
|
|
||||||
# Branch for each adjacent board cell
|
# Branch for each adjacent board cell
|
||||||
@@ -359,6 +366,7 @@ class WordTree:
|
|||||||
subtree.active_node = subtree.active_node.children[cell.letters] # update subtree pointer
|
subtree.active_node = subtree.active_node.children[cell.letters] # update subtree pointer
|
||||||
self.active_node = self.active_node.children[cell.letters] # update dictionary tree pointer
|
self.active_node = self.active_node.children[cell.letters] # update dictionary tree pointer
|
||||||
self.build_boggle_tree(board, board.board[cell.pos], subtree, depth=depth+1)
|
self.build_boggle_tree(board, board.board[cell.pos], subtree, depth=depth+1)
|
||||||
|
#print(f"depth: {depth}")
|
||||||
|
|
||||||
# print(f"DONE SEARCHING at {subtree.active_node}")
|
# print(f"DONE SEARCHING at {subtree.active_node}")
|
||||||
|
|
||||||
@@ -371,7 +379,6 @@ def build_full_boggle_tree(board: BoggleBoard, wordlist_path: str) -> dict[str,
|
|||||||
# TODO: implement multiprocessing / multitthreading for each start letter
|
# TODO: implement multiprocessing / multitthreading for each start letter
|
||||||
# TODO: benchmark on laptop and chromebook
|
# TODO: benchmark on laptop and chromebook
|
||||||
alphabet = sorted(set([cell.letters for cell in board.board.values()]))
|
alphabet = sorted(set([cell.letters for cell in board.board.values()]))
|
||||||
print(f"alphabet: {alphabet}")
|
|
||||||
board_tree = {}
|
board_tree = {}
|
||||||
index = {}
|
index = {}
|
||||||
|
|
||||||
@@ -402,12 +409,13 @@ def read_boggle_file(file):
|
|||||||
return [x.rstrip().split(',') for x in file.readlines()]
|
return [x.rstrip().split(',') for x in file.readlines()]
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
#b2 = read_boggle_file("boards/b1.csv")
|
#b2 = read_boggle_file(sys.argv[1])
|
||||||
b2 = read_boggle_file(sys.argv[1])
|
b2 = read_boggle_file("boards/b1.csv")
|
||||||
b2_board = BoggleBoard(b2)
|
b2_board = BoggleBoard(b2)
|
||||||
|
boggle_tree = build_full_boggle_tree(b2_board, "wordlists/dwyl")
|
||||||
|
print("\nBOARD")
|
||||||
print(b2_board)
|
print(b2_board)
|
||||||
#boggle_tree = build_full_boggle_tree(b2_board, "wordlists/dwyl")
|
|
||||||
#print("Search 'quay':", boggle_tree[(1,1)].search('quay').path)
|
w1 = boggle_tree[(0,0)].word_paths
|
||||||
#print("Search 'quip':", boggle_tree[(1,1)].search('quip').path)
|
for w in w1:
|
||||||
#print("Search 'squall':", boggle_tree[(0,0)].search('squall').path)
|
print(f"{w[0]: <{b2_board.max_word_len}}: {w[1]}")
|
||||||
#print("Search 'quash':", boggle_tree[(1,1)].search('quash').path)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user