-
Notifications
You must be signed in to change notification settings - Fork 0
/
boggle.py
147 lines (107 loc) · 4.68 KB
/
boggle.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""Utilities related to Boggle game."""
from random import choice
import string
class Boggle():
def __init__(self, board_size=5):
self.board_size = board_size
self.words = self.read_dict("words.txt")
def read_dict(self, dict_path):
"""Read and return all words in dictionary."""
dict_file = open(dict_path)
words = [w.strip() for w in dict_file]
dict_file.close()
return words
def make_board(self):
"""Make and return a random boggle board."""
board = []
for y in range(self.board_size):
row = [choice(string.ascii_uppercase)
for i in range(self.board_size)]
board.append(row)
return board
# Update the check_valid_word function in boggle.py
def check_valid_word(self, board, word):
"""Check if a word is a valid word in the dictionary and/or the boggle board"""
word_exists = word in self.words
valid_word = self.find(board, word.upper())
if word_exists and valid_word:
result = "ok"
elif word_exists and not valid_word:
result = "not-on-board"
else:
result = "not-word"
return result
def find_from(self, board, word, y, x, seen):
"""Can we find a word on board, starting at x, y?"""
if x > 4 or y > 4:
return
# This is called recursively to find smaller and smaller words
# until all tries are exhausted or until success.
# Base case: this isn't the letter we're looking for.
if 0 <= y < len(board) and 0 <= x < len(board[y]) and board[y][x] != word[0]:
return False
# Base case: we've used this letter before in this current path
if (y, x) in seen:
return False
# Base case: we are down to the last letter --- so we win!
if len(word) == 1:
return True
# Otherwise, this letter is good, so note that we've seen it,
# and try of all of its neighbors for the first letter of the
# rest of the word
# This next line is a bit tricky: we want to note that we've seen the
# letter at this location. However, we only want the child calls of this
# to get that, and if we used `seen.add(...)` to add it to our set,
# *all* calls would get that, since the set is passed around. That would
# mean that once we try a letter in one call, it could never be tried again,
# even in a totally different path. Therefore, we want to create a *new*
# seen set that is equal to this set plus the new letter. Being a new
# object, rather than a mutated shared object, calls that don't descend
# from us won't have this `y,x` point in their seen.
#
# To do this, we use the | (set-union) operator, read this line as
# "rebind seen to the union of the current seen and the set of point(y,x))."
#
# (this could be written with an augmented operator as "seen |= {(y, x)}",
# in the same way "x = x + 2" can be written as "x += 2", but that would seem
# harder to understand).
seen = seen | {(y, x)}
# adding diagonals
if y > 0:
if self.find_from(board, word[1:], y - 1, x, seen):
return True
if y < 4:
if self.find_from(board, word[1:], y + 1, x, seen):
return True
if x > 0:
if self.find_from(board, word[1:], y, x - 1, seen):
return True
if x < 4:
if self.find_from(board, word[1:], y, x + 1, seen):
return True
# diagonals
if y > 0 and x > 0:
if self.find_from(board, word[1:], y - 1, x - 1, seen):
return True
if y < 4 and x < 4:
if self.find_from(board, word[1:], y + 1, x + 1, seen):
return True
if x > 0 and y < 4:
if self.find_from(board, word[1:], y + 1, x - 1, seen):
return True
if x < 4 and y > 0:
if self.find_from(board, word[1:], y - 1, x + 1, seen):
return True
# Couldn't find the next letter, so this path is dead
return False
def find(self, board, word):
"""Can word be found in board?"""
# Find starting letter --- try every spot on board and,
# win fast, should we find the word at that place.
for y in range(0, 5):
for x in range(0, 5):
if self.find_from(board, word, y, x, seen=set()):
return True
# We've tried every path from every starting square w/o luck.
# Sad panda.
return False