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