| 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 | |