mirror of
https://codeberg.org/cblanken/boggler.git
synced 2026-07-26 03:29:26 -04:00
Fix boggle tree missing multi-letter nodes
The BoggleTree object when inserting nodes did not properly check for nodes with values of more than 1 letter. Therefore, any words with "qu" after the root would not be included in the solved board. As of now, only nodes with a maximum of 2 letters are accounted for, 3+ letter nodes won't be checked.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from os import path
|
from os import path
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
from logging import info, debug, warn, error
|
import logging as log
|
||||||
import functools
|
import functools
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
@@ -258,6 +258,7 @@ class WordTree:
|
|||||||
def insert_node(self, letters: str, parent: WordNode, is_word: bool = False,
|
def insert_node(self, letters: str, parent: WordNode, is_word: bool = False,
|
||||||
children: dict[str, WordNode] = None, board_pos = None):
|
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`'''
|
||||||
|
log.debug("inserting node: ", 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)
|
||||||
|
|
||||||
@@ -272,41 +273,25 @@ class WordTree:
|
|||||||
curr_node = self.tree[prefix]
|
curr_node = self.tree[prefix]
|
||||||
word_len = len(word)
|
word_len = len(word)
|
||||||
|
|
||||||
# Insert remaining nodes as letter groups based on alphabet
|
i = len(prefix)
|
||||||
skip_cnt = 0
|
i_max = min(self.max_word_len, len(word))
|
||||||
for i, letter in enumerate(word[len(prefix):self.max_word_len]):
|
while i < i_max:
|
||||||
# Skip word letter iterations for length of a previously inserted letter group
|
letters = word[i]
|
||||||
if skip_cnt > 0:
|
log.debug("1 letter seq: ", word, letters, letters in curr_node.children)
|
||||||
skip_cnt -= 1
|
if letters not in self.alphabet:
|
||||||
continue
|
# Check two letter sequences like ("Qu", "Th", etc.) at current index
|
||||||
|
letters = word[i:i+2]
|
||||||
try:
|
log.debug("2 letter seq: ", word, letters, letters in curr_node.children)
|
||||||
alpha_index = self.alphabet.index(letter)
|
if letters not in self.alphabet:
|
||||||
except ValueError: # letters not in given alphabet
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Insert letter (single)
|
|
||||||
if letter == self.alphabet[alpha_index] and letter not in curr_node.children:
|
|
||||||
self.insert_node(letter, curr_node)
|
|
||||||
curr_node = curr_node.children[letter]
|
|
||||||
# Letter already exists
|
|
||||||
elif letter == self.alphabet[alpha_index]:
|
|
||||||
curr_node = curr_node.children[letter]
|
|
||||||
# Check for letter groups (like "Qu") starting with `letter` in alphabet
|
|
||||||
else:
|
|
||||||
alpha = self.alphabet[alpha_index]
|
|
||||||
# Check that letter group is shorter than and matches in the word remainder
|
|
||||||
if len(alpha) < word_len - i and alpha == word[i+1:i+1+len(alpha)]:
|
|
||||||
if alpha not in curr_node.children:
|
|
||||||
self.insert_node(alpha, curr_node )
|
|
||||||
curr_node = curr_node.children[alpha]
|
|
||||||
else: # node already exist
|
|
||||||
curr_node = curr_node.children[alpha]
|
|
||||||
|
|
||||||
skip_cnt = len(alpha) - 1
|
|
||||||
debug(f"LONG GROUP: {alpha}; {word}")
|
|
||||||
else:
|
|
||||||
return False
|
return False
|
||||||
|
# Insert node
|
||||||
|
if letters not in curr_node.children:
|
||||||
|
self.insert_node(letters, curr_node)
|
||||||
|
log.debug("3 letter seq: ", word, letters)
|
||||||
|
|
||||||
|
curr_node = curr_node.children[letters]
|
||||||
|
|
||||||
|
i += len(letters)
|
||||||
|
|
||||||
# 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
|
||||||
@@ -317,6 +302,7 @@ class WordTree:
|
|||||||
if len(word) == 0 or word is None:
|
if len(word) == 0 or word is None:
|
||||||
return curr_node
|
return curr_node
|
||||||
|
|
||||||
|
log.debug("Searching...", word, curr_node)
|
||||||
if curr_node is None:
|
if curr_node is None:
|
||||||
curr_node = self.root
|
curr_node = self.root
|
||||||
for letters in curr_node.children:
|
for letters in curr_node.children:
|
||||||
@@ -338,7 +324,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:
|
||||||
debug(f"MAX WORD LENGTH REACHED! len = {word_len}")
|
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
|
return
|
||||||
@@ -347,14 +333,14 @@ 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))
|
||||||
debug("1: WORD FOUND:", "".join([board.board[x].letters for x in word_path]), word_path)
|
log.debug("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
|
||||||
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]
|
||||||
subtree.word_paths.append((subtree.active_node.get_word(board), word_path))
|
subtree.word_paths.append((subtree.active_node.get_word(board), word_path))
|
||||||
debug("2: WORD FOUND:", "".join([board.board[x].letters for x in word_path]), word_path)
|
log.debug("2: WORD FOUND:", "".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:
|
||||||
@@ -384,7 +370,7 @@ def build_full_boggle_tree(board: BoggleBoard, wordlist_path: str) -> dict[str,
|
|||||||
board_tree = {}
|
board_tree = {}
|
||||||
index = {}
|
index = {}
|
||||||
|
|
||||||
info("Reading in wordlists...")
|
log.info("Reading in wordlists...")
|
||||||
for letters in alphabet:
|
for letters in alphabet:
|
||||||
if letters == "":
|
if letters == "":
|
||||||
# Skip wordlist read for blocks with empty string
|
# Skip wordlist read for blocks with empty string
|
||||||
@@ -398,16 +384,16 @@ def build_full_boggle_tree(board: BoggleBoard, wordlist_path: str) -> dict[str,
|
|||||||
try:
|
try:
|
||||||
wordlist = read_wordlist(path.join(path.abspath(wordlist_path), filename))
|
wordlist = read_wordlist(path.join(path.abspath(wordlist_path), filename))
|
||||||
index[letters] = wordlist
|
index[letters] = wordlist
|
||||||
info(f">> {letters}: {filename}")
|
log.info(f">> {letters}: {filename}")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
info(f">> {letters}: -- Skipping -- no wordlist found for {letters}")
|
log.info(f">> {letters}: -- Skipping -- no wordlist found for {letters}")
|
||||||
index[letters] = {}
|
index[letters] = {}
|
||||||
|
|
||||||
info("Generating WordTrees...")
|
log.info("Generating WordTrees...")
|
||||||
params = [ [alphabet, board, cell, index[cell.letters] ] for cell in board.board.values()]
|
params = [ [alphabet, board, cell, index[cell.letters] ] for cell in board.board.values()]
|
||||||
with Pool(processes=len(board.board)) as pool:
|
with Pool(processes=len(board.board)) as pool:
|
||||||
for i, res in enumerate(pool.map(build_boggle_tree, params)):
|
for i, res in enumerate(pool.map(build_boggle_tree, params)):
|
||||||
info(f">> {params[i][2]}")
|
log.info(f">> {params[i][2]}")
|
||||||
board_tree[params[i][2].pos] = res
|
board_tree[params[i][2].pos] = res
|
||||||
|
|
||||||
return board_tree
|
return board_tree
|
||||||
|
|||||||
Reference in New Issue
Block a user