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