-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli_boggle.py
More file actions
369 lines (264 loc) · 10 KB
/
Copy pathcli_boggle.py
File metadata and controls
369 lines (264 loc) · 10 KB
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
from boggle_helper import solve_board
from blessed import Terminal
import time
class BoggleBoard:
"""
A class to represent a boggle_board setup
Attributes:
board (list): 2D array that holds chars of board
SIZE (int): width and height of the board
last_soluton (list): List of found words
last_time (float): Time taken on last solve
"""
def __init__(self, size):
self.SIZE = size
self.board = [[" " for x in range(self.SIZE)] for y in range(self.SIZE)]
self.last_solution = []
self.last_time = 0
self.auto_refresh = True
def solve(self, solver):
"""Takes in a method to solve the boggle board, updates internal last solution accordingly
Args:
solver (function): Takes in a boggle board in n x n array format, returns list of words
"""
start_time = time.time()
self.last_solution = list(solver(self.board))
self.last_solution.sort(key=len, reverse=True)
elapsed = time.time() - start_time
self.last_time = elapsed * 1000
def clear_board(self):
"""
Overwrites internal board according to it's SIZE to clear board.
"""
self.board = list([[" " for x in range(self.SIZE)] for y in range(self.SIZE)])
class Cursor:
def __init__(self, max_range):
self.max_range = max_range
self.x = 0
self.y = 0
def move_left(self):
if self.x > 0:
self.x -= 1
return True
return False
def move_right(self):
if self.x + 1 < self.max_range:
self.x += 1
return True
return False
def move_up(self):
if self.y > 0:
self.y -= 1
return True
return False
def move_down(self):
if self.y + 1 < self.max_range:
self.y += 1
return True
return False
def echo(message):
"""Prints arguement message, with no end char; does not flush buffer
Args:
message (str): [description]
"""
print(message, end="", flush=False)
def render_board(boggle_board, cursor, term):
"""Render an empty boggle board in the terminal window size from boggle_board/
Keyword arguments:
boggle_board -- Will take the size of the board frodm this boggle_board param.
cursor -- Unused
term -- Terminal.terminal window that will be drawn on
"""
vertical_line = "║"
horizontal_line = "═"
ul_corner = "╔"
ur_corner = "╗"
ll_corner = "╚"
lr_corner = "╝"
border_top = "╦"
border_left = "╠"
border_bot = "╩"
border_right = "╣"
junction = "╬"
top_row = (border_top + 3 * horizontal_line) * boggle_board.SIZE
top_row = ul_corner + top_row[1:] + ur_corner
bot_row = (border_bot + 3 * horizontal_line) * boggle_board.SIZE
bot_row = ll_corner + bot_row[1:] + lr_corner
line_row = (junction + 3 * horizontal_line) * boggle_board.SIZE
line_row = border_left + line_row[1:] + border_right
echo(top_row)
for y in range(boggle_board.SIZE):
echo(term.move_yx((2 * y) + 1, 0) + vertical_line)
echo((" " + vertical_line) * boggle_board.SIZE + "\n")
echo(line_row)
# Draw the bottom row
echo(term.move_yx((2 * (boggle_board.SIZE)), 0) + bot_row)
print()
def render_letters(boggle_board, cursor, term):
"""Renders the letters and cursorfrom the boggle board onto the terminal.
Keyword arguments:
boggle_board -- Renders letters from boggle_board.board
cursor -- Renders cursor from cursor.x and cursor.y
term -- Terminal.terminal window that will be drawn on
"""
for y in range(boggle_board.SIZE):
for x in range(boggle_board.SIZE):
ps = term.move_yx(1 + (y * 2), 1 + (4 * x))
ls = " " + boggle_board.board[x][y]
if len(ls) < 3:
ls += " "
if cursor.x == x and cursor.y == y:
ps += term.reverse(ls)
else:
ps += ls
echo(ps)
print("", end="", flush=True)
def render_footer(boggle_board, cursor, term):
"""Renders the footer and found words onto the terminal
Keyword arguments:
boggle_board -- Renders found words from boggle_board.last_solution
cursor -- Unused
term -- Terminal.terminal window that will be drawn on
"""
row_i = boggle_board.SIZE * 2 + 2
row_i = max(row_i, 11)
# clear the bottom
echo(term.move_yx(row_i, 0))
echo(term.clear_eos)
# Start doing the logic for printing out the words
word_i = 0
p = ""
len_solution = len(boggle_board.last_solution)
last_time = boggle_board.last_time
echo(term.move_yx(row_i, 0))
echo(f"Found {len_solution} words in {last_time:.2f} ms")
row_i += 1
echo(term.move_yx(row_i, 0))
while word_i < len(boggle_board.last_solution):
new = boggle_board.last_solution[word_i] + ", "
word_i += 1
if len(p) + len(new) <= term.width:
p += new
continue
if row_i > term.height - 4:
break
echo(p)
p = new
row_i += 1
echo(term.move_yx(row_i, 0))
print(p, flush=False)
print(
f"{len(boggle_board.last_solution) - word_i} words omitted", end="", flush=True
)
def render_side_pane(boggle_board, cursor, term):
"""Renders the sidebar help info onto the terminal from attributes.
Keyword arguments:
boggle_board -- Renders info fron boggle_board.auto_refresh, boggle_board.SIZE
cursor -- Unused
term -- Terminal.terminal window that will be drawn on
"""
column = boggle_board.SIZE * 4 + 2 + 4
echo(term.move_yx(1, column) + "CLI Boggle Helper")
echo(term.move_yx(2, column) + "By J. Bremen")
echo(term.move_yx(4, column) + "ESC to exit")
echo(term.move_yx(5, column) + "Arrow keys to navigate")
echo(term.move_yx(6, column) + "Press letter to place ")
echo(term.move_yx(7, column))
refresh_symbol = term.reverse_green("On")
if not boggle_board.auto_refresh:
refresh_symbol = term.reverse_red("Off")
echo(f"TAB toggles auto refresh ({refresh_symbol}) ")
echo(term.move_yx(8, column))
echo(f"Press 1-9 to clear adjust size ({boggle_board.SIZE})")
def render_entire_scene(boggle_board, cursor, term):
echo(term.home + term.clear)
render_board(boggle_board, cursor, term)
render_letters(boggle_board, cursor, term)
render_side_pane(boggle_board, cursor, term)
render_footer(boggle_board, cursor, term)
def main():
"""Main program entry point."""
SIZE = 4
term = Terminal()
boggle_board = BoggleBoard(SIZE)
cursor = Cursor(SIZE)
inp = None
loop = True
update_flag = True
moved_cursor_flag = False
pressed_letter_flag = False
tab_flag = False
P_WIDTH, P_HEIGHT = term.width, term.height
with term.hidden_cursor(), term.cbreak(), term.fullscreen():
while loop:
if term.width != P_WIDTH or term.height != P_HEIGHT:
update_flag = True
P_WIDTH, P_HEIGHT = term.width, term.height
if update_flag:
render_entire_scene(boggle_board, cursor, term)
update_flag = False
moved_cursor_flag = False
pressed_letter_flag = False
tab_flag = False
if moved_cursor_flag:
render_letters(boggle_board, cursor, term)
moved_cursor_flag = False
if pressed_letter_flag:
render_letters(boggle_board, cursor, term)
render_footer(boggle_board, cursor, term)
pressed_letter_flag = False
if tab_flag:
render_side_pane(boggle_board, cursor, term)
render_footer(boggle_board, cursor, term)
tab_flag = False
inp = term.inkey(timeout=None)
if not inp:
continue
# If the input is not a sequence, then it's a single character
# We care if it's a letter or a number, so check this
if not inp.is_sequence:
if 65 <= ord(inp.upper()) <= 90:
a = inp.upper()
if a == "Q":
a = "Qu"
boggle_board.board[cursor.x][cursor.y] = a
if boggle_board.auto_refresh:
boggle_board.solve(solve_board)
pressed_letter_flag = True
# To resize the board, just reset everything
if 49 <= ord(inp) <= 57:
SIZE = int(inp)
del boggle_board
del cursor
boggle_board = BoggleBoard(SIZE)
cursor = Cursor(SIZE)
update_flag = True
continue
inp_name = inp.name
if not inp_name:
continue
if inp_name == "KEY_UP":
moved_cursor_flag = cursor.move_up()
elif inp_name == "KEY_LEFT":
moved_cursor_flag = cursor.move_left()
elif inp_name == "KEY_RIGHT":
moved_cursor_flag = cursor.move_right()
elif inp_name == "KEY_DOWN":
moved_cursor_flag = cursor.move_down()
elif inp_name == "KEY_ESCAPE":
loop = False
elif inp_name == "KEY_ENTER":
boggle_board.solve(solve_board)
pressed_letter_flag = True
elif inp_name in ("KEY_DELETE", "KEY_BACKSPACE"):
boggle_board.board[cursor.x][cursor.y] = " "
if boggle_board.auto_refresh:
boggle_board.solve(solve_board)
pressed_letter_flag = True
elif inp_name == "KEY_TAB":
boggle_board.auto_refresh = not boggle_board.auto_refresh
if boggle_board.auto_refresh:
boggle_board.solve(solve_board)
tab_flag = True
if __name__ == "__main__":
main()