]> git.saurik.com Git - wxWidgets.git/blob - wxPython/tests/hangman.py
exercise wxTextCtrl::GetRange
[wxWidgets.git] / wxPython / tests / 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 class WordFetcher:
21 def __init__(self, filename, min_length = 5):
22 self.min_length = min_length
23 print "Trying to open file %s" % (filename,)
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
29 self.filename = None
30 return
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
36 def Get(self):
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()
103
104 class HangmanWnd(wxWindow):
105 def __init__(self, parent, id, pos=wxDefaultPosition, size=wxDefaultSize):
106 wxWindow.__init__(self, parent, id, pos, size)
107 self.SetBackgroundColour(wxNamedColour('white'))
108 if wxPlatform == '__WXGTK__':
109 self.font = wxFont(12, wxMODERN, wxNORMAL, wxNORMAL)
110 else:
111 self.font = wxFont(10, wxMODERN, wxNORMAL, wxNORMAL)
112 self.SetFocus()
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):
124 self.message = ""
125 if self.guess.count(key):
126 self.message = 'Already guessed %s' % (key,)
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
146 def Draw(self, dc = None):
147 if not dc:
148 dc = wxClientDC(self)
149 dc.SetFont(self.font)
150 dc.Clear()
151 (x,y) = self.GetSizeTuple()
152 x1 = x-200; y1 = 20
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
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)
165 dc.DrawText(guesses[:13], x1+80, 70)
166 dc.DrawText(guesses[13:], x1+80, 90)
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):
191 dc = wxPaintDC(self)
192 self.Draw(dc)
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
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
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)
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")
252 menu.Append(1004, "Demo...")
253 menu.AppendSeparator()
254 menu.Append(1005, "Exit")
255 menubar = wxMenuBar()
256 menubar.Append(menu, "Game")
257 menu = wxMenu()
258 #menu.Append(1010, "Internal", "Use internal dictionary", TRUE)
259 menu.Append(1011, "ASCII File...")
260 urls = [ 'wxPython home', 'http://208.240.253.245/wxPython/main.html',
261 'slashdot.org', 'http://slashdot.org/',
262 'cnn.com', 'http://cnn.com',
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']
267 urlmenu = wxMenu()
268 for item in range(0,len(urls),2):
269 urlmenu.Append(1020+item/2, urls[item], urls[item+1])
270 urlmenu.Append(1080, 'Other...', 'Enter an URL')
271 menu.AppendMenu(1012, 'URL', urlmenu, 'Use a webpage')
272 menu.Append(1013, 'Dump', 'Write contents to stdout')
273 menubar.Append(menu, "Dictionary")
274 self.urls = urls
275 self.urloffset = 1020
276 menu = wxMenu()
277 menu.Append(1090, "About...")
278 menubar.Append(menu, "Help")
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)
284 EVT_MENU(self, 1004, self.OnGameDemo)
285 EVT_MENU(self, 1005, self.OnWindowClose)
286 EVT_MENU(self, 1011, self.OnDictFile)
287 EVT_MENU_RANGE(self, 1020, 1020+len(urls)/2, self.OnDictURL)
288 EVT_MENU(self, 1080, self.OnDictURLSel)
289 EVT_MENU(self, 1013, self.OnDictDump)
290 EVT_MENU(self, 1090, self.OnHelpAbout)
291 EVT_CHAR(self.wnd, self.OnChar)
292 self.OnGameReset()
293 def OnGameNew(self, event):
294 word = self.wf.Get()
295 self.in_progress = 1
296 self.SetStatusText("",0)
297 self.wnd.StartGame(word)
298 def OnGameEnd(self, event):
299 self.UpdateAverages(0)
300 self.in_progress = 0
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)
309 def OnGameDemo(self, event):
310 frame = HangmanDemoFrame(self.wf, self, -1, wxDefaultPosition, self.GetSize())
311 frame.Show(TRUE)
312 def OnDictFile(self, event):
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])
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)
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!
334 def UpdateAverages(self, has_won):
335 if has_won:
336 self.won = self.won + 1
337 self.played = self.played+1
338 self.history.append(self.wnd.misses) # ugly
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':
352 event.Skip()
353 return
354 res = self.wnd.HandleKey(key)
355 if res == 0:
356 self.SetStatusText(self.wnd.message)
357 elif res == 1:
358 self.UpdateAverages(0)
359 self.SetStatusText("Too bad, you're dead!",0)
360 self.in_progress = 0
361 elif res == 2:
362 self.in_progress = 0
363 self.UpdateAverages(1)
364 self.SetStatusText("Congratulations!",0)
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
371 def OnWindowClose(self, event):
372 self.Destroy()
373
374 class MyApp(wxApp):
375 def OnInit(self):
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)
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()