mirror of
https://codeberg.org/cblanken/boggler.git
synced 2026-07-26 11:35:10 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0fe222832 | ||
|
|
6771ec9205 | ||
|
|
e1981c44de | ||
|
|
02bb7c4262 | ||
|
|
da00afb395 | ||
|
|
afaa378131 | ||
|
|
daebee948a | ||
|
|
2bc9d9fb9e | ||
|
|
7500720992 | ||
|
|
d81f5e28cc | ||
|
|
a0d60d280c | ||
|
|
ac58828521 | ||
|
|
59c5beb491 | ||
|
|
49cf552cc1 | ||
|
|
f0cee00bef | ||
|
|
843e167bf4 | ||
|
|
1a1f5acb17 | ||
|
|
2f795be626 | ||
|
|
6f4d4a69a0 | ||
|
|
82991e96e7 | ||
|
|
1386e1cace | ||
|
|
8c9ed00826 | ||
|
|
c09dce4bd6 | ||
|
|
a37548a4ee | ||
|
|
1dc118e953 | ||
|
|
6d3a58595c | ||
|
|
8b9dc89420 |
@@ -5,10 +5,6 @@ A solver for the popular word game Boggle.
|
|||||||
```console
|
```console
|
||||||
pip install boggler
|
pip install boggler
|
||||||
```
|
```
|
||||||
OR
|
|
||||||
```console
|
|
||||||
git clone https://github.com/cblanken/boggler.git
|
|
||||||
```
|
|
||||||
|
|
||||||
To use the script to solve a particular Boggle board configuration, you'll need to do a few things
|
To use the script to solve a particular Boggle board configuration, you'll need to do a few things
|
||||||
1. Create `.csv` of the board state like so:
|
1. Create `.csv` of the board state like so:
|
||||||
@@ -399,5 +395,16 @@ quem : [(3, 3), (3, 2), (3, 1)]
|
|||||||
queme : [(3, 3), (3, 2), (3, 1), (2, 2)]
|
queme : [(3, 3), (3, 2), (3, 1), (2, 2)]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Build
|
||||||
|
Make sure the hatchling build system is installed
|
||||||
|
```bash
|
||||||
|
pip install hatchling
|
||||||
|
```
|
||||||
|
|
||||||
|
Navigate to the project folder then run the following.
|
||||||
|
```bash
|
||||||
|
python -m build .
|
||||||
|
```
|
||||||
|
|
||||||
# License
|
# License
|
||||||
The included [wordlists](src/boggler/wordlists) are covered by their respective licenses. All other files MIT © Cameron Blankenbuehler
|
The included [wordlists](src/boggler/wordlists) are covered by their respective licenses. All other files MIT © Cameron Blankenbuehler
|
||||||
|
|||||||
Executable
+84
@@ -0,0 +1,84 @@
|
|||||||
|
"""Boggler Demo"""
|
||||||
|
from pprint import pprint
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import csv
|
||||||
|
from itertools import chain
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from .boggler_utils import BoggleBoard, build_full_boggle_tree, read_boggle_file
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="boggler",
|
||||||
|
description="Boggle board game solver"
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument("board", type=Path, help="Path to board CSV file")
|
||||||
|
parser.add_argument("wordlists", type=Path,
|
||||||
|
help="Path to directory of wordlist files. The directory must contain \
|
||||||
|
text files of the form words_X.txt where \"X\" is a character of \
|
||||||
|
the alphabet")
|
||||||
|
parser.add_argument("max_word_length", nargs="?", type=int, default=16,
|
||||||
|
help="Maximum length of words searched for on provided board")
|
||||||
|
parser.add_argument("-f", "--format", type=str,
|
||||||
|
help="Specify alternative output format including [txt, json]")
|
||||||
|
parser.add_argument("-p", "--include-path", action="store_true", default=False,
|
||||||
|
help="Include full paths for each word in output")
|
||||||
|
parser.add_argument("-s", "--sort", action="store_true", default=False,
|
||||||
|
help="Sort output alphabetically. By default the results are sorted by the starting \
|
||||||
|
block position on the board from top-to-bottom, left-to-right as given in the \
|
||||||
|
board file.")
|
||||||
|
parser.add_argument("-d", "--dedup", action="store_true", default=False,
|
||||||
|
help="Remove duplicates from word-only output. Note that de-duplication does not preserve \
|
||||||
|
the original order of the output, so it is recommended to also use the sort option when \
|
||||||
|
de-duplicating.")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Command line tool for sovling Boggle boards"""
|
||||||
|
board = read_boggle_file(args.board)
|
||||||
|
try:
|
||||||
|
boggle_board = BoggleBoard(board, args.max_word_length)
|
||||||
|
except ValueError:
|
||||||
|
print("Invalid MAX_WORD_LENGTH. Please try again with a valid integer.")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
boggle_tree = build_full_boggle_tree(boggle_board, Path(args.wordlists))
|
||||||
|
|
||||||
|
if args.format:
|
||||||
|
match args.format.lower():
|
||||||
|
case "txt":
|
||||||
|
word_paths = [start_block.word_paths for start_block in boggle_tree.values()]
|
||||||
|
data = list(chain(*word_paths))
|
||||||
|
if args.include_path:
|
||||||
|
data = [f"{line[0]} {line[1]}" for line in data]
|
||||||
|
else:
|
||||||
|
data = [x[0] for x in data]
|
||||||
|
if args.dedup:
|
||||||
|
data = list(set(data))
|
||||||
|
|
||||||
|
if args.sort:
|
||||||
|
data.sort()
|
||||||
|
|
||||||
|
for line in data:
|
||||||
|
print(line)
|
||||||
|
|
||||||
|
case "json":
|
||||||
|
data = { str(k):v.word_paths for k,v in boggle_tree.items()}
|
||||||
|
print(json.dumps(data, indent=2, sort_keys=True))
|
||||||
|
case _:
|
||||||
|
print(f"Invalid format (-f) option provided: \"{args.format}\"")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
else:
|
||||||
|
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]}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
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 os import path
|
from pathlib import Path
|
||||||
|
|
||||||
def read_dice_file(dice_path: str):
|
def read_dice_file(dice_path: Path):
|
||||||
'''Return list of die strings from file ignoring all comments (#)'''
|
'''Return list of die strings from file ignoring all comments (#)'''
|
||||||
with open(path.abspath(dice_path), 'r+', encoding="utf-8") as file:
|
with open(dice_path, 'r+', encoding="utf-8") as file:
|
||||||
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: str):
|
def roll_die(die: str):
|
||||||
@@ -38,11 +38,9 @@ if __name__ == '__main__':
|
|||||||
print('Usage: python3 board_randomizer.py <dice_file>')
|
print('Usage: python3 board_randomizer.py <dice_file>')
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
dice = read_dice_file(path.abspath(argv[1]))
|
dice = read_dice_file(Path(argv[1]))
|
||||||
board = get_random_board_csv(dice)
|
board = get_random_board_csv(dice)
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
'''Boggler Utils'''
|
'''Boggler Utils'''
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from os import path
|
from pathlib import Path
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
|
import logging as log
|
||||||
import functools
|
import functools
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
class BoardCell:
|
class BoardCell:
|
||||||
'''Boggle Board cell'''
|
'''Boggle Board cell'''
|
||||||
def __init__(self, row: int, col: int, letters: str,
|
def __init__(self, row: int, col: int, letters: str,
|
||||||
adjacent_cells: list[BoardCell] = None) -> BoardCell:
|
adjacent_cells: list[BoardCell] = None) -> None:
|
||||||
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: tuple[int, int] = (self.__row, self.__col)
|
||||||
self.__letters: str = letters
|
self.__letters: str = letters
|
||||||
self.__adjacent_cells: list[BoardCell] = adjacent_cells
|
self.__adjacent_cells: list[BoardCell] = adjacent_cells
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ class BoardCell:
|
|||||||
return self.__col
|
return self.__col
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pos(self) -> (int, int):
|
def pos(self) -> tuple[int, int]:
|
||||||
'''Getter for pos property'''
|
'''Getter for pos property'''
|
||||||
return self.__pos
|
return self.__pos
|
||||||
|
|
||||||
@@ -52,11 +53,11 @@ class BoardCell:
|
|||||||
|
|
||||||
class BoggleBoard:
|
class BoggleBoard:
|
||||||
'''Boggle board structure'''
|
'''Boggle board structure'''
|
||||||
def __init__(self, board: list[list[str]], max_word_len: int = 14) -> BoggleBoard:
|
def __init__(self, board: list[list[str]], max_word_len: int = 14) -> None:
|
||||||
self.__height: int = len(board)
|
self.__height: int = len(board)
|
||||||
self.__width: int = len(board[0]) if self.__height > 0 else 0
|
self.__width: int = len(board[0]) if self.__height > 0 else 0
|
||||||
self.__board_list = board
|
self.__board_list = board
|
||||||
self.__board: dict[(int, int), BoardCell] = {}
|
self.__board: dict[tuple[int, int], BoardCell] = {}
|
||||||
|
|
||||||
# Max word length is limited by size of the board
|
# Max word length is limited by size of the board
|
||||||
self.__max_word_len = min(max_word_len, self.__width * self.__height)
|
self.__max_word_len = min(max_word_len, self.__width * self.__height)
|
||||||
@@ -103,7 +104,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[tuple[int, int], BoardCell]:
|
||||||
'''Getter for board property'''
|
'''Getter for board property'''
|
||||||
return self.__board
|
return self.__board
|
||||||
|
|
||||||
@@ -128,14 +129,14 @@ class BoggleBoard:
|
|||||||
indexes.append((row, col+1)) # right
|
indexes.append((row, col+1)) # right
|
||||||
return indexes
|
return indexes
|
||||||
|
|
||||||
def get_cell(self, row: int, col: int) -> str:
|
def get_cell(self, row: int, col: int) -> BoardCell:
|
||||||
'''Return the value at the specified row x column'''
|
'''Return the value at the specified row x column'''
|
||||||
return self.__board[(row, col)]
|
return self.__board[(row, col)]
|
||||||
|
|
||||||
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,
|
def __init__(self, letters: str, is_word: bool = False, parent: WordNode = None,
|
||||||
children: dict[str, WordNode] = None, board_pos = None) -> WordNode:
|
children: dict[str, WordNode] = None, board_pos = (None, None)) -> None:
|
||||||
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 {}
|
||||||
@@ -167,7 +168,7 @@ class WordNode:
|
|||||||
return self.__parent
|
return self.__parent
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def board_pos(self) -> (int, int):
|
def board_pos(self) -> tuple[int, int]:
|
||||||
'''Getter for board_pos property'''
|
'''Getter for board_pos property'''
|
||||||
return self.__board_pos
|
return self.__board_pos
|
||||||
|
|
||||||
@@ -199,14 +200,14 @@ class WordNode:
|
|||||||
|
|
||||||
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: list, root: WordNode, words: list[str] = None,
|
def __init__(self, alphabet: list[str], root: WordNode, words: list[str] = None,
|
||||||
max_word_len = 16) -> WordTree:
|
max_word_len = 16) -> None:
|
||||||
self.__alphabet: list = alphabet
|
self.__alphabet = alphabet
|
||||||
self.__wordlist: list[str] = words
|
self.__wordlist = words
|
||||||
self.__root: WordNode = root
|
self.__root = root
|
||||||
self.__max_word_len: int = max_word_len
|
self.__max_word_len = max_word_len
|
||||||
self.__tree: dict[str, WordNode] = {}
|
self.__tree: dict[str, WordNode] = {}
|
||||||
self.__word_paths: dict[str, list[WordNode]] = []
|
self.__word_paths: list[str, list[WordNode]] = []
|
||||||
|
|
||||||
# Generate root node
|
# Generate root node
|
||||||
self.__tree[root.letters] = root
|
self.__tree[root.letters] = root
|
||||||
@@ -252,11 +253,15 @@ class WordTree:
|
|||||||
self.__word_paths = value
|
self.__word_paths = value
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return ", ".join(self.wordlist)
|
return ", ".join([str(x) for x in self.word_paths])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
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: %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)
|
||||||
|
|
||||||
@@ -271,41 +276,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: %s %s %s", 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: %s %s %s", 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
|
|
||||||
#print(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: %s %s", 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
|
||||||
@@ -316,6 +305,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... %s %s", 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:
|
||||||
@@ -337,23 +327,23 @@ 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:
|
||||||
#print(f"MAX DEPTH REACHED! Depth = {depth}")
|
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 subtree
|
||||||
|
|
||||||
# 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]
|
||||||
subtree.word_paths.append((subtree.active_node.get_word(board), 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)
|
log.debug("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
|
return subtree
|
||||||
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))
|
||||||
#print("2: WORD FOUND:", "".join([board.board[x].letters for x in word_path]), 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:
|
||||||
@@ -377,13 +367,13 @@ def build_boggle_tree(args):
|
|||||||
sub_tree = WordTree(alphabet, WordNode(cell.letters, False, board_pos=cell.pos))
|
sub_tree = WordTree(alphabet, WordNode(cell.letters, False, board_pos=cell.pos))
|
||||||
return dict_tree.build_boggle_tree(board, cell, sub_tree)
|
return dict_tree.build_boggle_tree(board, cell, sub_tree)
|
||||||
|
|
||||||
def build_full_boggle_tree(board: BoggleBoard, wordlist_path: str) -> dict[str, WordTree]:
|
def build_full_boggle_tree(board: BoggleBoard, wordlist_path: Path) -> dict[str, WordTree]:
|
||||||
'''Return dictionary of WordTree(s) for every letter on a BoggleBoard'''
|
'''Return dictionary of WordTree(s) for every letter on a BoggleBoard'''
|
||||||
alphabet = sorted(set([cell.letters for cell in board.board.values()]))
|
alphabet = sorted(set([cell.letters for cell in board.board.values()]))
|
||||||
board_tree = {}
|
board_tree = {}
|
||||||
index = {}
|
index: dict[str] = {}
|
||||||
|
|
||||||
print("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
|
||||||
@@ -395,18 +385,18 @@ def build_full_boggle_tree(board: BoggleBoard, wordlist_path: str) -> dict[str,
|
|||||||
|
|
||||||
filename = "words_" + letters[0] + ".txt"
|
filename = "words_" + letters[0] + ".txt"
|
||||||
try:
|
try:
|
||||||
wordlist = read_wordlist(path.join(path.abspath(wordlist_path), filename))
|
wordlist = read_wordlist(Path(wordlist_path, filename))
|
||||||
index[letters] = wordlist
|
index[letters] = wordlist
|
||||||
print(f">> {letters}: {filename}")
|
log.info(">> %s: %s", letters, filename)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f">> {letters}: -- Skipping -- no wordlist found for {letters}")
|
log.info(">> %s: -- Skipping -- no wordlist found for %s", letters, letters)
|
||||||
index[letters] = {}
|
index[letters] = {}
|
||||||
|
|
||||||
print("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)):
|
||||||
print(f">> {params[i][2]}")
|
log.info(">> %s", params[i][2])
|
||||||
board_tree[params[i][2].pos] = res
|
board_tree[params[i][2].pos] = res
|
||||||
|
|
||||||
return board_tree
|
return board_tree
|
||||||
@@ -414,12 +404,34 @@ def build_full_boggle_tree(board: BoggleBoard, wordlist_path: str) -> dict[str,
|
|||||||
def read_wordlist(file):
|
def read_wordlist(file):
|
||||||
'''Return dictionary of words with associated word count (1 by default)'''
|
'''Return dictionary of words with associated word count (1 by default)'''
|
||||||
with open(file, 'r', encoding='utf-8') as file:
|
with open(file, 'r', encoding='utf-8') as file:
|
||||||
return {k:1 for k in file.read().split()}
|
return file.read().split()
|
||||||
|
|
||||||
|
class BadBoardFormat(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
def read_boggle_file(file):
|
def read_boggle_file(file):
|
||||||
'''Return list of rows from Boggle board csv file'''
|
'''Return list of rows from Boggle board csv file
|
||||||
|
|
||||||
|
The size of the board is determined by the width (number of comma-separated values)
|
||||||
|
of the first (non-empty) line in the file.
|
||||||
|
|
||||||
|
'''
|
||||||
with open(file, 'r', encoding='utf-8') as file:
|
with open(file, 'r', encoding='utf-8') as file:
|
||||||
return [x.rstrip().split(',') for x in file.readlines()]
|
board = []
|
||||||
|
|
||||||
|
board.append([x.strip() for x in file.readline().split(",")])
|
||||||
|
board_size = len(board[0])
|
||||||
|
for line in file.readlines():
|
||||||
|
if line.strip() == "":
|
||||||
|
raise BadBoardFormat("board files must contain no blank lines")
|
||||||
|
|
||||||
|
row = [x.strip() for x in line.strip().split(',')]
|
||||||
|
if len(row) != board_size:
|
||||||
|
raise BadBoardFormat("the length of each row must be the same")
|
||||||
|
|
||||||
|
board.append(row)
|
||||||
|
|
||||||
|
return board
|
||||||
|
|
||||||
def find_paths_by_word(board_letters, dictionary_path, max_len):
|
def find_paths_by_word(board_letters, dictionary_path, max_len):
|
||||||
'''Return list of paths by word'''
|
'''Return list of paths by word'''
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
CC = gcc
|
||||||
|
CFLAGS = -Wall -Werror -std=gnu17 -g3
|
||||||
|
OBJS = boggler.o
|
||||||
|
|
||||||
|
all: $(OBJS)
|
||||||
|
$(CC) $(OBJS) -o boggler
|
||||||
|
|
||||||
|
boggler.o: boggler.c board.h tree.h
|
||||||
|
$(CC) $(CFLAGS) -c boggler.c
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *~ *.o boggler
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
#ifndef BOGGLER_UTILS
|
||||||
|
#define BOGGLER_UTILS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Board is structured like so when referencing cells
|
||||||
|
* x ->
|
||||||
|
* +---+---+---+---+
|
||||||
|
* y |0,0|1,0|2,0|3,0| -+
|
||||||
|
* | |---------------| |
|
||||||
|
* v |0,1|1,1|2,1|3,1| |
|
||||||
|
* |---------------| | height
|
||||||
|
* |0,2|1,2|2,2|3,2| |
|
||||||
|
* |---------------| |
|
||||||
|
* |0,3|1,3|2,3|3,3| -+
|
||||||
|
* +---+---+---+---+
|
||||||
|
* | |
|
||||||
|
* +-------------+
|
||||||
|
* width
|
||||||
|
*
|
||||||
|
* Vec2 = (x, y) = (col, row)
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct BoardCell {
|
||||||
|
char *letters;
|
||||||
|
uint8_t row;
|
||||||
|
uint8_t col;
|
||||||
|
struct BoardCell **adjacent_cells;
|
||||||
|
uint8_t adjacent_cnt;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct Vec2 {
|
||||||
|
uint8_t row;
|
||||||
|
uint8_t col;
|
||||||
|
} Vec2;
|
||||||
|
|
||||||
|
typedef struct Board {
|
||||||
|
/* BoardCells are indexed from top-to-bottom, left-to-right
|
||||||
|
* For instance a 3x3 board would have cells ordered like this
|
||||||
|
* +---+---+---+
|
||||||
|
* | 0 | 1 | 2 |
|
||||||
|
* |---+---+---|
|
||||||
|
* | 3 | 4 | 5 |
|
||||||
|
* |---+---+---|
|
||||||
|
* | 6 | 7 | 8 |
|
||||||
|
* +---+---+---+
|
||||||
|
*/
|
||||||
|
uint8_t height;
|
||||||
|
unsigned short width;
|
||||||
|
struct BoardCell **cells;
|
||||||
|
} Board;
|
||||||
|
|
||||||
|
struct BoardCell *create_board_cell(char *letters, uint8_t row, uint8_t col) {
|
||||||
|
/* BoardCells are created with 8 adjacent cells since that is the minimum required
|
||||||
|
* for centrally placed (not on sides or corners) cells on a board.
|
||||||
|
*/
|
||||||
|
struct BoardCell *cell = malloc(sizeof(struct BoardCell));
|
||||||
|
cell->letters = letters;
|
||||||
|
cell->row = row;
|
||||||
|
cell->col = col;
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BoardCell *get_cell_at(Board *board, Vec2 pos) {
|
||||||
|
return board->cells[pos.row * pos.col + pos.col];
|
||||||
|
}
|
||||||
|
|
||||||
|
int print_boardcell(struct BoardCell *c) {
|
||||||
|
printf("%3s | (%d,%d)\n", c->letters, c->row, c->col);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int print_board(Board *board) {
|
||||||
|
uint8_t row_len = (board->width * 4 + 1) * sizeof(char) + 1;
|
||||||
|
char *row_delim = malloc(row_len);
|
||||||
|
snprintf(row_delim, 2, "%c", '+');
|
||||||
|
for (uint8_t i = 0; i < board->width; i++) {
|
||||||
|
strcat(row_delim, "----");
|
||||||
|
}
|
||||||
|
row_delim[strlen(row_delim)-1] = '+';
|
||||||
|
|
||||||
|
for (uint8_t row = 0; row < board->height; row++) {
|
||||||
|
printf("%s\n", row_delim);
|
||||||
|
for (uint8_t col = 0; col < board->width; col++) {
|
||||||
|
printf("|%2s ", board->cells[(row * board->width) + col]->letters);
|
||||||
|
}
|
||||||
|
puts("|");
|
||||||
|
}
|
||||||
|
printf("%s\n", row_delim);
|
||||||
|
|
||||||
|
free(row_delim);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_board(Board *b) {
|
||||||
|
if (b != NULL) {
|
||||||
|
for (uint8_t i = 0; i < b->width * b->height; i++) {
|
||||||
|
free(b->cells[i]);
|
||||||
|
b->cells[i] = NULL;
|
||||||
|
}
|
||||||
|
free(b->cells);
|
||||||
|
free(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Vec2[] get_adjacent_indexes(uint8_t board_size, Vec2 pos) {
|
||||||
|
// // Calculate total possible number of adjacent cells
|
||||||
|
// uint16_t = total_adj_cell_cnt = ((board-size-2) * (board-size-2) * 8) + ((board-size-2) * 4 * 5) + (4 * 3);
|
||||||
|
//
|
||||||
|
// Vec2[] indexes = malloc(8 * sizeof(Vec2));
|
||||||
|
// uint16_t i = 0;
|
||||||
|
// if (pos.row > 0) {
|
||||||
|
// indexes[i].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
|
||||||
|
//
|
||||||
|
// if (i != 8) {
|
||||||
|
// indexes = realloc(i * sizeof(Vec2));
|
||||||
|
// }
|
||||||
|
// return indexes
|
||||||
|
//}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include "board.h"
|
||||||
|
#include "tree.h"
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
const uint8_t BOARD_SIZE = 4;
|
||||||
|
char *letters[] = {
|
||||||
|
"u", "n", "r", "e",
|
||||||
|
"qu","n", "i", "l",
|
||||||
|
"i", "s", "h", "a",
|
||||||
|
"s", "e", "l", "b"
|
||||||
|
};
|
||||||
|
char **cell_letters = malloc(BOARD_SIZE * sizeof(struct BoardCell*));
|
||||||
|
|
||||||
|
struct BoardCell **cells = malloc(sizeof(struct BoardCell*) * BOARD_SIZE * BOARD_SIZE);
|
||||||
|
for (uint8_t i = 0; i < BOARD_SIZE * BOARD_SIZE; i++) {
|
||||||
|
cells[i] = create_board_cell(letters[i], (int)(i / BOARD_SIZE), i % BOARD_SIZE);
|
||||||
|
print_boardcell(cells[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Board board = { .height = 4, .width = 4, .cells = cells };
|
||||||
|
print_board(&board);
|
||||||
|
free(cell_letters);
|
||||||
|
|
||||||
|
struct WordNode *root = malloc(sizeof(struct WordNode) + sizeof(struct WordNode*));
|
||||||
|
init_wordnode(root, false, cells[0], NULL, NULL, 0);
|
||||||
|
add_child_node(&root, cells[5], false);
|
||||||
|
add_child_node(&root, cells[4], false);
|
||||||
|
print_wordnode(root);
|
||||||
|
print_wordnode_path(root->children[0]);
|
||||||
|
|
||||||
|
free_wordnode_children(root);
|
||||||
|
free(root);
|
||||||
|
for (uint8_t i = 0; i < BOARD_SIZE * BOARD_SIZE; i++) {
|
||||||
|
free(cells[i]);
|
||||||
|
}
|
||||||
|
free(cells);
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#ifndef WORD_TREE
|
||||||
|
#define WORD_TREE
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "board.h"
|
||||||
|
|
||||||
|
struct WordNode {
|
||||||
|
bool is_word;
|
||||||
|
struct BoardCell *cell;
|
||||||
|
struct WordNode *parent;
|
||||||
|
uint8_t child_cnt;
|
||||||
|
struct WordNode *children[];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WordTree {
|
||||||
|
char *alphabet;
|
||||||
|
struct WordNode *root;
|
||||||
|
};
|
||||||
|
|
||||||
|
void *init_wordnode(struct WordNode *n, bool is_word, struct BoardCell *cell,
|
||||||
|
struct WordNode *parent, struct WordNode *children[], uint8_t child_cnt) {
|
||||||
|
n->is_word = is_word;
|
||||||
|
n->cell = cell;
|
||||||
|
n->parent = parent;
|
||||||
|
if (children != NULL) {
|
||||||
|
for (uint8_t i = 0; i < child_cnt; i++) {
|
||||||
|
n->children[i] = children[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n->child_cnt = child_cnt;
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
void *print_wordnode(struct WordNode *n) {
|
||||||
|
if (n == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
printf("%3s | (%d,%d) | %3d\n", n->cell->letters, n->cell->row, n->cell->col, n->child_cnt);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *print_wordnode_path(struct WordNode *n) {
|
||||||
|
if (n == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
struct WordNode *node = n;
|
||||||
|
|
||||||
|
// Print letters from leaf to root
|
||||||
|
do {
|
||||||
|
printf("%s < ", node->cell->letters);
|
||||||
|
node = node->parent;
|
||||||
|
} while(node != NULL);
|
||||||
|
puts("ROOT");
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WordNode *add_child_node(struct WordNode **parent, struct BoardCell *cell, bool is_word) {
|
||||||
|
*parent = realloc(*parent, sizeof(struct WordNode) + sizeof(struct WordNode) * ((*parent)->child_cnt + 1));
|
||||||
|
(*parent)->children[(*parent)->child_cnt] = malloc(sizeof(struct WordNode));
|
||||||
|
init_wordnode((*parent)->children[(*parent)->child_cnt], is_word, cell, *parent, NULL, 0);
|
||||||
|
(*parent)->child_cnt++;
|
||||||
|
return (*parent)->children[(*parent)->child_cnt];
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_wordnode_children(struct WordNode *n) {
|
||||||
|
// TODO: recursively free wordnode children
|
||||||
|
for (uint8_t i = 0; i < n->child_cnt; i++) {
|
||||||
|
free(n->children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void *insert_word(struct WordTree *tree, char *word) {
|
||||||
|
// TODO: Traverse tree from tree->root and add nodes needed to complete `word`
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int search(struct WordTree *tree, char *word) {
|
||||||
|
// TODO: Traverse `tree` node-by-node and return an array of Board paths for `word`
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* WORD_TREE */
|
||||||
Generated
+394
@@ -0,0 +1,394 @@
|
|||||||
|
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "astroid"
|
||||||
|
version = "2.15.5"
|
||||||
|
description = "An abstract syntax tree for Python with inference support."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7.2"
|
||||||
|
files = [
|
||||||
|
{file = "astroid-2.15.5-py3-none-any.whl", hash = "sha256:078e5212f9885fa85fbb0cf0101978a336190aadea6e13305409d099f71b2324"},
|
||||||
|
{file = "astroid-2.15.5.tar.gz", hash = "sha256:1039262575027b441137ab4a62a793a9b43defb42c32d5670f38686207cd780f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
lazy-object-proxy = ">=1.4.0"
|
||||||
|
wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
description = "Cross-platform colored terminal text."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
files = [
|
||||||
|
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||||
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dill"
|
||||||
|
version = "0.3.6"
|
||||||
|
description = "serialize all of python"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"},
|
||||||
|
{file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
graph = ["objgraph (>=1.7.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "brain-dead simple config-ini parsing"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||||
|
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "isort"
|
||||||
|
version = "5.12.0"
|
||||||
|
description = "A Python utility / library to sort Python imports."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8.0"
|
||||||
|
files = [
|
||||||
|
{file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"},
|
||||||
|
{file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
colors = ["colorama (>=0.4.3)"]
|
||||||
|
pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
|
||||||
|
plugins = ["setuptools"]
|
||||||
|
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy-object-proxy"
|
||||||
|
version = "1.9.0"
|
||||||
|
description = "A fast and thorough lazy object proxy."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"},
|
||||||
|
{file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mccabe"
|
||||||
|
version = "0.7.0"
|
||||||
|
description = "McCabe checker, plugin for flake8"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
|
||||||
|
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy"
|
||||||
|
version = "1.3.0"
|
||||||
|
description = "Optional static typing for Python"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "mypy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d"},
|
||||||
|
{file = "mypy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85"},
|
||||||
|
{file = "mypy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd"},
|
||||||
|
{file = "mypy-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152"},
|
||||||
|
{file = "mypy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228"},
|
||||||
|
{file = "mypy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd"},
|
||||||
|
{file = "mypy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c"},
|
||||||
|
{file = "mypy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae"},
|
||||||
|
{file = "mypy-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca"},
|
||||||
|
{file = "mypy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf"},
|
||||||
|
{file = "mypy-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409"},
|
||||||
|
{file = "mypy-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929"},
|
||||||
|
{file = "mypy-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a"},
|
||||||
|
{file = "mypy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee"},
|
||||||
|
{file = "mypy-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f"},
|
||||||
|
{file = "mypy-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb"},
|
||||||
|
{file = "mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4"},
|
||||||
|
{file = "mypy-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305"},
|
||||||
|
{file = "mypy-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf"},
|
||||||
|
{file = "mypy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8"},
|
||||||
|
{file = "mypy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703"},
|
||||||
|
{file = "mypy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017"},
|
||||||
|
{file = "mypy-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e"},
|
||||||
|
{file = "mypy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a"},
|
||||||
|
{file = "mypy-1.3.0-py3-none-any.whl", hash = "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897"},
|
||||||
|
{file = "mypy-1.3.0.tar.gz", hash = "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mypy-extensions = ">=1.0.0"
|
||||||
|
typing-extensions = ">=3.10"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dmypy = ["psutil (>=4.0)"]
|
||||||
|
install-types = ["pip"]
|
||||||
|
python2 = ["typed-ast (>=1.4.0,<2)"]
|
||||||
|
reports = ["lxml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-extensions"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "Type system extensions for programs checked with the mypy type checker."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||||
|
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "23.1"
|
||||||
|
description = "Core utilities for Python packages"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
|
||||||
|
{file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platformdirs"
|
||||||
|
version = "3.5.1"
|
||||||
|
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "platformdirs-3.5.1-py3-none-any.whl", hash = "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5"},
|
||||||
|
{file = "platformdirs-3.5.1.tar.gz", hash = "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.2.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
|
||||||
|
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "plugin and hook calling mechanisms for python"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||||
|
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pre-commit", "tox"]
|
||||||
|
testing = ["pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pylint"
|
||||||
|
version = "2.17.4"
|
||||||
|
description = "python code static checker"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7.2"
|
||||||
|
files = [
|
||||||
|
{file = "pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"},
|
||||||
|
{file = "pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
astroid = ">=2.15.4,<=2.17.0-dev0"
|
||||||
|
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
|
||||||
|
dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""}
|
||||||
|
isort = ">=4.2.5,<6"
|
||||||
|
mccabe = ">=0.6,<0.8"
|
||||||
|
platformdirs = ">=2.2.0"
|
||||||
|
tomlkit = ">=0.10.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
spelling = ["pyenchant (>=3.2,<4.0)"]
|
||||||
|
testutils = ["gitpython (>3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "7.3.1"
|
||||||
|
description = "pytest: simple powerful testing with Python"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"},
|
||||||
|
{file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
|
iniconfig = "*"
|
||||||
|
packaging = "*"
|
||||||
|
pluggy = ">=0.12,<2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomlkit"
|
||||||
|
version = "0.11.8"
|
||||||
|
description = "Style preserving TOML library"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"},
|
||||||
|
{file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.6.2"
|
||||||
|
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "typing_extensions-4.6.2-py3-none-any.whl", hash = "sha256:3a8b36f13dd5fdc5d1b16fe317f5668545de77fa0b8e02006381fd49d731ab98"},
|
||||||
|
{file = "typing_extensions-4.6.2.tar.gz", hash = "sha256:06006244c70ac8ee83fa8282cb188f697b8db25bc8b4df07be1873c43897060c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wrapt"
|
||||||
|
version = "1.15.0"
|
||||||
|
description = "Module for decorators, wrappers and monkey patching."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||||
|
files = [
|
||||||
|
{file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"},
|
||||||
|
{file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"},
|
||||||
|
{file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"},
|
||||||
|
{file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"},
|
||||||
|
{file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"},
|
||||||
|
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"},
|
||||||
|
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"},
|
||||||
|
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"},
|
||||||
|
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"},
|
||||||
|
{file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"},
|
||||||
|
{file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"},
|
||||||
|
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"},
|
||||||
|
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"},
|
||||||
|
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"},
|
||||||
|
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"},
|
||||||
|
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"},
|
||||||
|
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"},
|
||||||
|
{file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"},
|
||||||
|
{file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"},
|
||||||
|
{file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"},
|
||||||
|
{file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"},
|
||||||
|
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"},
|
||||||
|
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"},
|
||||||
|
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"},
|
||||||
|
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"},
|
||||||
|
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"},
|
||||||
|
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"},
|
||||||
|
{file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"},
|
||||||
|
{file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"},
|
||||||
|
{file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"},
|
||||||
|
{file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"},
|
||||||
|
{file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"},
|
||||||
|
{file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"},
|
||||||
|
{file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"},
|
||||||
|
{file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"},
|
||||||
|
{file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"},
|
||||||
|
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"},
|
||||||
|
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"},
|
||||||
|
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"},
|
||||||
|
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"},
|
||||||
|
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"},
|
||||||
|
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"},
|
||||||
|
{file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"},
|
||||||
|
{file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"},
|
||||||
|
{file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"},
|
||||||
|
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"},
|
||||||
|
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"},
|
||||||
|
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"},
|
||||||
|
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"},
|
||||||
|
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"},
|
||||||
|
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"},
|
||||||
|
{file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"},
|
||||||
|
{file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"},
|
||||||
|
{file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"},
|
||||||
|
{file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"},
|
||||||
|
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"},
|
||||||
|
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"},
|
||||||
|
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"},
|
||||||
|
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"},
|
||||||
|
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"},
|
||||||
|
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"},
|
||||||
|
{file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"},
|
||||||
|
{file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"},
|
||||||
|
{file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"},
|
||||||
|
{file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"},
|
||||||
|
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"},
|
||||||
|
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"},
|
||||||
|
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"},
|
||||||
|
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"},
|
||||||
|
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"},
|
||||||
|
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"},
|
||||||
|
{file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"},
|
||||||
|
{file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"},
|
||||||
|
{file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"},
|
||||||
|
{file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.0"
|
||||||
|
python-versions = "^3.11"
|
||||||
|
content-hash = "a14e3d6fa508612e47f2dab1d5068300be5735fce187ae7543b198ed57eede70"
|
||||||
+23
-4
@@ -1,7 +1,3 @@
|
|||||||
[build-system]
|
|
||||||
requires = ["hatchling"]
|
|
||||||
build-backend = "hatchling.build"
|
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "boggler"
|
name = "boggler"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@@ -21,3 +17,26 @@ classifiers = [
|
|||||||
[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]
|
||||||
|
name = "boggler"
|
||||||
|
version = "2.0.1"
|
||||||
|
description = "Utilities for solving the Boggle word game."
|
||||||
|
authors = ["Cameron Blankenbuehler <cameron.blankenbuehler@protonmail.com>"]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.11"
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
boggler = "boggler.__main__:main"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
pytest = "^7.3.1"
|
||||||
|
pylint = "^2.17.4"
|
||||||
|
mypy = "^1.3.0"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
"""Boggler Demo"""
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from boggler_utils import BoggleBoard, build_full_boggle_tree, read_boggle_file
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if len(sys.argv) < 3 or len(sys.argv) > 4:
|
|
||||||
print('Usage: python3 boggler.py <BOARD_FILE> <WORDLISTS_DIR> [MAX_WORD_LENGTH]')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
board = read_boggle_file(Path(sys.argv[1]))
|
|
||||||
if len(sys.argv) == 3:
|
|
||||||
boggle_board = BoggleBoard(board)
|
|
||||||
elif len(sys.argv) == 4:
|
|
||||||
try:
|
|
||||||
boggle_board = BoggleBoard(board, int(sys.argv[3]))
|
|
||||||
except ValueError:
|
|
||||||
print("Invalid MAX_WORD_LENGTH. Please try again with a valid integer.")
|
|
||||||
sys.exit(1)
|
|
||||||
try:
|
|
||||||
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:
|
|
||||||
print("The [MAX_WORD_LENGTH] argument must be an integer.")
|
|
||||||
print("Please try again.")
|
|
||||||
sys.exit(1)
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
u, n,r ,e
|
||||||
|
qu, n,i, l
|
||||||
|
i ,s,h, a
|
||||||
|
s ,e , l,b
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
|
u,n,r,e
|
||||||
|
qu,n,i,l
|
||||||
|
i,s,h,a
|
||||||
|
|
||||||
|
s,e,l,b
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
u,n,r,e
|
||||||
|
qu,n,i,l
|
||||||
|
i,s,h,a
|
||||||
|
s,e,l,b
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
u,n,r,e,a,i
|
||||||
|
qu,n,i,l
|
||||||
|
i,s,h,a,j
|
||||||
|
s,e,l
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
u,n,r,e
|
||||||
|
qu,n,i,l
|
||||||
|
i,s,h,a
|
||||||
|
s,e,l,b
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
u,n,r,e
|
||||||
|
qu,n,i,l
|
||||||
|
i,s,h,a
|
||||||
|
s,e,l,b
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
u,n,r,e,b
|
||||||
|
qu,n,i,l
|
||||||
|
i,s,h,a,g,i
|
||||||
|
s,e,l,b
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
../boggler
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import pytest
|
||||||
|
from boggler.boggler_utils import read_boggle_file, BadBoardFormat
|
||||||
|
|
||||||
|
boards_path = Path(Path(__file__).parent.resolve())
|
||||||
|
|
||||||
|
@pytest.fixture(name="expected_board_4x4")
|
||||||
|
def fixture_expected_board_4x4():
|
||||||
|
return [
|
||||||
|
['u', 'n', 'r', 'e'],
|
||||||
|
['qu', 'n', 'i', 'l'],
|
||||||
|
['i', 's', 'h', 'a'],
|
||||||
|
['s', 'e', 'l', 'b']
|
||||||
|
]
|
||||||
|
|
||||||
|
# Boards with clean input
|
||||||
|
@pytest.fixture(name="clean_4x4_path")
|
||||||
|
def fixture_clean_4x4_path():
|
||||||
|
return Path(boards_path, "./boards/4x4_clean.board")
|
||||||
|
|
||||||
|
def test_clean_4x4_board(expected_board_4x4, clean_4x4_path):
|
||||||
|
assert expected_board_4x4 == read_boggle_file(clean_4x4_path)
|
||||||
|
|
||||||
|
|
||||||
|
# Boards with leading whitespace
|
||||||
|
@pytest.fixture(name="leading_whitespace_4x4_path")
|
||||||
|
def fixture_leading_whitespace_4x4_path():
|
||||||
|
return Path(boards_path, "./boards/4x4_leading_whitespace.board")
|
||||||
|
|
||||||
|
def test_leading_whitespace_4x4_board(expected_board_4x4, leading_whitespace_4x4_path):
|
||||||
|
assert expected_board_4x4 == read_boggle_file(leading_whitespace_4x4_path)
|
||||||
|
|
||||||
|
|
||||||
|
# Boards with between whitespace
|
||||||
|
@pytest.fixture(name="between_whitespace_4x4_path")
|
||||||
|
def fixture_between_whitespace_4x4_path():
|
||||||
|
return Path(boards_path, "./boards/4x4_between_whitespace.board")
|
||||||
|
|
||||||
|
def test_between_whitespace_4x4_board(expected_board_4x4, between_whitespace_4x4_path):
|
||||||
|
assert expected_board_4x4 == read_boggle_file(between_whitespace_4x4_path)
|
||||||
|
|
||||||
|
|
||||||
|
# Boards with trailing whitespace
|
||||||
|
@pytest.fixture(name="trailing_whitespace_4x4_path")
|
||||||
|
def fixture_trailing_whitespace_4x4_path():
|
||||||
|
return Path(boards_path, "./boards/4x4_trailing_whitespace.board")
|
||||||
|
|
||||||
|
def test_trailing_whitespace_4x4_board(expected_board_4x4, trailing_whitespace_4x4_path):
|
||||||
|
assert expected_board_4x4 == read_boggle_file(trailing_whitespace_4x4_path)
|
||||||
|
|
||||||
|
|
||||||
|
# Boards with blank lines
|
||||||
|
@pytest.fixture(name="blank_lines_4x4_path")
|
||||||
|
def fixture_blank_lines_4x4_path():
|
||||||
|
return Path(boards_path, "./boards/4x4_blank_lines.board")
|
||||||
|
|
||||||
|
def test_blank_lines_4x4_board(blank_lines_4x4_path):
|
||||||
|
with pytest.raises(BadBoardFormat, match="must contain no blank lines"):
|
||||||
|
read_boggle_file(blank_lines_4x4_path)
|
||||||
|
|
||||||
|
|
||||||
|
# Boards with inconsistent width lines
|
||||||
|
@pytest.fixture(name="inconsistent_width_4x4_path")
|
||||||
|
def fixture_inconsistent_width_4x4_path():
|
||||||
|
return Path(boards_path, "./boards/4x4_inconsistent_width.board")
|
||||||
|
|
||||||
|
def test_inconsistent_width_4x4_board(inconsistent_width_4x4_path):
|
||||||
|
with pytest.raises(BadBoardFormat, match="length of each row must be the same"):
|
||||||
|
read_boggle_file(inconsistent_width_4x4_path)
|
||||||
Executable
+177
@@ -0,0 +1,177 @@
|
|||||||
|
from itertools import chain
|
||||||
|
from pathlib import Path
|
||||||
|
import pytest
|
||||||
|
from boggler.boggler_utils import BoggleBoard, build_full_boggle_tree
|
||||||
|
|
||||||
|
WORDLISTS_DIR = Path("./boggler/wordlists/dwyl/").absolute()
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def board_4x4():
|
||||||
|
return [
|
||||||
|
['u','n','r','e'],
|
||||||
|
['qu','n','i','l'],
|
||||||
|
['i','s','h','a'],
|
||||||
|
['s','e','l','b']
|
||||||
|
]
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def expected_words_max_4():
|
||||||
|
return [
|
||||||
|
"u", "un", "unn", "uns", "uni", "unie", "uni", "unie", "un", "unn", "uni",
|
||||||
|
"unie", "n", "ni", "nis", "nisi", "nil", "nile", "nu", "nun", "nuns", "nr", "r",
|
||||||
|
"rin", "rie", "riel", "risqu", "rise", "riss", "ria", "rial", "rial", "rin",
|
||||||
|
"rins", "rile", "rn", "rle", "rn", "re", "rel", "rei", "rein", "reis", "rein",
|
||||||
|
"e", "el", "ela", "elhi", "eli", "elia", "eir", "eila", "er", "erin", "eris",
|
||||||
|
"eria", "erin", "ern", "erns", "ern", "qu", "qui", "quin", "quis", "quis", "n",
|
||||||
|
"nu", "nun", "nr", "ns", "ni", "nis", "nies", "nies", "nis", "nisi", "ni",
|
||||||
|
"nis", "nisi", "nil", "nile", "i", "ir", "ire", "in", "inn", "inns", "ie",
|
||||||
|
"ihs", "is", "isn", "ise", "isl", "isle", "isis", "ish", "ia", "in", "inn",
|
||||||
|
"ins", "il", "ile", "ila", "l", "le", "lei", "leis", "ler", "lr", "la", "lai",
|
||||||
|
"lair", "lain", "lain", "lab", "lah", "lh", "lhb", "li", "lir", "lire", "lin",
|
||||||
|
"linn", "lie", "lier", "lis", "lise", "liss", "lish", "lin", "linn", "lins",
|
||||||
|
"i", "in", "inn", "ins", "inia", "is", "ise", "ie", "is", "isn", "ise", "isl",
|
||||||
|
"isle", "ish", "s", "sn", "si", "sir", "sire", "sin", "sie", "sier", "sia",
|
||||||
|
"sial", "sial", "sin", "sinh", "sil", "sile", "se", "sei", "seis", "sel", "ss",
|
||||||
|
"ssi", "sl", "sla", "slab", "si", "sin", "sinh", "sis", "sise", "sie", "sh",
|
||||||
|
"shi", "shin", "shia", "shin", "she", "shes", "shel", "sha", "shai", "shab",
|
||||||
|
"h", "hi", "hir", "hire", "hin", "hie", "his", "hisn", "hiss", "hia", "hin",
|
||||||
|
"hins", "hile", "hila", "hl", "hler", "hl", "he", "hes", "hei", "hein", "hes",
|
||||||
|
"hel", "hb", "hs", "hsi", "hsi", "ha", "hal", "hale", "hair", "hain", "hain",
|
||||||
|
"hail", "hab", "hal", "hals", "hale", "a", "al", "ale", "alin", "alin", "ai",
|
||||||
|
"air", "airn", "airn", "aire", "ain", "ainu", "aiel", "ais", "ain", "ainu",
|
||||||
|
"ains", "ail", "aile", "ab", "abl", "able", "al", "als", "ale", "ales", "ales",
|
||||||
|
"alb", "ah", "ahi", "ahir", "ahs", "s", "si", "sin", "sins", "sinh", "sie",
|
||||||
|
"sis", "sisi", "sise", "sish", "ss", "ssi", "ssi", "se", "sei", "seis", "sel",
|
||||||
|
"sels", "e", "es", "ess", "eh", "es", "ess", "el", "elhi", "els", "ela", "elb",
|
||||||
|
"l", "lh", "lhb", "ls", "la", "lai", "lair", "lain", "lain", "lab", "lah", "le",
|
||||||
|
"les", "less", "lei", "leis", "leis", "lehi", "les", "less", "lb", "b", "ba",
|
||||||
|
"bal", "bale", "balr", "bali", "bai", "bain", "bais", "bain", "bail", "bal",
|
||||||
|
"bals", "bale", "bah", "bhil", "bl", "bls", "blah"
|
||||||
|
]
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def expected_words_max_20():
|
||||||
|
return [
|
||||||
|
"u","un","unn","unrelinquishable","unrelishable","unreliable","unrein","uns",
|
||||||
|
"unsin","unslain","unshale","unshale","uni","unie","unhale","unhair","unhale",
|
||||||
|
"uni","unie","un","unn","uni","unie","unrelishable","unreliable",
|
||||||
|
"unrelinquishable","unrein","n","ni","nihal","nihal","nis","nisei","niseis",
|
||||||
|
"nisse","nisi","nil","nile","nu","nun","nuns","nr","r","rin","rie",
|
||||||
|
"riel","risqu","rise","rises","riss","rissel","ria","rial","rial",
|
||||||
|
"rials","rin","rins","rinse","rinses","rile","rn","rle","rn",
|
||||||
|
"re","rel","relais","relinquish","relinquishes","relish","relishes",
|
||||||
|
"relishable","reliable","relinquish","relinquishes","rei","rein","reis",
|
||||||
|
"rein","reins","e","el","ela","elain","elain","elains","elhi","eli","elisha",
|
||||||
|
"elia","eir","eila","er","erin","eris","eria","erin","ern","erns","ernie",
|
||||||
|
"ern","qu","qui","quin","quins","quinse","quinin","quiniela","quinia",
|
||||||
|
"quis","quis","quisle","n","nu","nun","nr","ns","ni",
|
||||||
|
"nis","nisse","nies","nies","niels","nis","nisi","nisse","ni",
|
||||||
|
"nihal","nihal","nis","nisei","niseis","nisse","nisi","nil","nile",
|
||||||
|
"i","ir","ire","in","inn","inns","ie","ihs","is",
|
||||||
|
"isn","ise","issei","isl","isle","isles","isis","ish","ia",
|
||||||
|
"in","inn","ins","inisle","inhale","inhaler","inhale","inhales","inhales",
|
||||||
|
"il","ile","ila","l","le","lei","leis","leiss","ler",
|
||||||
|
"lr","la","lai","lair","lain","laisse","lain","lab","lah",
|
||||||
|
"lh","lhb","li","lir","lire","lin","linn","linns","linquish",
|
||||||
|
"lie","lier","lis","lise","liss","lisle","lisles","lish","liable",
|
||||||
|
"lin","linn","lins","linie","linha","linquish","i","in","inn",
|
||||||
|
"ins","inhale","inhaler","inhale","inhales","inhales","inisle","inia","inial",
|
||||||
|
"inial","is","ise","ie","is","isn","ise","isl","isle",
|
||||||
|
"isles","ish","s","sn","snies","snirl","squin","squinnier","si",
|
||||||
|
"sir","sire","sin","sie","sier","sia","sial","sial","sin",
|
||||||
|
"sinh","sil","sile","se","sei","seis","sel","selah","ss",
|
||||||
|
"ssi","sl","sla","slain","slain","slab","si","sin","sinh",
|
||||||
|
"sis","sise","sisel","sie","sh","shi","shirl","shire","shin",
|
||||||
|
"shinnies","shiel","shier","shia","shin","she","shes","shel","shela",
|
||||||
|
"sha","shale","shalier","shai","shairn","shairn","shab","shale","shales",
|
||||||
|
"h","hi","hir","hire","hin","hinnies","hinnies","hie","his",
|
||||||
|
"hisn","hiss","hissel","hisis","hia","hin","hins","hile","hila",
|
||||||
|
"hl","hler","hl","he","hes","hei","hein","heinie","hes",
|
||||||
|
"hel","hb","hs","hsi","hsi","ha","hal","hale","haler",
|
||||||
|
"hair","haire","hain","hain","hail","hailer","hab","hable","hal",
|
||||||
|
"hals","halse","hale","hales","hales","a","al","ale","alin",
|
||||||
|
"alish","alin","ai","air","airn","airns","airn","aire","ain",
|
||||||
|
"ainu","aiel","ais","aisle","aisles","ain","ainu","ains","ail",
|
||||||
|
"aile","ab","abl","able","ables","ables","al","als","ale",
|
||||||
|
"ales","ales","alb","ah","ahi","ahir","ahs","s","si",
|
||||||
|
"sin","sins","sinh","sinhs","sie","sis","sisi","sise","sisel",
|
||||||
|
"sish","ss","ssi","ssi","se","sesqui","sesia","sei","seis",
|
||||||
|
"seisin","seisin","sel","sels","selah","selahs","e","es","ess",
|
||||||
|
"eshin","eshin","eh","es","ess","essie","el","elhi","els",
|
||||||
|
"elsin","elsin","elsin","elshin","elshin","ela","elain","elain","elains",
|
||||||
|
"elb","l","lh","lhb","ls","la","lai","lair","lain",
|
||||||
|
"laisse","lain","lab","lah","le","les","less","lei","leis",
|
||||||
|
"leiss","leis","leiss","lehi","les","less","lessn","lb","b",
|
||||||
|
"ba","bal","bale","balei","baleise","baler","balr","bali","bai",
|
||||||
|
"bairn","bairns","bairnie","bairnish","bairn","bain","bais","bain","bainie",
|
||||||
|
"bail","baile","bailer","bal","bals","bale","bales","balei","bales",
|
||||||
|
"bah","bhil","bl","bls","blair","blain","blain","blains","blah",
|
||||||
|
"blahs","bless","bless"
|
||||||
|
]
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Solved board max 4-letter words
|
||||||
|
# -----------------------------------------------------
|
||||||
|
|
||||||
|
# Init for word tree (4 board_4x4 or less) and fixtures
|
||||||
|
@pytest.fixture
|
||||||
|
def board_4(board_4x4):
|
||||||
|
return BoggleBoard(board_4x4, 4)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def tree_4(board_4):
|
||||||
|
return build_full_boggle_tree(board_4, Path(WORDLISTS_DIR))
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def found_words_4(tree_4):
|
||||||
|
return list(chain.from_iterable([x.word_paths for x in tree_4.values()]))
|
||||||
|
|
||||||
|
# Tests for solved board with max of 4-letter words
|
||||||
|
def test_word_count_4_board_4x4_max(expected_words_max_4, tree_4):
|
||||||
|
assert len(expected_words_max_4) == sum(map(lambda x: len(x.word_paths), tree_4.values()))
|
||||||
|
|
||||||
|
def test_words_full_4_board_4x4_max(expected_words_max_4, found_words_4, tree_4):
|
||||||
|
found_words = [x.word_paths for x in tree_4.values()]
|
||||||
|
for letter, subtree in tree_4.items():
|
||||||
|
for i in range(0, min(len(expected_words_max_4), len(found_words_4))):
|
||||||
|
assert expected_words_max_4[i] == found_words_4[i][0]
|
||||||
|
|
||||||
|
def test_format_words_4_to_csv():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_format_words_4_to_json():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Solved board max 20-letter words
|
||||||
|
# -----------------------------------------------------
|
||||||
|
|
||||||
|
# Init for word tree (20 board_4x4 or less) and fixtures
|
||||||
|
@pytest.fixture
|
||||||
|
def board_20(board_4x4):
|
||||||
|
return BoggleBoard(board_4x4, 20)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def tree_20(board_20):
|
||||||
|
return build_full_boggle_tree(board_20, WORDLISTS_DIR)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def found_words_20(tree_20):
|
||||||
|
return list(chain.from_iterable([x.word_paths for x in tree_20.values()]))
|
||||||
|
|
||||||
|
# Tests for solved board with max of 20-letter words
|
||||||
|
def test_word_count_20_board_4x4_max(expected_words_max_20, tree_20):
|
||||||
|
assert len(expected_words_max_20) == sum(map(lambda x: len(x.word_paths), tree_20.values()))
|
||||||
|
|
||||||
|
def test_words_full_20_board_4x4_max(expected_words_max_20, found_words_20, tree_20):
|
||||||
|
found_words = [x.word_paths for x in tree_20.values()]
|
||||||
|
for letter, subtree in tree_20.items():
|
||||||
|
for i in range(0, min(len(expected_words_max_20), len(found_words_20))):
|
||||||
|
assert expected_words_max_20[i] == found_words_20[i][0]
|
||||||
|
|
||||||
|
def test_format_words_20_to_csv():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_format_words_20_to_json():
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user