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