"""
Creates a random wordsearch with eight different directions
that are best described as compass locations.
@ https://en.wikipedia.org/wiki/Word_search
"""
from random import choice, randint, shuffle
WORDS = ["cat", "dog", "snake", "fish"]
WIDTH = 10
HEIGHT = 10
class WordSearch:
"""
>>> ws = WordSearch(WORDS, WIDTH, HEIGHT)
>>> ws.board # doctest: +ELLIPSIS
[[None, ..., None], ..., [None, ..., None]]
>>> ws.generate_board()
"""
def __init__(self, words: list[str], width: int, height: int) -> None:
self.words = words
self.width = width
self.height = height
self.board: list[list[str | None]] = [[None] * width for _ in range(height)]
def insert_north(self, word: str, rows: list[int], cols: list[int]) -> None:
"""
>>> ws = WordSearch(WORDS, 3, 3)
>>> ws.insert_north("cat", [2], [2])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[[None, None, 't'],
[None, None, 'a'],
[None, None, 'c']]
>>> ws.insert_north("at", [0, 1, 2], [2, 1])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[[None, 't', 't'],
[None, 'a', 'a'],
[None, None, 'c']]
"""
word_length = len(word)
for row in rows:
if word_length > row + 1:
continue
for col in cols:
letters_above = [self.board[row - i][col] for i in range(word_length)]
if all(letter is None for letter in letters_above):
for i in range(word_length):
self.board[row - i][col] = word[i]
return
def insert_northeast(self, word: str, rows: list[int], cols: list[int]) -> None:
"""
>>> ws = WordSearch(WORDS, 3, 3)
>>> ws.insert_northeast("cat", [2], [0])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[[None, None, 't'],
[None, 'a', None],
['c', None, None]]
>>> ws.insert_northeast("at", [0, 1], [2, 1, 0])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[[None, 't', 't'],
['a', 'a', None],
['c', None, None]]
"""
word_length = len(word)
for row in rows:
if word_length > row + 1:
continue
for col in cols:
if word_length + col > self.width:
continue
letters_diagonal_left = [
self.board[row - i][col + i] for i in range(word_length)
]
if all(letter is None for letter in letters_diagonal_left):
for i in range(word_length):
self.board[row - i][col + i] = word[i]
return
def insert_east(self, word: str, rows: list[int], cols: list[int]) -> None:
"""
>>> ws = WordSearch(WORDS, 3, 3)
>>> ws.insert_east("cat", [1], [0])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[[None, None, None],
['c', 'a', 't'],
[None, None, None]]
>>> ws.insert_east("at", [1, 0], [2, 1, 0])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[[None, 'a', 't'],
['c', 'a', 't'],
[None, None, None]]
"""
word_length = len(word)
for row in rows:
for col in cols:
if word_length + col > self.width:
continue
letters_left = [self.board[row][col + i] for i in range(word_length)]
if all(letter is None for letter in letters_left):
for i in range(word_length):
self.board[row][col + i] = word[i]
return
def insert_southeast(self, word: str, rows: list[int], cols: list[int]) -> None:
"""
>>> ws = WordSearch(WORDS, 3, 3)
>>> ws.insert_southeast("cat", [0], [0])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[['c', None, None],
[None, 'a', None],
[None, None, 't']]
>>> ws.insert_southeast("at", [1, 0], [2, 1, 0])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[['c', None, None],
['a', 'a', None],
[None, 't', 't']]
"""
word_length = len(word)
for row in rows:
if word_length + row > self.height:
continue
for col in cols:
if word_length + col > self.width:
continue
letters_diagonal_left = [
self.board[row + i][col + i] for i in range(word_length)
]
if all(letter is None for letter in letters_diagonal_left):
for i in range(word_length):
self.board[row + i][col + i] = word[i]
return
def insert_south(self, word: str, rows: list[int], cols: list[int]) -> None:
"""
>>> ws = WordSearch(WORDS, 3, 3)
>>> ws.insert_south("cat", [0], [0])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[['c', None, None],
['a', None, None],
['t', None, None]]
>>> ws.insert_south("at", [2, 1, 0], [0, 1, 2])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[['c', None, None],
['a', 'a', None],
['t', 't', None]]
"""
word_length = len(word)
for row in rows:
if word_length + row > self.height:
continue
for col in cols:
letters_below = [self.board[row + i][col] for i in range(word_length)]
if all(letter is None for letter in letters_below):
for i in range(word_length):
self.board[row + i][col] = word[i]
return
def insert_southwest(self, word: str, rows: list[int], cols: list[int]) -> None:
"""
>>> ws = WordSearch(WORDS, 3, 3)
>>> ws.insert_southwest("cat", [0], [2])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[[None, None, 'c'],
[None, 'a', None],
['t', None, None]]
>>> ws.insert_southwest("at", [1, 2], [2, 1, 0])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[[None, None, 'c'],
[None, 'a', 'a'],
['t', 't', None]]
"""
word_length = len(word)
for row in rows:
if word_length + row > self.height:
continue
for col in cols:
if word_length > col + 1:
continue
letters_diagonal_left = [
self.board[row + i][col - i] for i in range(word_length)
]
if all(letter is None for letter in letters_diagonal_left):
for i in range(word_length):
self.board[row + i][col - i] = word[i]
return
def insert_west(self, word: str, rows: list[int], cols: list[int]) -> None:
"""
>>> ws = WordSearch(WORDS, 3, 3)
>>> ws.insert_west("cat", [1], [2])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[[None, None, None],
['t', 'a', 'c'],
[None, None, None]]
>>> ws.insert_west("at", [1, 0], [1, 2, 0])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[['t', 'a', None],
['t', 'a', 'c'],
[None, None, None]]
"""
word_length = len(word)
for row in rows:
for col in cols:
if word_length > col + 1:
continue
letters_left = [self.board[row][col - i] for i in range(word_length)]
if all(letter is None for letter in letters_left):
for i in range(word_length):
self.board[row][col - i] = word[i]
return
def insert_northwest(self, word: str, rows: list[int], cols: list[int]) -> None:
"""
>>> ws = WordSearch(WORDS, 3, 3)
>>> ws.insert_northwest("cat", [2], [2])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[['t', None, None],
[None, 'a', None],
[None, None, 'c']]
>>> ws.insert_northwest("at", [1, 2], [0, 1])
>>> ws.board # doctest: +NORMALIZE_WHITESPACE
[['t', None, None],
['t', 'a', None],
[None, 'a', 'c']]
"""
word_length = len(word)
for row in rows:
if word_length > row + 1:
continue
for col in cols:
if word_length > col + 1:
continue
letters_diagonal_left = [
self.board[row - i][col - i] for i in range(word_length)
]
if all(letter is None for letter in letters_diagonal_left):
for i in range(word_length):
self.board[row - i][col - i] = word[i]
return
def generate_board(self) -> None:
"""
Generates a board with a random direction for each word.
>>> wt = WordSearch(WORDS, WIDTH, HEIGHT)
>>> wt.generate_board()
>>> len(list(filter(lambda word: word is not None, sum(wt.board, start=[])))
... ) == sum(map(lambda word: len(word), WORDS))
True
"""
directions = (
self.insert_north,
self.insert_northeast,
self.insert_east,
self.insert_southeast,
self.insert_south,
self.insert_southwest,
self.insert_west,
self.insert_northwest,
)
for word in self.words:
rows, cols = list(range(self.height)), list(range(self.width))
shuffle(rows)
shuffle(cols)
choice(directions)(word, rows, cols)
def visualise_word_search(
board: list[list[str | None]] | None = None, *, add_fake_chars: bool = True
) -> None:
"""
Graphically displays the word search in the terminal.
>>> ws = WordSearch(WORDS, 5, 5)
>>> ws.insert_north("cat", [4], [4])
>>> visualise_word_search(
... ws.board, add_fake_chars=False) # doctest: +NORMALIZE_WHITESPACE
# # # # #
# # # # #
# # # # t
# # # # a
# # # # c
>>> ws.insert_northeast("snake", [4], [4, 3, 2, 1, 0])
>>> visualise_word_search(
... ws.board, add_fake_chars=False) # doctest: +NORMALIZE_WHITESPACE
# # # # e
# # # k #
# # a # t
# n # # a
s # # # c
"""
if board is None:
word_search = WordSearch(WORDS, WIDTH, HEIGHT)
word_search.generate_board()
board = word_search.board
result = ""
for row in range(len(board)):
for col in range(len(board[0])):
character = "#"
if (letter := board[row][col]) is not None:
character = letter
elif add_fake_chars:
character = chr(randint(97, 122))
result += f"{character} "
result += "\n"
print(result, end="")
if __name__ == "__main__":
import doctest
doctest.testmod()
visualise_word_search()