-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinteractive.py
More file actions
159 lines (139 loc) · 5.97 KB
/
interactive.py
File metadata and controls
159 lines (139 loc) · 5.97 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
from solving.puzzle import Puzzle
from solving.solver import Solver
from words.dictionary import Dictionary
from words.word import Word
from time import perf_counter
APP_NAME = "WordLadder"
PROMPT_PREFIX = APP_NAME + "> "
MINIMUM_WORD_LENGTH = 2
MAXIMUM_WORD_LENGTH = 15
MINIMUM_LADDER_LENGTH = 1
MAXIMUM_LADDER_LENGTH = 20
STEPS = [
"Enter start word: ",
"Enter final word: ",
'Maximum ladder length? [%d-%d, or return]: ' % (MINIMUM_LADDER_LENGTH, MAXIMUM_LADDER_LENGTH)
]
class Interactive(object):
def __init__(self, args):
# not using these yet
self.args = args
self.on_step: int = 0
self.dictionary: Dictionary = None
self.dictionary_load_time: int = 0
self.start_word: Word = None
self.end_word: Word = None
self.maximum_ladder_length: int = -1
def run(self):
again = True
while again:
while self.on_step < len(STEPS):
self._process_input()
self.on_step = 0
self._solve()
print()
inp = input('%sRun again? [y/n]: ' % PROMPT_PREFIX)
again = inp == "y" or inp == "Y"
print()
def _process_input(self):
inp = input('%s%s' % (PROMPT_PREFIX, STEPS[self.on_step]))
ok = False
if self.on_step == 0:
ok = self._set_start_word(inp)
elif self.on_step == 1:
ok = self._set_end_word(inp)
elif self.on_step == 2:
ok = self._set_maximum_ladder_length(inp)
if ok:
self.on_step += 1
def _set_start_word(self, inp: str) -> bool:
if len(inp) < MINIMUM_WORD_LENGTH or len(inp) > MAXIMUM_WORD_LENGTH:
print(red(' Please enter a word with between %d and %d characters' % (MINIMUM_WORD_LENGTH, MAXIMUM_WORD_LENGTH)))
return False
self._load_dictionary(len(inp))
self.start_word = self._validate_word(inp)
return self.start_word is not None
def _set_end_word(self, inp: str) -> bool:
if len(inp) != self.dictionary.word_length:
print(red(" Final word length must match start word length!"))
return False
self.end_word = self._validate_word(inp)
return self.end_word is not None
def _validate_word(self, inp: str) -> Word or None:
word: Word = self.dictionary[inp]
if word is None:
print(red(' Word \'%s\' does not exist!' % inp))
return
elif word.is_island:
print(red(' Word \'%s\' is an island word (cannot change single letter to form another word)' % inp))
return
return word
def _set_maximum_ladder_length(self, inp: str) -> bool:
if len(inp) == 0:
print(green(" No answer - assuming auto calc of minimum ladder length"))
self.maximum_ladder_length = -1
return True
try:
inp_int = int(inp)
if inp_int < MINIMUM_LADDER_LENGTH or inp_int > MAXIMUM_LADDER_LENGTH:
raise TypeError
self.maximum_ladder_length = inp_int
return True
except TypeError:
print(red(' Invalid input (please enter a integer between %d and %d)' % (MINIMUM_LADDER_LENGTH, MAXIMUM_LADDER_LENGTH)))
return False
def _load_dictionary(self, word_length: int):
start = perf_counter()
self.dictionary = Dictionary(word_length)
self.dictionary_load_time = (perf_counter() - start) * 1000
def _solve(self):
print('Took %s to load dictionary' % green('%.2fms' % self.dictionary_load_time))
puzzle = Puzzle(self.start_word, self.end_word)
if self.maximum_ladder_length == -1:
start = perf_counter()
min_ladder = puzzle.calculate_minimum_ladder_length()
took = (perf_counter() - start) * 1000
if min_ladder is None:
print(red('Cannot solve \'%s\' to \'%s\' (took %.2fms to determine that)' % (self.start_word, self.end_word, took)))
return
self.maximum_ladder_length = min_ladder
print('Took %s to determine minimum ladder length of %s' % (green('%.2fms' % took), green('%d' % min_ladder)))
solver = Solver(puzzle)
start = perf_counter()
solutions = solver.solve(self.maximum_ladder_length)
took = (perf_counter() - start) * 1000
if len(solutions) == 0:
print(red('Took %.2fms to find no solutions (explored %d solutions)' % (took, solver.explored_count)))
return
print('Took %s to find %s solutions (explored %s solutions)'
% (green('%.2fms' % took), green(len(solutions)), green(solver.explored_count)))
self._display_solutions(solutions)
def _display_solutions(self, solutions):
solutions.sort()
page_start: int = 0
length: int = len(solutions)
while page_start < (length - 1):
inp = input(
'%sList%s solutions? (Enter \'n\' for no, \'y\' or return for next 10, \'all\' for all or how many): '
% (PROMPT_PREFIX, " more" if page_start > 0 else ""))
limit = 10
if inp == "n" or inp == "N":
return
elif inp == "all":
limit = length
elif len(inp) > 0 and inp != "y" and inp != "Y":
try:
inp_int = int(inp)
if inp_int > 0:
limit = inp_int
except TypeError:
limit = 10
for page_start in range(page_start, min(page_start + limit, length)):
print('%d/%d %s' % (page_start + 1, length, green(solutions[page_start])))
TERMINAL_COLOUR_RED = "\u001b[31m"
TERMINAL_COLOUR_GREEN = "\u001b[32m"
TERMINAL_COLOUR_BLACK = "\u001b[0m"
def green(msg):
return TERMINAL_COLOUR_GREEN + str(msg) + TERMINAL_COLOUR_BLACK
def red(msg):
return TERMINAL_COLOUR_RED + str(msg) + TERMINAL_COLOUR_BLACK