1eac3a55a6db4ae9f9ebfe7ed26d624340aac9f0
[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(l.lower() for l in open('/usr/share/dict/british-english').read() if l in string.ascii_letters)
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 guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]
83 return [l for l in self.ordered_letters if l not in guessed_letters][0]
84
85 class PlayerAlphabetical(PlayerFixedOrder):
86 def __init__(self):
87 super().__init__(string.ascii_lowercase)
88
89 class PlayerAlphabeticalReversed(PlayerFixedOrder):
90 def __init__(self):
91 super().__init__(list(reversed(string.ascii_lowercase)))
92
93 class PlayerFreqOrdered(PlayerFixedOrder):
94 def __init__(self):
95 super().__init__(LETTERS_IN_ORDER)
96
97 class PlayerDictFreqOrdered(PlayerFixedOrder):
98 def __init__(self):
99 super().__init__(DICT_LETTERS_IN_ORDER)
100
101
102 class PlayerAdaptive:
103 def __init__(self, words):
104 self.candidate_words = words
105
106 def guess(self, discovered, missed, lives):
107 self.filter_candidate_words(discovered, missed)
108 self.set_ordered_letters()
109 guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]
110 return [l for l in self.ordered_letters if l not in guessed_letters][0]
111
112 def filter_candidate_words(self, discovered, missed):
113 pass
114
115 def set_ordered_letters(self):
116 counts = collections.Counter(l.lower()
117 for l in ''.join(self.candidate_words) + string.ascii_lowercase
118 if l in string.ascii_letters)
119 self.ordered_letters = [p[0] for p in counts.most_common()]
120
121 def match(self, pattern, target, excluded=None):
122 if not excluded:
123 excluded = ''
124 if len(pattern) != len(target):
125 return False
126 for m, c in zip(pattern, target):
127 if m == '_' and c not in excluded:
128 # true
129 pass
130 elif m != '_' and m == c:
131 # true
132 pass
133 else:
134 return False
135 return True
136
137 class PlayerAdaptiveLength(PlayerAdaptive):
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 class PlayerAdaptiveIncludedLetters(PlayerAdaptive):
154 def filter_candidate_words(self, discovered, missed):
155 self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w)]
156
157
158 class PlayerAdaptiveExcludedLetters(PlayerAdaptive):
159 def filter_candidate_words(self, discovered, missed):
160 if missed:
161 empty_target = '_' * len(discovered)
162 self.candidate_words = [w for w in self.candidate_words if self.match(empty_target, w, missed)]
163
164
165 class PlayerAdaptivePattern(PlayerAdaptive):
166 def filter_candidate_words(self, discovered, missed):
167 attempted_letters = [l for l in discovered if l != '_'] + missed
168 self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w, attempted_letters)]
169
170
171 class PlayerAdaptiveSplit(PlayerAdaptivePattern):
172 def set_ordered_letters(self):
173 def letter_diff(l):
174 return abs(sum(1 if l in w else -1 for w in self.candidate_words))
175 possible_letters = set(''.join(self.candidate_words))
176 letter_diffs = [(l, letter_diff(l)) for l in possible_letters]
177 self.ordered_letters = [p[0] for p in sorted(letter_diffs, key=lambda p: p[1])]
178