Added split-candidate player
[cas-master-teacher-training.git] / hangman / hangman.py
1
2 # coding: utf-8
3
4 # In[4]:
5
6 import re
7 import random
8 import string
9 import collections
10
11
12 # In[5]:
13
14 WORDS = [w.strip() for w in open('/usr/share/dict/british-english').readlines()
15 if re.match(r'^[a-z]*$', w.strip())]
16
17
18 # In[6]:
19
20 LETTER_COUNTS = collections.Counter(l.lower() for l in open('../sherlock-holmes.txt').read() if l in string.ascii_letters)
21 LETTERS_IN_ORDER = [p[0] for p in LETTER_COUNTS.most_common()]
22
23
24 # In[7]:
25
26 STARTING_LIVES = 10
27
28
29 # In[8]:
30
31 class Game:
32 def __init__(self, target, player=None, lives=STARTING_LIVES):
33 self.lives = lives
34 self.player = player
35 self.target = target
36 self.discovered = list('_' * len(target))
37 self.wrong_letters = []
38 self.game_finished = False
39 self.game_won = False
40 self.game_lost = False
41
42 def find_all(self, letter):
43 return [p for p, l in enumerate(self.target) if l == letter]
44
45 def update_discovered_word(self, guessed_letter):
46 locations = self.find_all(guessed_letter)
47 for location in locations:
48 self.discovered[location] = guessed_letter
49 return self.discovered
50
51 def do_turn(self):
52 if self.player:
53 guess = self.player.guess(self.discovered, self.wrong_letters, self.lives)
54 else:
55 guess = self.ask_for_guess()
56 if guess in self.target:
57 self.update_discovered_word(guess)
58 else:
59 self.lives -= 1
60 if guess not in self.wrong_letters:
61 self.wrong_letters += [guess]
62 if self.lives == 0:
63 self.game_finished = True
64 self.game_lost = True
65 if '_' not in self.discovered:
66 self.game_finished = True
67 self.game_won = True
68
69 def ask_for_guess(self):
70 print('Word:', ' '.join(self.discovered),
71 ' : Lives =', self.lives,
72 ', wrong guesses:', ' '.join(sorted(self.wrong_letters)))
73 guess = input('Enter letter: ').strip().lower()[0]
74 return guess
75
76 def play_game(self):
77 while not self.game_finished:
78 self.do_turn()
79 if not self.player:
80 self.report_on_game()
81 return self.game_won
82
83 def report_on_game(self):
84 if self.game_won:
85 print('You won! The word was', self.target)
86 else:
87 print('You lost. The word was', self.target)
88 return self.game_won
89
90
91 # In[9]:
92
93 DICT_COUNTS = collections.Counter(l.lower() for l in open('/usr/share/dict/british-english').read() if l in string.ascii_letters)
94 DICT_LETTERS_IN_ORDER = [p[0] for p in DICT_COUNTS.most_common()]
95
96
97 # In[10]:
98
99 class PlayerAdaptiveNoRegex:
100 def __init__(self, words):
101 self.candidate_words = words
102
103 def guess(self, discovered, missed, lives):
104 self.filter_candidate_words(discovered, missed)
105 self.set_ordered_letters()
106 guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]
107 return [l for l in self.ordered_letters if l not in guessed_letters][0]
108
109 def filter_candidate_words(self, discovered, missed):
110 pass
111
112 def set_ordered_letters(self):
113 counts = collections.Counter(l.lower()
114 for l in ''.join(self.candidate_words) + string.ascii_lowercase
115 if l in string.ascii_letters)
116 self.ordered_letters = [p[0] for p in counts.most_common()]
117
118 def match(self, pattern, target, excluded=None):
119 if not excluded:
120 excluded = ''
121 if len(pattern) != len(target):
122 return False
123 for m, c in zip(pattern, target):
124 if m == '_' and c not in excluded:
125 # true
126 pass
127 elif m != '_' and m == c:
128 # true
129 pass
130 else:
131 return False
132 return True
133
134
135 # In[11]:
136
137 class PlayerAdaptiveLengthNoRegex(PlayerAdaptiveNoRegex):
138 def __init__(self, words):
139 super().__init__(words)
140 self.word_len = None
141 self.ordered_letters = None
142
143 def filter_candidate_words(self, discovered, missed):
144 if not self.word_len:
145 self.word_len = len(discovered)
146 self.candidate_words = [w for w in self.candidate_words if len(w) == self.word_len]
147
148 def set_ordered_letters(self):
149 if not self.ordered_letters:
150 super().set_ordered_letters()
151
152
153 # In[12]:
154
155 class PlayerAdaptiveIncludedLettersNoRegex(PlayerAdaptiveNoRegex):
156 def filter_candidate_words(self, discovered, missed):
157 self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w)]
158
159
160 # In[13]:
161
162 class PlayerAdaptiveExcludedLettersNoRegex(PlayerAdaptiveNoRegex):
163 def filter_candidate_words(self, discovered, missed):
164 if missed:
165 empty_target = '_' * len(discovered)
166 self.candidate_words = [w for w in self.candidate_words if self.match(empty_target, w, missed)]
167
168
169 # In[14]:
170
171 class PlayerAdaptivePatternNoRegex(PlayerAdaptiveNoRegex):
172 def filter_candidate_words(self, discovered, missed):
173 attempted_letters = [l for l in discovered if l != '_'] + missed
174 self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w, attempted_letters)]
175
176
177 # In[15]:
178
179 get_ipython().run_cell_magic('timeit', '', '\nwins = 0\nfor _ in range(1000):\n g = Game(random.choice(WORDS), player=PlayerAdaptivePatternNoRegex(WORDS))\n g.play_game()\n if g.game_won:\n wins += 1\nprint(wins)')
180
181
182 # In[21]:
183
184 len([w for w in WORDS if 'r' in w])
185
186
187 # In[22]:
188
189 len([w for w in WORDS if 'r' not in w])
190
191
192 # In[19]:
193
194 letter_diffs = []
195 for l in string.ascii_lowercase:
196 n = 0
197 for w in WORDS:
198 if l in w:
199 n += 1
200 else:
201 n -=1
202 letter_diffs += [(l, abs(n))]
203 sorted(letter_diffs, key=lambda p: p[1])
204
205
206 # In[23]:
207
208 def letter_diff(l):
209 return abs(sum(1 if l in w else -1 for w in WORDS))
210
211 letter_diffs = [(l, letter_diff(l))
212 for l in string.ascii_lowercase]
213 sorted(letter_diffs, key=lambda p: p[1])
214
215
216 # In[71]:
217
218 class PlayerAdaptiveSplit(PlayerAdaptivePatternNoRegex):
219 def set_ordered_letters(self):
220 def letter_diff(l):
221 return abs(sum(1 if l in w else -1 for w in self.candidate_words))
222 possible_letters = set(''.join(self.candidate_words))
223 # if len(self.candidate_words) > 1:
224 letter_diffs = [(l, letter_diff(l)) for l in possible_letters]
225 self.ordered_letters = [p[0] for p in sorted(letter_diffs, key=lambda p: p[1])]
226 # else:
227 # self.ordered_letters = list(self.candidate_words[0])
228
229
230 # In[72]:
231
232 g = Game(random.choice(WORDS), player=PlayerAdaptiveSplit(WORDS))
233 g.play_game()
234
235
236 # In[73]:
237
238 g.target
239
240
241 # In[74]:
242
243 g.discovered
244
245
246 # In[75]:
247
248 g.wrong_letters
249
250
251 # In[78]:
252
253 get_ipython().run_cell_magic('timeit', '', '\nwins = 0\nfor _ in range(1000):\n g = Game(random.choice(WORDS), player=PlayerAdaptiveSplit(WORDS))\n g.play_game()\n if g.game_won:\n wins += 1\nprint(wins)')
254
255
256 # In[54]:
257
258 p=PlayerAdaptiveSplit(WORDS)
259
260
261 # In[55]:
262
263 dsc = ['_'] * len('recognition')
264 dsc
265
266
267 # In[56]:
268
269 p.guess(dsc, [], 10)
270
271
272 # In[57]:
273
274 len(p.candidate_words)
275
276
277 # In[58]:
278
279 p.guess(['_', '_', '_', 'o', '_', '_', '_', '_', '_', 'o', '_'], [], 10)
280
281
282 # In[59]:
283
284 p.candidate_words
285
286
287 # In[60]:
288
289 p.guess(['_', '_', 'c', 'o', '_', '_', '_', '_', '_', 'o', '_'], [], 10)
290
291
292 # In[61]:
293
294 p.candidate_words
295
296
297 # In[62]:
298
299 p.guess(['_', '_', 'c', 'o', '_', '_', '_', '_', '_', 'o', '_'], ['a'], 9)
300
301
302 # In[63]:
303
304 p.candidate_words
305
306
307 # In[64]:
308
309 p.guess(['_', '_', 'c', 'o', '_', '_', '_', '_', '_', 'o', '_'], ['a', 'd'], 8)
310
311
312 # In[65]:
313
314 p.candidate_words
315
316
317 # In[67]:
318
319 g = Game('recognition', player=PlayerAdaptiveSplit(WORDS))
320 g.play_game()
321
322
323 # In[68]:
324
325 g.discovered
326
327
328 # In[69]:
329
330 g.lives
331
332
333 # In[79]:
334
335 get_ipython().run_cell_magic('timeit', '', '\nwins = 0\nfor _ in range(10000):\n g = Game(random.choice(WORDS), player=PlayerAdaptiveSplit(WORDS))\n g.play_game()\n if g.game_won:\n wins += 1\nprint(wins)')
336
337
338 # In[81]:
339
340 get_ipython().run_cell_magic('timeit', '', '\nwins = 0\nfor _ in range(10000):\n g = Game(random.choice(WORDS), player=PlayerAdaptivePatternNoRegex(WORDS))\n g.play_game()\n if g.game_won:\n wins += 1\nprint(wins)')
341
342
343 # In[90]:
344
345 for w in random.sample(WORDS, 5000):
346 gp = Game(w, player=PlayerAdaptivePatternNoRegex(WORDS))
347 gp.play_game()
348 gs = Game(w, player=PlayerAdaptiveSplit(WORDS))
349 gs.play_game()
350 if not gp.game_won and not gs.game_won:
351 print('Both:::::', gp.target, 'Pattern:', '[' + ' '.join(gp.discovered) + ']', ''.join(gp.wrong_letters),
352 ':: Split:', '[' + ' '.join(gs.discovered) + ']', ''.join(gs.wrong_letters))
353 if not gp.game_won and gs.game_won:
354 print('Pattern::', gp.target, '[' + ' '.join(gp.discovered) + ']', ''.join(gp.wrong_letters))
355 if gp.game_won and not gs.game_won:
356 print('Split::::', gs.target, '[' + ' '.join(gs.discovered) + ']', ''.join(gs.wrong_letters))
357
358
359 # In[91]:
360
361 gs = Game('businesses', player=PlayerAdaptiveSplit(WORDS))
362
363
364 # In[170]:
365
366 g = Game('feminism', player=PlayerAdaptiveSplit(WORDS))
367 while not g.game_finished:
368 guess = g.player.guess(g.discovered, g.wrong_letters, g.lives)
369 print(g.target, '(' + str(g.lives) + ')',
370 '[' + ' '.join(g.discovered) + ']', ''.join(g.wrong_letters),
371 ';', len(g.player.candidate_words), 'candidate words')
372 print('Guess = ', guess)
373 g.do_turn()
374
375
376 # In[172]:
377
378 g = Game('feminism', player=PlayerAdaptivePatternNoRegex(WORDS))
379 while not g.game_finished:
380 guess = g.player.guess(g.discovered, g.wrong_letters, g.lives)
381 print(g.target, '(' + str(g.lives) + ')',
382 '[' + ' '.join(g.discovered) + ']', ''.join(g.wrong_letters),
383 ';', len(g.player.candidate_words), 'candidate words')
384 print('Guess = ', guess)
385 g.do_turn()
386
387
388 # In[171]:
389
390 g.player.candidate_words
391
392
393 # In[ ]:
394
395
396