This repository has been archived by the owner on Feb 13, 2021. It is now read-only.
/
Boggle.py
415 lines (344 loc) · 11.6 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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
from Tile import *
from Countdown import *
from TrieNode import *
from Tkinter import *
import tkMessageBox
from collections import defaultdict
import random, sys, types, os, pprint, math, platform
_inidle = type(sys.stdin) == types.InstanceType and \
sys.stdin.__class__.__name__ == 'PyShell'
class Boggle:
def __init__(self, root):
self.upd = 0
self.roots = {}
self.guess = []
self.guessList = []
self.stopClock = False
self.play = True
#define color scheme
self.black = "#0B0E06"
self.lyellow = "#F1D58A"
self.yellow = "#E7C963"
self.ygreen = "#A6AE33"
self.orange = "#C67822"
self.blue = "#438288"
self.green = "#4D793F"
self.dgreen = "#5C5E21"
# build the dictionary of words
self.readfile("dictionary.txt")
unplayable = True
# keep track of how many boards were tried
newBoardstat = 0
while unplayable:
#holds a double array of tile objects
self.grid = defaultdict(lambda : defaultdict(list))
self.grid = self.randomBoard()
# list for all possible words on the board
self.foundWords = []
for i in range(5):
for j in range(5):
#print self.grid[i][j].letter
self.findWords("", None, i, j)
# if there are not at least 20 possible words
# create a different board
if len(self.foundWords) > 20:
unplayable = False
else:
newBoardstat += 1
self.foundWords = []
# put the list in alphabetical order
self.foundWords.sort()
print "Tried %d" % newBoardstat + " board(s) before this one."
# temporary, to see all possible words
#for i in self.foundWords:
#print i
# create the board
self.drawBoard(root)
# method for reading in lines of a given filename
def readfile(self, filename):
file = open(filename, 'r')
# uppercase letters to standardize and strip newlines
line = file.readline().upper().strip()
while line:
self.insert(line)
line = file.readline().upper().strip()
file.close()
# insert a word into the dictionary
def insert(self, line):
# if the root node doesn't exist yet, add it
if not self.roots.has_key(line[0]):
self.roots[line[0]] = TrieNode(line[0])
#print "add "+self.roots[line[0]].letter+" to dictionary"
#else:
#print line[0]+" already exists in dictionary"
# insert the rest of the word, send the root node and rest of word
self.insertWord(line[1:], self.roots[line[0]])
# recursive method that inserts new word into trie tree
def insertWord(self, word, node):
#each node has children nodes to build diff words
#check if children nodes contain next letter
if node.children.has_key(word[0]):
nextChild = node.children.get(word[0])
else:
nextChild = TrieNode(word[0])
#print "add "+nextChild.letter
node.children[word[0]] = nextChild
if len(word) == 1:
nextChild.fullWord = True
else:
self.insertWord(word[1:], nextChild)
# depth first search starting with cell (i, j)
def findWords(self, prefix, node, row, col):
# add the next letter we're trying
prefix = prefix + self.grid[row][col].letter
if row < 0 or col < 0 or row >= 5 or col >= 5:
#system out of bounds
#print "System out of bounds"
return
if self.grid[row][col].visited:
#already visited, can't visit more than once
return
# tile is visited
self.grid[row][col].visited = True
# get the first node for the root layer
if len(prefix) == 1 and prefix[0] in self.roots:
# grab root node for first letter
node = self.roots[prefix[0]]
if not self.grid[row][col].letter in node.children and len(prefix) > 1:
self.grid[row][col].visited = False
return
else:
if len(prefix) > 1 and self.grid[row][col].letter in node.children:
node = node.children[self.grid[row][col].letter]
# words must be at least 3 long, a full word, and not in list
if len(prefix) > 2 and node.fullWord:
if not prefix in self.foundWords:
self.foundWords.append(prefix)
for a in range(-1, 2):
for b in range(-1, 2):
nrow = row+a
ncol = col+b
if nrow >= 0 and ncol >= 0 and nrow < 5 and ncol < 5 and math.fabs(a) != math.fabs(b):
self.findWords(prefix, node, nrow, ncol)
self.grid[row][col].visited = False
# contains instructions for drawing the boggle board
def drawBoard(self, root):
self.world = [-1, -1, 1, 1]
self.bgcolor = "#F5E7C4"
self.root = root
self.pad = 25
self._ALL = 'all'
WIDTH, HEIGHT = 400, 400
root.bind_all("<Escape>", lambda _ : root.destroy())
root.bind_all('<Key>', self.key)
root.bind_all('<Button-1>', self.mouseClick)
self.canvas = Canvas(root,
width = WIDTH,
height = HEIGHT,
bg = self.bgcolor,
bd = 10,
)
# start the countdown clock
self.clock = Countdown(10, self.canvas)
self.root.title('Boggle')
# draw the board
self.paintgraphics()
self.canvas.pack(fill=BOTH, expand=YES)
self.refresh()
def refresh(self):
self.upd += 1
if self.upd%10 == 0: self.upd = 0
self.redraw(self.upd)
# redraw screen at faster rate for smoother interface
if self.play: self.root.after(100,self.refresh)
def redraw(self, sec):
self.canvas.delete(self._ALL)
# only decrement after a full second passes
if self.clock.remaining > 0 and sec == 0 and not self.stopClock:
self.clock.remaining -= 1
if self.play and self.clock.remaining < 1:
self.scoreBoard()
if self.clock.remaining < 1:
self.stopClock = True
self.play = False
if self.stopClock:
self.clock.remaining = 0
self.play = False
self.clock.countdown()
self.paintgraphics()
# draw letters on the board
def paintgraphics(self):
# mac and windows size differently
if platform.system() == "Windows":
self.text_size=20
else:
self.text_size=35
actfill = self.black
if self.play:
actfill = self.orange
# 25px padding from edges
r, c = 30, 30
for i in range(5):
for j in range(5):
# draw letter on the canvas
self.canvas.create_text(r, c,
text=self.grid[i][j].letter,
fill=self.grid[i][j].color,
activefill=actfill,
font="Arial %d bold" % self.text_size,
)
self.grid[i][j].x, self.grid[i][j].y = r, c
# increment row
r += 65
# increment column outside first loop
c += 65
# reset to first row
r = 30
if self.play:
act2fill = actfill
fillclr = self.green
button_text = "Submit\n Word"
button_text2= "Possible: %d" % len(self.foundWords)
else:
act2fill = self.orange
fillclr = self.orange
button_text = "Play\nAgain?"
button_text2= "Found %d\nout of %d\npossible words" % (len(self.guessList), len(self.foundWords))
self.canvas.create_rectangle(160, 335, 250, 395,
fill=fillclr, outline=self.dgreen,
activefill=act2fill
)
self.canvas.create_text(175, 365,
font="Arial %d bold" % (self.text_size/2),
anchor="w",
text=button_text
)
self.canvas.create_text(260, 350,
font="Helvetica %d bold" % (self.text_size/2),
fill="#CFA130",
anchor="w",
text=button_text2
)
# print user's found words
if self.guessList:
coorx, coory = 340, 25
spaceH = 335
pad = len(self.foundWords) * 2
fontsize = (spaceH - pad)/len(self.foundWords)
#print "%d minus %d divided by %d = %d" %(spaceH, pad, len(self.foundWords), fontsize)
for word in self.guessList:
self.canvas.create_text(coorx, coory,
text=word.lower(),
anchor="w",
fill="black",
font="Arial %d" % (fontsize)
)
coory += fontsize+5
#reads in user input
def key(self, event):
if event.keysym == 'Return' and self.play:
self.tryWord()
for i in range(5):
for j in range(5):
if self.grid[i][j].letter.lower() == event.keysym.lower() and self.play:
self.buildWord(i, j)
def mouseClick(self, mouseevent):
# only process mouse clicks if it falls within the board
if mouseevent.x < 315 and mouseevent.y < 315 and self.play:
self.mouseWord(mouseevent.x, mouseevent.y)
if mouseevent.x > 160 and mouseevent.x < 250 and mouseevent.y > 335 and mouseevent.y < 395:
if self.play:
self.tryWord()
else:
self.restartGame(self.root)
def mouseWord(self, x, y):
# map mouse clicks to a particular letter
for i in range(5):
for j in range(5):
if x >= (self.grid[i][j].x-5) and x <= (self.grid[i][j].x+25) and y >= (self.grid[i][j].y-15) and y <= (self.grid[i][j].y+15):
self.buildWord(i, j)
def buildWord(self, r, c):
# check if it's first selected or near last selected
if not self.guess:
self.guess.append(self.grid[r][c])
self.grid[r][c].select(True)
else:
nrow = math.fabs(self.grid[r][c].row - self.guess[len(self.guess)-1].row)
ncol = math.fabs(self.grid[r][c].column - self.guess[len(self.guess)-1].column)
if nrow < 2 and ncol < 2 and nrow != ncol and not self.find(self.grid[r][c], self.guess):
self.guess.append(self.grid[r][c])
self.grid[r][c].select(True)
def find(self, f, seq):
try:
return (seq.index(f)+1)
except:
return False
def tryWord(self):
# user has hit the submit button
word = ""
for l in self.guess:
word = word + l.letter
l.select(False)
# if the word is less than 3 characters long: ERROR
if len(word) < 3:
tkMessageBox.showinfo(title="ERROR",message="Words must be longer than 2 characters.")
# if the word does not exist on the board: ERROR
elif not self.find(word, self.foundWords):
tkMessageBox.showinfo(title="ERROR",message=word+" is not a word.")
# if the word has already been found: ERROR
elif self.find(word, self.guessList):
tkMessageBox.showinfo(title="ERROR",message=word+" has already been found.")
# else add to the guessList
else:
self.guessList.append(word)
# reinitialize guess to empty
self.guess = []
# check if all words have been found
if len(self.guessList) == len(self.foundWords):
print "all words have been found"
self.stopClock = True
self.scoreBoard()
def randomBoard(self):
board = defaultdict(lambda : defaultdict(list))
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
alpha = 26
boardl = 25
row, col = 0, 0
while boardl > 0:
r = random.randrange(alpha)
a = alphabet[r]
board[row][col] = Tile(a, row, col)
alphabet = alphabet.replace(a, "")
col += 1
if col >= 5:
col = 0
row += 1
alpha -= 1
boardl -= 1
return board
def scoreBoard(self):
self.score = 0
for word in self.guessList:
if len(word) > 7: self.score += 11
elif len(word) == 7: self.score += 5
elif len(word) == 6: self.score += 3
elif len(word) == 5: self.score += 2
elif len(word) < 5: self.score += 1
print self.score
self.play = False
self.redraw(0)
def restartGame(self, root):
#restart the game somehow
print "here"
def main():
root = Tk()
#root.status = True
b = Boggle(root)
#if not root.status:
#del b
#root.status = True
#b = Boggle(root)
if not _inidle:
root.mainloop()
if __name__=='__main__':
main()