Finished the text
[cas-master-teacher-training.git] / hangman / hangman.py
1 import re
2 import random
3 import string
4 import collections
5
6 WORDS = [w.strip() for w in open('/usr/share/dict/british-english').readlines()
7 if re.match(r'^[a-z]*$', w.strip())]
8
9 LETTER_COUNTS = collections.Counter(l.lower() for l in open('../sherlock-holmes.txt').read() if l in string.ascii_letters)
10 LETTERS_IN_ORDER = [p[0] for p in LETTER_COUNTS.most_common()]
11
12 DICT_COUNTS = collections.Counter(''.join(WORDS))
13 DICT_LETTERS_IN_ORDER = [p[0] for p in DICT_COUNTS.most_common()]
14
15 STARTING_LIVES = 10
16
17 class Game:
18 def __init__(self, target, player=None, lives=STARTING_LIVES):
19 self.lives = lives
20 self.player = player
21 self.target = target
22 self.discovered = list('_' * len(target))
23 self.wrong_letters = []
24 self.game_finished = False
25 self.game_won = False
26 self.game_lost = False
27
28 def find_all(self, letter):
29 return [p for p, l in enumerate(self.target) if l == letter]
30
31 def update_discovered_word(self, guessed_letter):
32 locations = self.find_all(guessed_letter)
33 for location in locations:
34 self.discovered[location] = guessed_letter
35 return self.discovered
36
37 def do_turn(self):
38 if self.player:
39 guess = self.player.guess(self.discovered, self.wrong_letters, self.lives)
40 else:
41 guess = self.ask_for_guess()
42 if guess in self.target:
43 self.update_discovered_word(guess)
44 else:
45 self.lives -= 1
46 if guess not in self.wrong_letters:
47 self.wrong_letters += [guess]
48 if self.lives == 0:
49 self.game_finished = True
50 self.game_lost = True
51 if '_' not in self.discovered:
52 self.game_finished = True
53 self.game_won = True
54
55 def ask_for_guess(self):
56 print('Word:', ' '.join(self.discovered),
57 ' : Lives =', self.lives,
58 ', wrong guesses:', ' '.join(sorted(self.wrong_letters)))
59 guess = input('Enter letter: ').strip().lower()[0]
60 return guess
61
62 def play_game(self):
63 while not self.game_finished:
64 self.do_turn()
65 if not self.player:
66 self.report_on_game()
67 return self.game_won
68
69 def report_on_game(self):
70 if self.game_won:
71 print('You won! The word was', self.target)
72 else:
73 print('You lost. The word was', self.target)
74 return self.game_won
75
76
77 class PlayerFixedOrder:
78 def __init__(self, ordered_letters):
79 self.ordered_letters = ordered_letters
80
81 def guess(self, discovered, missed, lives):
82 return [l for l in self.ordered_letters if l not in discovered + missed][0]
83
84 class PlayerAlphabetical(PlayerFixedOrder):
85 def __init__(self):
86 super().__init__(string.ascii_lowercase)
87
88 class PlayerAlphabeticalReversed(PlayerFixedOrder):
89 def __init__(self):
90 super().__init__(list(reversed(string.ascii_lowercase)))
91
92 class PlayerFreqOrdered(PlayerFixedOrder):
93 def __init__(self):
94 super().__init__(LETTERS_IN_ORDER)
95
96 class PlayerDictFreqOrdered(PlayerFixedOrder):
97 def __init__(self):
98 super().__init__(DICT_LETTERS_IN_ORDER)
99
100
101 class PlayerAdaptive:
102 def __init__(self, words):
103 self.candidate_words = words
104
105 def guess(self, discovered, missed, lives):
106 self.filter_candidate_words(discovered, missed)
107 self.set_ordered_letters()
108 return [l for l in self.ordered_letters if l not in discovered + missed][0]
109
110 def filter_candidate_words(self, discovered, missed):
111 pass
112
113 def set_ordered_letters(self):
114 counts = collections.Counter(l.lower()
115 for l in ''.join(self.candidate_words) + string.ascii_lowercase
116 if l in string.ascii_letters)
117 self.ordered_letters = [p[0] for p in counts.most_common()]
118
119 def match(self, pattern, target, excluded=None):
120 if not excluded:
121 excluded = ''
122 if len(pattern) != len(target):
123 return False
124 for m, c in zip(pattern, target):
125 if m == '_' and c not in excluded:
126 # true
127 pass
128 elif m != '_' and m == c:
129 # true
130 pass
131 else:
132 return False
133 return True
134
135 class PlayerAdaptiveLength(PlayerAdaptive):
136 def __init__(self, words):
137 super().__init__(words)
138 self.word_len = None
139 self.ordered_letters = None
140
141 def filter_candidate_words(self, discovered, missed):
142 if not self.word_len:
143 self.word_len = len(discovered)
144 self.candidate_words = [w for w in self.candidate_words if len(w) == self.word_len]
145
146 def set_ordered_letters(self):
147 if not self.ordered_letters:
148 super().set_ordered_letters()
149
150
151 class PlayerAdaptiveIncludedLetters(PlayerAdaptive):
152 def filter_candidate_words(self, discovered, missed):
153 self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w)]
154
155
156 class PlayerAdaptiveExcludedLetters(PlayerAdaptive):
157 def filter_candidate_words(self, discovered, missed):
158 if missed:
159 empty_target = '_' * len(discovered)
160 self.candidate_words = [w for w in self.candidate_words if self.match(empty_target, w, missed)]
161
162
163 class PlayerAdaptivePattern(PlayerAdaptive):
164 def filter_candidate_words(self, discovered, missed):
165 attempted_letters = [l for l in discovered if l != '_'] + missed
166 self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w, attempted_letters)]
167
168
169 class PlayerAdaptiveSplit(PlayerAdaptivePattern):
170 def set_ordered_letters(self):
171 def letter_diff(l):
172 return abs(sum(1 if l in w else -1 for w in self.candidate_words))
173 possible_letters = set(''.join(self.candidate_words))
174 letter_diffs = [(l, letter_diff(l)) for l in possible_letters]
175 self.ordered_letters = [p[0] for p in sorted(letter_diffs, key=lambda p: p[1])]
176