wxWidgets/wxPython/samples/hangman/hangman.py

472 lines
15 KiB
Python
Raw Normal View History

"""Hangman.py, a simple wxPython game, inspired by the
old bsd game by Ken Arnold.
>From the original man page:
In hangman, the computer picks a word from the on-line
word list and you must try to guess it. The computer
keeps track of which letters have been guessed and how
many wrong guesses you have made on the screen in a
graphic fashion.
That says it all, doesn't it?
Have fun with it,
Harm van der Heijden (H.v.d.Heijden@phys.tue.nl)"""
import random,re
import wx
class WordFetcher:
builtin_words = ' albatros banana electrometer eggshell'
def __init__(self, filename, min_length = 5):
self.min_length = min_length
print "Trying to open file %s" % (filename,)
try:
f = open(filename, "r")
except:
print "Couldn't open dictionary file %s, using builtins" % (filename,)
self.words = self.builtin_words
self.filename = None
return
self.words = f.read()
self.filename = filename
print "Got %d bytes." % (len(self.words),)
def SetMinLength(min_length):
self.min_length = min_length
def Get(self):
reg = re.compile('\s+([a-zA-Z]+)\s+')
n = 50 # safety valve; maximum number of tries to find a suitable word
while n:
index = int(random.random()*len(self.words))
m = reg.search(self.words[index:])
if m and len(m.groups()[0]) >= self.min_length: break
n = n - 1
if n: return m.groups()[0].lower()
return "error"
def stdprint(x):
print x
class URLWordFetcher(WordFetcher):
def __init__(self, url):
self.OpenURL(url)
WordFetcher.__init__(self, "hangman_dict.txt")
def logprint(self,x):
print x
def RetrieveAsFile(self, host, path=''):
from httplib import HTTP
try:
h = HTTP(host)
except:
self.logprint("Failed to create HTTP connection to %s... is the network available?" % (host))
return None
h.putrequest('GET',path)
h.putheader('Accept','text/html')
h.putheader('Accept','text/plain')
h.endheaders()
errcode, errmsg, headers = h.getreply()
if errcode != 200:
self.logprint("HTTP error code %d: %s" % (errcode, errmsg))
return None
f = h.getfile()
return f
def OpenURL(self,url):
from htmllib import HTMLParser
import formatter
self.url = url
m = re.match('http://([^/]+)(/\S*)\s*', url)
if m:
host = m.groups()[0]
path = m.groups()[1]
else:
m = re.match('http://(\S+)\s*', url)
if not m:
# Invalid URL
self.logprint("Invalid or unsupported URL: %s" % (url))
return
host = m.groups()[0]
path = ''
f = self.RetrieveAsFile(host,path)
if not f:
self.logprint("Could not open %s" % (url))
return
self.logprint("Receiving data...")
data = f.read()
tmp = open('hangman_dict.txt','w')
fmt = formatter.AbstractFormatter(formatter.DumbWriter(tmp))
p = HTMLParser(fmt)
self.logprint("Parsing data...")
p.feed(data)
p.close()
tmp.close()
class HangmanWnd(wx.Window):
def __init__(self, parent, id, pos=wx.DefaultPosition, size=wx.DefaultSize):
wx.Window.__init__(self, parent, id, pos, size)
self.SetBackgroundColour(wx.NamedColour('white'))
if wx.Platform == '__WXGTK__':
self.font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL)
else:
self.font = wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)
self.SetFocus()
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_SIZE, self.OnSize)
def OnSize(self, event):
self.Refresh()
def StartGame(self, word):
self.word = word
self.guess = []
self.tries = 0
self.misses = 0
self.Draw()
def EndGame(self):
self.misses = 7;
self.guess = map(chr, range(ord('a'),ord('z')+1))
self.Draw()
def HandleKey(self, key):
self.message = ""
if self.guess.count(key):
self.message = 'Already guessed %s' % (key,)
return 0
self.guess.append(key)
self.guess.sort()
self.tries = self.tries+1
if not key in self.word:
self.misses = self.misses+1
if self.misses == 7:
self.EndGame()
return 1
has_won = 1
for letter in self.word:
if not self.guess.count(letter):
has_won = 0
break
if has_won:
self.Draw()
return 2
self.Draw()
return 0
def Draw(self, dc = None):
if not dc:
dc = wx.ClientDC(self)
dc.SetFont(self.font)
dc.Clear()
(x,y) = self.GetSizeTuple()
x1 = x-200; y1 = 20
for letter in self.word:
if self.guess.count(letter):
dc.DrawText(letter, x1, y1)
else:
dc.DrawText('.', x1, y1)
x1 = x1 + 10
x1 = x-200
dc.DrawText("tries %d misses %d" % (self.tries,self.misses),x1,50)
guesses = ""
for letter in self.guess:
guesses = guesses + letter
dc.DrawText("guessed:", x1, 70)
dc.DrawText(guesses[:13], x1+80, 70)
dc.DrawText(guesses[13:], x1+80, 90)
dc.SetUserScale(x/1000.0, y/1000.0)
self.DrawVictim(dc)
def DrawVictim(self, dc):
dc.SetPen(wx.Pen(wx.NamedColour('black'), 20))
dc.DrawLines([(10, 980), (10,900), (700,900), (700,940), (720,940),
(720,980), (900,980)])
dc.DrawLines([(100,900), (100, 100), (300,100)])
dc.DrawLine(100,200,200,100)
if ( self.misses == 0 ): return
dc.SetPen(wx.Pen(wx.NamedColour('blue'), 10))
dc.DrawLine(300,100,300,200)
if ( self.misses == 1 ): return
dc.DrawEllipse(250,200,100,100)
if ( self.misses == 2 ): return
dc.DrawLine(300,300,300,600)
if ( self.misses == 3) : return
dc.DrawLine(300,300,250,550)
if ( self.misses == 4) : return
dc.DrawLine(300,300,350,550)
if ( self.misses == 5) : return
dc.DrawLine(300,600,350,850)
if ( self.misses == 6) : return
dc.DrawLine(300,600,250,850)
def OnPaint(self, event):
dc = wx.PaintDC(self)
self.Draw(dc)
class HangmanDemo(HangmanWnd):
def __init__(self, wf, parent, id, pos, size):
HangmanWnd.__init__(self, parent, id, pos, size)
self.StartGame("dummy")
self.start_new = 1
self.wf = wf
self.delay = 500
self.timer = self.PlayTimer(self.MakeMove)
def MakeMove(self):
self.timer.Stop()
if self.start_new:
self.StartGame(self.wf.Get())
self.start_new = 0
self.left = list('aaaabcdeeeeefghiiiiijklmnnnoooopqrssssttttuuuuvwxyz')
else:
key = self.left[int(random.random()*len(self.left))]
while self.left.count(key): self.left.remove(key)
self.start_new = self.HandleKey(key)
self.timer.Start(self.delay)
def Stop(self):
self.timer.Stop()
class PlayTimer(wx.Timer):
def __init__(self,func):
wx.Timer.__init__(self)
self.func = func
self.Start(1000)
def Notify(self):
apply(self.func, ())
class HangmanDemoFrame(wx.Frame):
def __init__(self, wf, parent, id, pos, size):
wx.Frame.__init__(self, parent, id, "Hangman demo", pos, size)
self.demo = HangmanDemo(wf, self, -1, wx.DefaultPosition, wx.DefaultSize)
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
def OnCloseWindow(self, event):
self.demo.timer.Stop()
self.Destroy()
class AboutBox(wx.Dialog):
def __init__(self, parent,wf):
wx.Dialog.__init__(self, parent, -1, "About Hangman", wx.DefaultPosition, (350,450))
self.wnd = HangmanDemo(wf, self, -1, (1,1), (350,150))
self.static = wx.StaticText(self, -1, __doc__, (1,160), (350, 250))
self.button = wx.Button(self, 2001, "OK", (150,420), (50,-1))
self.Fit()
self.button.Bind(wx.EVT_BUTTON, self.OnOK)
def OnOK(self, event):
self.wnd.Stop()
self.EndModal(wx.ID_OK)
class MyFrame(wx.Frame):
def __init__(self, parent, wf):
self.wf = wf
wx.Frame.__init__(self, parent, -1, "hangman", wx.DefaultPosition, (400,300))
self.wnd = HangmanWnd(self, -1)
menu = wx.Menu()
menu.Append(1001, "New")
menu.Append(1002, "End")
menu.AppendSeparator()
menu.Append(1003, "Reset")
menu.Append(1004, "Demo...")
menu.AppendSeparator()
menu.Append(1005, "Exit")
menubar = wx.MenuBar()
menubar.Append(menu, "Game")
menu = wx.Menu()
#menu.Append(1010, "Internal", "Use internal dictionary", True)
menu.Append(1011, "ASCII File...")
urls = [ 'wxPython home', 'http://wxPython.org/',
'slashdot.org', 'http://slashdot.org/',
'cnn.com', 'http://cnn.com',
'The New York Times', 'http://www.nytimes.com',
'De Volkskrant', 'http://www.volkskrant.nl/frameless/25000006.html',
'Gnu GPL', 'http://www.fsf.org/copyleft/gpl.html',
'Bijbel: Genesis', 'http://www.coas.com/bijbel/gn1.htm']
urlmenu = wx.Menu()
for item in range(0,len(urls),2):
urlmenu.Append(1020+item/2, urls[item], urls[item+1])
urlmenu.Append(1080, 'Other...', 'Enter an URL')
menu.AppendMenu(1012, 'URL', urlmenu, 'Use a webpage')
menu.Append(1013, 'Dump', 'Write contents to stdout')
menubar.Append(menu, "Dictionary")
self.urls = urls
self.urloffset = 1020
menu = wx.Menu()
menu.Append(1090, "About...")
menubar.Append(menu, "Help")
self.SetMenuBar(menubar)
self.CreateStatusBar(2)
self.Bind(wx.EVT_MENU, self.OnGameNew, id=1001)
self.Bind(wx.EVT_MENU, self.OnGameEnd, id=1002)
self.Bind(wx.EVT_MENU, self.OnGameReset, id=1003)
self.Bind(wx.EVT_MENU, self.OnGameDemo, id=1004)
self.Bind(wx.EVT_MENU, self.OnWindowClose, id=1005)
self.Bind(wx.EVT_MENU, self.OnDictFile, id=1011)
self.Bind(wx.EVT_MENU, self.OnDictURL, id=1020, id2=1020+len(urls)/2)
self.Bind(wx.EVT_MENU, self.OnDictURLSel, id=1080)
self.Bind(wx.EVT_MENU, self.OnDictDump, id=1013)
self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=1090)
self.wnd.Bind(wx.EVT_CHAR, self.OnChar)
self.OnGameReset()
def OnGameNew(self, event):
word = self.wf.Get()
self.in_progress = 1
self.SetStatusText("",0)
self.wnd.StartGame(word)
def OnGameEnd(self, event):
self.UpdateAverages(0)
self.in_progress = 0
self.SetStatusText("",0)
self.wnd.EndGame()
def OnGameReset(self, event=None):
self.played = 0
self.won = 0
self.history = []
self.average = 0.0
self.OnGameNew(None)
def OnGameDemo(self, event):
frame = HangmanDemoFrame(self.wf, self, -1, wx.DefaultPosition, self.GetSize())
frame.Show(True)
def OnDictFile(self, event):
fd = wx.FileDialog(self)
if (self.wf.filename):
fd.SetFilename(self.wf.filename)
if fd.ShowModal() == wx.ID_OK:
file = fd.GetPath()
self.wf = WordFetcher(file)
def OnDictURL(self, event):
item = (event.GetId() - self.urloffset)*2
print "Trying to open %s at %s" % (self.urls[item], self.urls[item+1])
self.wf = URLWordFetcher(self.urls[item+1])
def OnDictURLSel(self, event):
msg = wx.TextEntryDialog(self, "Enter the URL of the dictionary document", "Enter URL")
if msg.ShowModal() == wx.ID_OK:
url = msg.GetValue()
self.wf = URLWordFetcher(url)
def OnDictDump(self, event):
print self.wf.words
def OnHelpAbout(self, event):
about = AboutBox(self, self.wf)
about.ShowModal()
about.wnd.Stop() # that damn timer won't stop!
def UpdateAverages(self, has_won):
if has_won:
self.won = self.won + 1
self.played = self.played+1
self.history.append(self.wnd.misses) # ugly
total = 0.0
for m in self.history:
total = total + m
self.average = float(total/len(self.history))
def OnChar(self, event):
if not self.in_progress:
#print "new"
self.OnGameNew(None)
return
key = event.GetKeyCode();
#print key
if key >= ord('A') and key <= ord('Z'):
key = key + ord('a') - ord('A')
key = chr(key)
if key < 'a' or key > 'z':
event.Skip()
return
res = self.wnd.HandleKey(key)
if res == 0:
self.SetStatusText(self.wnd.message)
elif res == 1:
self.UpdateAverages(0)
self.SetStatusText("Too bad, you're dead!",0)
self.in_progress = 0
elif res == 2:
self.in_progress = 0
self.UpdateAverages(1)
self.SetStatusText("Congratulations!",0)
if self.played:
percent = (100.*self.won)/self.played
else:
percent = 0.0
self.SetStatusText("p %d, w %d (%g %%), av %g" % (self.played,self.won, percent, self.average),1)
def OnWindowClose(self, event):
self.Destroy()
class MyApp(wx.App):
def OnInit(self):
if wx.Platform == '__WXGTK__':
defaultfile = "/usr/share/games/hangman-words"
elif wx.Platform == '__WXMSW__':
defaultfile = "c:\\windows\\hardware.txt"
else:
defaultfile = ""
wf = WordFetcher(defaultfile)
frame = MyFrame(None, wf)
self.SetTopWindow(frame)
frame.Show(True)
return True
if __name__ == '__main__':
app = MyApp(0)
app.MainLoop()
#----------------------------------------------------------------------
overview = __doc__
def runTest(frame, nb, log):
if wx.Platform == '__WXGTK__' or wx.Platform == '__WXMOTIF__':
defaultfile = "/usr/share/games/hangman-words"
elif wx.Platform == '__WXMSW__':
defaultfile = "c:\\windows\\hardware.txt"
else:
defaultfile = ""
wf = WordFetcher(defaultfile)
win = MyFrame(frame, wf)
frame.otherWin = win
win.Show(True)
#----------------------------------------------------------------------