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


#----------------------------------------------------------------------




