]>
Commit | Line | Data |
---|---|---|
0bea2d47 HH |
1 | """Hangman.py, a simple wxPython game, inspired by the |
2 | old bsd game by Ken Arnold. | |
eeec36a2 HH |
3 | From the original man page: |
4 | ||
5 | In hangman, the computer picks a word from the on-line | |
6 | word list and you must try to guess it. The computer | |
7 | keeps track of which letters have been guessed and how | |
8 | many wrong guesses you have made on the screen in a | |
9 | graphic fashion. | |
10 | ||
11 | That says it all, doesn't it? | |
12 | ||
13 | Have fun with it, | |
14 | ||
15 | Harm van der Heijden (H.v.d.Heijden@phys.tue.nl)""" | |
16 | ||
17 | import random,re,string | |
35c1ca4b HH |
18 | from wxPython.wx import * |
19 | ||
20 | class WordFetcher: | |
eeec36a2 HH |
21 | def __init__(self, filename, min_length = 5): |
22 | self.min_length = min_length | |
0bea2d47 | 23 | print "Trying to open file %s" % (filename,) |
35c1ca4b HH |
24 | try: |
25 | f = open(filename, "r") | |
26 | except: | |
27 | print "Couldn't open dictionary file %s, using build-ins" % (filename,) | |
28 | self.words = self.builtin_words | |
0bea2d47 | 29 | self.filename = None |
35c1ca4b | 30 | return |
eeec36a2 HH |
31 | self.words = f.read() |
32 | self.filename = filename | |
33 | print "Got %d bytes." % (len(self.words),) | |
34 | def SetMinLength(min_length): | |
35 | self.min_length = min_length | |
35c1ca4b | 36 | def Get(self): |
eeec36a2 HH |
37 | reg = re.compile('\s+([a-zA-Z]+)\s+') |
38 | n = 50 # safety valve; maximum number of tries to find a suitable word | |
39 | while n: | |
40 | index = int(random.random()*len(self.words)) | |
41 | m = reg.search(self.words[index:]) | |
42 | if m and len(m.groups()[0]) >= self.min_length: break | |
43 | n = n - 1 | |
44 | if n: return string.lower(m.groups()[0]) | |
45 | return "error" | |
46 | builtin_words = ' albatros banana electrometer eggshell' | |
47 | ||
48 | def stdprint(x): | |
49 | print x | |
50 | ||
51 | class URLWordFetcher(WordFetcher): | |
52 | def __init__(self, url): | |
53 | self.OpenURL(url) | |
54 | WordFetcher.__init__(self, "hangman_dict.txt") | |
55 | def logprint(self,x): | |
56 | print x | |
57 | def RetrieveAsFile(self, host, path=''): | |
58 | from httplib import HTTP | |
59 | try: | |
60 | h = HTTP(host) | |
61 | except: | |
62 | self.logprint("Failed to create HTTP connection to %s... is the network available?" % (host)) | |
63 | return None | |
64 | h.putrequest('GET',path) | |
65 | h.putheader('Accept','text/html') | |
66 | h.putheader('Accept','text/plain') | |
67 | h.endheaders() | |
68 | errcode, errmsg, headers = h.getreply() | |
69 | if errcode != 200: | |
70 | self.logprint("HTTP error code %d: %s" % (errcode, errmsg)) | |
71 | return None | |
72 | f = h.getfile() | |
73 | return f | |
74 | def OpenURL(self,url): | |
75 | from htmllib import HTMLParser | |
76 | import formatter | |
77 | self.url = url | |
78 | m = re.match('http://([^/]+)(/\S*)\s*', url) | |
79 | if m: | |
80 | host = m.groups()[0] | |
81 | path = m.groups()[1] | |
82 | else: | |
83 | m = re.match('http://(\S+)\s*', url) | |
84 | if not m: | |
85 | # Invalid URL | |
86 | self.logprint("Invalid or unsupported URL: %s" % (url)) | |
87 | return | |
88 | host = m.groups()[0] | |
89 | path = '' | |
90 | f = self.RetrieveAsFile(host,path) | |
91 | if not f: | |
92 | self.logprint("Could not open %s" % (url)) | |
93 | return | |
94 | self.logprint("Receiving data...") | |
95 | data = f.read() | |
96 | tmp = open('hangman_dict.txt','w') | |
97 | fmt = formatter.AbstractFormatter(formatter.DumbWriter(tmp)) | |
98 | p = HTMLParser(fmt) | |
99 | self.logprint("Parsing data...") | |
100 | p.feed(data) | |
101 | p.close() | |
102 | tmp.close() | |
35c1ca4b | 103 | |
0d236c34 | 104 | class HangmanWnd(wxWindow): |
eeec36a2 HH |
105 | def __init__(self, parent, id, pos=wxDefaultPosition, size=wxDefaultSize): |
106 | wxWindow.__init__(self, parent, id, pos, size) | |
0d236c34 | 107 | self.SetBackgroundColour(wxNamedColour('white')) |
8a252c00 HH |
108 | if wxPlatform == '__WXGTK__': |
109 | self.font = wxFont(12, wxMODERN, wxNORMAL, wxNORMAL) | |
110 | else: | |
111 | self.font = wxFont(10, wxMODERN, wxNORMAL, wxNORMAL) | |
0d236c34 | 112 | self.SetFocus() |
0d236c34 HH |
113 | def StartGame(self, word): |
114 | self.word = word | |
115 | self.guess = [] | |
116 | self.tries = 0 | |
117 | self.misses = 0 | |
118 | self.Draw() | |
119 | def EndGame(self): | |
120 | self.misses = 7; | |
121 | self.guess = map(chr, range(ord('a'),ord('z')+1)) | |
122 | self.Draw() | |
123 | def HandleKey(self, key): | |
eeec36a2 | 124 | self.message = "" |
0d236c34 | 125 | if self.guess.count(key): |
eeec36a2 | 126 | self.message = 'Already guessed %s' % (key,) |
0d236c34 HH |
127 | return 0 |
128 | self.guess.append(key) | |
129 | self.guess.sort() | |
130 | self.tries = self.tries+1 | |
131 | if not key in self.word: | |
132 | self.misses = self.misses+1 | |
133 | if self.misses == 7: | |
134 | self.EndGame() | |
135 | return 1 | |
136 | has_won = 1 | |
137 | for letter in self.word: | |
138 | if not self.guess.count(letter): | |
139 | has_won = 0 | |
140 | break | |
141 | if has_won: | |
142 | self.Draw() | |
143 | return 2 | |
144 | self.Draw() | |
145 | return 0 | |
35c1ca4b HH |
146 | def Draw(self, dc = None): |
147 | if not dc: | |
0d236c34 | 148 | dc = wxClientDC(self) |
0bea2d47 | 149 | dc.SetFont(self.font) |
35c1ca4b | 150 | dc.Clear() |
0d236c34 HH |
151 | (x,y) = self.GetSizeTuple() |
152 | x1 = x-200; y1 = 20 | |
35c1ca4b HH |
153 | for letter in self.word: |
154 | if self.guess.count(letter): | |
155 | dc.DrawText(letter, x1, y1) | |
156 | else: | |
157 | dc.DrawText('.', x1, y1) | |
158 | x1 = x1 + 10 | |
0d236c34 HH |
159 | x1 = x-200 |
160 | dc.DrawText("tries %d misses %d" % (self.tries,self.misses),x1,50) | |
161 | guesses = "" | |
162 | for letter in self.guess: | |
163 | guesses = guesses + letter | |
164 | dc.DrawText("guessed:", x1, 70) | |
0bea2d47 HH |
165 | dc.DrawText(guesses[:13], x1+80, 70) |
166 | dc.DrawText(guesses[13:], x1+80, 90) | |
35c1ca4b HH |
167 | dc.SetUserScale(x/1000., y/1000.) |
168 | self.DrawVictim(dc) | |
169 | def DrawVictim(self, dc): | |
170 | dc.SetPen(wxPen(wxNamedColour('black'), 20)) | |
171 | dc.DrawLines([(10, 980), (10,900), (700,900), (700,940), (720,940), | |
172 | (720,980), (900,980)]) | |
173 | dc.DrawLines([(100,900), (100, 100), (300,100)]) | |
174 | dc.DrawLine(100,200,200,100) | |
175 | if ( self.misses == 0 ): return | |
176 | dc.SetPen(wxPen(wxNamedColour('blue'), 10)) | |
177 | dc.DrawLine(300,100,300,200) | |
178 | if ( self.misses == 1 ): return | |
179 | dc.DrawEllipse(250,200,100,100) | |
180 | if ( self.misses == 2 ): return | |
181 | dc.DrawLine(300,300,300,600) | |
182 | if ( self.misses == 3) : return | |
183 | dc.DrawLine(300,300,250,550) | |
184 | if ( self.misses == 4) : return | |
185 | dc.DrawLine(300,300,350,550) | |
186 | if ( self.misses == 5) : return | |
187 | dc.DrawLine(300,600,350,850) | |
188 | if ( self.misses == 6) : return | |
189 | dc.DrawLine(300,600,250,850) | |
190 | def OnPaint(self, event): | |
0d236c34 | 191 | dc = wxPaintDC(self) |
35c1ca4b | 192 | self.Draw(dc) |
eeec36a2 HH |
193 | |
194 | class HangmanDemo(HangmanWnd): | |
195 | def __init__(self, wf, parent, id, pos, size): | |
196 | HangmanWnd.__init__(self, parent, id, pos, size) | |
197 | self.StartGame("dummy") | |
198 | self.start_new = 1 | |
199 | self.wf = wf | |
200 | self.delay = 500 | |
201 | self.timer = self.PlayTimer(self.MakeMove) | |
202 | def MakeMove(self): | |
203 | self.timer.Stop() | |
204 | if self.start_new: | |
205 | self.StartGame(self.wf.Get()) | |
206 | self.start_new = 0 | |
207 | self.left = list('aaaabcdeeeeefghiiiiijklmnnnoooopqrssssttttuuuuvwxyz') | |
208 | else: | |
209 | key = self.left[int(random.random()*len(self.left))] | |
210 | while self.left.count(key): self.left.remove(key) | |
211 | self.start_new = self.HandleKey(key) | |
212 | self.timer.Start(self.delay) | |
213 | def Stop(self): | |
214 | self.timer.Stop() | |
215 | class PlayTimer(wxTimer): | |
216 | def __init__(self,func): | |
217 | wxTimer.__init__(self) | |
218 | self.func = func | |
219 | self.Start(1000) | |
220 | def Notify(self): | |
221 | apply(self.func, ()) | |
222 | ||
0bea2d47 HH |
223 | class HangmanDemoFrame(wxFrame): |
224 | def __init__(self, wf, parent, id, pos, size): | |
225 | wxFrame.__init__(self, parent, id, "Hangman demo", pos, size) | |
226 | self.demo = HangmanDemo(wf, self, -1, wxDefaultPosition, wxDefaultSize) | |
227 | def OnCloseWindow(self, event): | |
228 | self.demo.timer.Stop() | |
229 | self.Destroy() | |
230 | ||
eeec36a2 HH |
231 | class AboutBox(wxDialog): |
232 | def __init__(self, parent,wf): | |
233 | wxDialog.__init__(self, parent, -1, "About Hangman", wxDefaultPosition, wxSize(350,450)) | |
234 | self.wnd = HangmanDemo(wf, self, -1, wxPoint(1,1), wxSize(350,150)) | |
235 | self.static = wxStaticText(self, -1, __doc__, wxPoint(1,160), wxSize(350, 250)) | |
236 | self.button = wxButton(self, 2001, "OK", wxPoint(150,420), wxSize(50,-1)) | |
237 | EVT_BUTTON(self, 2001, self.OnOK) | |
238 | def OnOK(self, event): | |
239 | self.wnd.Stop() | |
240 | self.EndModal(wxID_OK) | |
0d236c34 HH |
241 | |
242 | class MyFrame(wxFrame): | |
243 | def __init__(self, wf): | |
244 | self.wf = wf | |
245 | wxFrame.__init__(self, NULL, -1, "hangman", wxDefaultPosition, wxSize(400,300)) | |
246 | self.wnd = HangmanWnd(self, -1) | |
247 | menu = wxMenu() | |
248 | menu.Append(1001, "New") | |
249 | menu.Append(1002, "End") | |
250 | menu.AppendSeparator() | |
251 | menu.Append(1003, "Reset") | |
eeec36a2 | 252 | menu.Append(1004, "Demo...") |
0d236c34 | 253 | menu.AppendSeparator() |
eeec36a2 | 254 | menu.Append(1005, "Exit") |
0d236c34 HH |
255 | menubar = wxMenuBar() |
256 | menubar.Append(menu, "Game") | |
257 | menu = wxMenu() | |
eeec36a2 | 258 | #menu.Append(1010, "Internal", "Use internal dictionary", TRUE) |
0d236c34 | 259 | menu.Append(1011, "ASCII File...") |
eeec36a2 HH |
260 | urls = [ 'wxPython home', 'http://208.240.253.245/wxPython/main.html', |
261 | 'slashdot.org', 'http://slashdot.org/', | |
262 | 'cnn.com', 'http://cnn.com', | |
8a252c00 HH |
263 | 'The New York Times', 'http://www.nytimes.com', |
264 | 'De Volkskrant', 'http://www.volkskrant.nl/frameless/25000006.html', | |
265 | 'Gnu GPL', 'http://www.fsf.org/copyleft/gpl.html', | |
266 | 'Bijbel: Genesis', 'http://www.coas.com/bijbel/gn1.htm'] | |
eeec36a2 HH |
267 | urlmenu = wxMenu() |
268 | for item in range(0,len(urls),2): | |
269 | urlmenu.Append(1020+item/2, urls[item], urls[item+1]) | |
0bea2d47 | 270 | urlmenu.Append(1080, 'Other...', 'Enter an URL') |
eeec36a2 HH |
271 | menu.AppendMenu(1012, 'URL', urlmenu, 'Use a webpage') |
272 | menu.Append(1013, 'Dump', 'Write contents to stdout') | |
0d236c34 | 273 | menubar.Append(menu, "Dictionary") |
eeec36a2 HH |
274 | self.urls = urls |
275 | self.urloffset = 1020 | |
276 | menu = wxMenu() | |
277 | menu.Append(1090, "About...") | |
278 | menubar.Append(menu, "Help") | |
0d236c34 HH |
279 | self.SetMenuBar(menubar) |
280 | self.CreateStatusBar(2) | |
281 | EVT_MENU(self, 1001, self.OnGameNew) | |
282 | EVT_MENU(self, 1002, self.OnGameEnd) | |
283 | EVT_MENU(self, 1003, self.OnGameReset) | |
eeec36a2 HH |
284 | EVT_MENU(self, 1004, self.OnGameDemo) |
285 | EVT_MENU(self, 1005, self.OnWindowClose) | |
0d236c34 | 286 | EVT_MENU(self, 1011, self.OnDictFile) |
eeec36a2 | 287 | EVT_MENU_RANGE(self, 1020, 1020+len(urls)/2, self.OnDictURL) |
0bea2d47 | 288 | EVT_MENU(self, 1080, self.OnDictURLSel) |
eeec36a2 HH |
289 | EVT_MENU(self, 1013, self.OnDictDump) |
290 | EVT_MENU(self, 1090, self.OnHelpAbout) | |
0d236c34 HH |
291 | EVT_CHAR(self.wnd, self.OnChar) |
292 | self.OnGameReset() | |
35c1ca4b | 293 | def OnGameNew(self, event): |
0d236c34 | 294 | word = self.wf.Get() |
35c1ca4b | 295 | self.in_progress = 1 |
0d236c34 HH |
296 | self.SetStatusText("",0) |
297 | self.wnd.StartGame(word) | |
35c1ca4b HH |
298 | def OnGameEnd(self, event): |
299 | self.UpdateAverages(0) | |
35c1ca4b | 300 | self.in_progress = 0 |
0d236c34 HH |
301 | self.SetStatusText("",0) |
302 | self.wnd.EndGame() | |
303 | def OnGameReset(self, event=None): | |
304 | self.played = 0 | |
305 | self.won = 0 | |
306 | self.history = [] | |
307 | self.average = 0.0 | |
308 | self.OnGameNew(None) | |
eeec36a2 | 309 | def OnGameDemo(self, event): |
0bea2d47 | 310 | frame = HangmanDemoFrame(self.wf, self, -1, wxDefaultPosition, self.GetSize()) |
eeec36a2 | 311 | frame.Show(TRUE) |
0d236c34 | 312 | def OnDictFile(self, event): |
eeec36a2 HH |
313 | fd = wxFileDialog(self) |
314 | if (self.wf.filename): | |
315 | fd.SetFilename(self.wf.filename) | |
316 | if fd.ShowModal() == wxID_OK: | |
317 | file = fd.GetPath() | |
318 | self.wf = WordFetcher(file) | |
319 | def OnDictURL(self, event): | |
320 | item = (event.GetId() - self.urloffset)*2 | |
321 | print "Trying to open %s at %s" % (self.urls[item], self.urls[item+1]) | |
322 | self.wf = URLWordFetcher(self.urls[item+1]) | |
0bea2d47 HH |
323 | def OnDictURLSel(self, event): |
324 | msg = wxTextEntryDialog(self, "Enter the URL of the dictionary document", "Enter URL") | |
325 | if msg.ShowModal() == wxID_OK: | |
326 | url = msg.GetValue() | |
327 | self.wf = URLWordFetcher(url) | |
eeec36a2 HH |
328 | def OnDictDump(self, event): |
329 | print self.wf.words | |
330 | def OnHelpAbout(self, event): | |
331 | about = AboutBox(self, self.wf) | |
332 | about.ShowModal() | |
333 | about.wnd.Stop() # that damn timer won't stop! | |
35c1ca4b HH |
334 | def UpdateAverages(self, has_won): |
335 | if has_won: | |
336 | self.won = self.won + 1 | |
337 | self.played = self.played+1 | |
0d236c34 | 338 | self.history.append(self.wnd.misses) # ugly |
35c1ca4b HH |
339 | total = 0.0 |
340 | for m in self.history: | |
341 | total = total + m | |
342 | self.average = float(total/len(self.history)) | |
343 | def OnChar(self, event): | |
344 | if not self.in_progress: | |
345 | self.OnGameNew(None) | |
346 | return | |
347 | key = event.KeyCode(); | |
348 | if key >= ord('A') and key <= ord('Z'): | |
349 | key = key + ord('a') - ord('A') | |
350 | key = chr(key) | |
351 | if key < 'a' or key > 'z': | |
0d236c34 | 352 | event.Skip() |
35c1ca4b | 353 | return |
0d236c34 | 354 | res = self.wnd.HandleKey(key) |
eeec36a2 HH |
355 | if res == 0: |
356 | self.SetStatusText(self.wnd.message) | |
357 | elif res == 1: | |
0d236c34 | 358 | self.UpdateAverages(0) |
35c1ca4b | 359 | self.SetStatusText("Too bad, you're dead!",0) |
35c1ca4b | 360 | self.in_progress = 0 |
0d236c34 HH |
361 | elif res == 2: |
362 | self.in_progress = 0 | |
363 | self.UpdateAverages(1) | |
35c1ca4b | 364 | self.SetStatusText("Congratulations!",0) |
0d236c34 HH |
365 | if self.played: |
366 | percent = (100.*self.won)/self.played | |
367 | else: | |
368 | percent = 0.0 | |
369 | self.SetStatusText("p %d, w %d (%g %%), av %g" % (self.played,self.won, percent, self.average),1) | |
370 | ||
35c1ca4b HH |
371 | def OnWindowClose(self, event): |
372 | self.Destroy() | |
373 | ||
374 | class MyApp(wxApp): | |
375 | def OnInit(self): | |
0bea2d47 HH |
376 | if wxPlatform == '__WXGTK__': |
377 | defaultfile = "/usr/share/games/hangman-words" | |
378 | elif wxPlatform == '__WXMSW__': | |
379 | defaultfile = "c:\\windows\\hardware.txt" | |
380 | else: | |
381 | defaultfile = "" | |
382 | wf = WordFetcher(defaultfile) | |
35c1ca4b HH |
383 | frame = MyFrame(wf) |
384 | self.SetTopWindow(frame) | |
385 | frame.Show(TRUE) | |
386 | return TRUE | |
387 | ||
388 | if __name__ == '__main__': | |
389 | app = MyApp(0) | |
390 | app.MainLoop() |