[Tutor] hanoi.py - bug found and corrected. (was: request to run...)

Gregor Lingl glingl at aon.at
Mon Feb 16 17:18:24 EST 2004



Nick Lunt schrieb:

>Hi Gregor,
>
>...
>
>I put the speed down to 1 and quit the program while a disc was moving
>and got the following error in the console:
>
>$ ./hanoi.py 
>Exception in Tkinter callback
>Traceback (most recent call last):
>  File "/usr/lib/python2.3/lib-tk/Tkinter.py", line 1345, in __call__
>    return self.func(*args)
>...
>
>    self.cv.move(self.item,dx,dy)
>  File "/usr/lib/python2.3/lib-tk/Tkinter.py", line 2162, in move
>    self.tk.call((self._w, 'move') + args)
>TclError: invalid command name ".1078258988"
>$
>
>Cheers
>Nick.
>
>  
>
Hi Nick!

Thanks for your and credos very useful hints. They revealed a bug of
the program which also occured under Windows as I recognized only
now. So once more I didn't test a program thoroughly enough. *grrrrr*
But I was able to correct it easily. (It was a somewhat silly error)
The corrected version of hanoi.py is attached  to this posting.
***Please abandon the first version and take this one***

 From the other feedbacks from Mac and Linux useres I expect, that this
now runs without errors on all operating systems (Thanks also to you!).

Having the choice of being ashamed for having made this error or being lucky
to have got your help I choose the latter ;-)

Thanks again,
Gregor

>
>On Mon, 16 Feb 2004 18:52:29 +0100 Gregor Lingl <glingl at aon.at> wrote:
>
>  
>
>>Hi!
>>
>>At the moment I unfortunately don't have a computer running
>>Linux at hand, so ...
>>
>>... is there anybody out there using a machine running
>>Python 2.3 with Tkinter under Linux, who is willing
>>to run the attached program and give me feedback concerning
>>two questions:
>>
>>(1) Does the program run smoothly
>>
>>(2) Does it terminate without error-messages (TclError)
>>when the window is closed while the animation is running
>>(i. e. while a disc is moving).
>>This can be seen e.g. when you run it from within IDLE or
>>from the commandline of some console window ...
>>
>>Many thanks in advance
>>Gregor
>>
>>P.S. A second posting concerning this Hanoi program,
>>concerning some remarks on the problem mentioned above
>>will follow.
>>
>>
>>    
>>
>
>_______________________________________________
>Tutor maillist  -  Tutor at python.org
>http://mail.python.org/mailman/listinfo/tutor
>
>
>  
>
-------------- next part --------------
## Animated Tower-Of-Hanoi game with Tkinter GUI
## author: Gregor Lingl, Vienna, Austria
## email: glingl at aon.at
## date: 16. 2. 2004
#########################################################
## bug (found by Nick Hunt and credo - Tutor list) in
## HanoiEngine.run() and HanoiEngine.step() corrected:
## lines 124 - 132
######## 16. 2. 2004  - 23:07 ###########################

from Tkinter import * 

class Disc:
    """Movable Rectangle on a Tkinter Canvas"""
    def __init__(self,cv,pos,length,height,colour):
        """creates disc on given Canvas cv at given pos-ition"""
        x0, y0 = pos
        x1, x2 = x0-length/2.0, x0+length/2.0
        y1, y2 = y0-height, y0
        self.cv = cv
        self.item = cv.create_rectangle(x1,y1,x2,y2,
                                        fill = "#%02x%02x%02x" % colour)       
    def move_to(self, x, y, speed):
        """moves bottom center of disc to position (x,y).
           speed is intended to assume values from 1 to 10"""
        x1,y1,x2,y2 = self.cv.coords(self.item)
        x0, y0 = (x1 + x2)/2, y2
        dx, dy = x-x0, y-y0
        d = (dx**2+dy**2)**0.5
        steps = int(d/(10*speed-5)) + 1
        dx, dy = dx/steps, dy/steps
        for i in range(steps):
            self.cv.move(self.item,dx,dy)
            self.cv.update()
            self.cv.after(20)

class Tower(list):
    """Hanoi tower designed as a subclass of built-in type list"""
    def __init__(self, x, y, h):
        """ creates an empty tower.
            (x,y) is floor-level position of tower,
            h is height of the discs. """
        self.x = x
        self.y = y
        self.h = h
    def top(self):
        return self.x, self.y - len(self)*self.h
    

class HanoiEngine:
    """Plays the Hanoi-game on a given canvas."""
    def __init__(self, canvas, nrOfDiscs, speed, moveCntDisplay=None):
        """Sets Canvas to play on as well as default values for
        number of discs and animation-speed.
        moveCntDisplay is a function with 1 parameter, which communicates
        the count of the actual move to the GUI containing the
        Hanoi-engine-canvas."""
        self.cv = canvas
        self.nrOfDiscs = nrOfDiscs
        self.speed = speed
        self.moveDisplay = moveCntDisplay
        self.running = False
        self.moveCnt = 0
        self.discs = []
        self.towerA = Tower( 80, 190, 15)
        self.towerB = Tower(220, 190, 15)
        self.towerC = Tower(360, 190, 15)
        self.reset()
        
    def hanoi(self, n, src, dest, temp):
        """The classical recursive Towers-Of-Hanoi algorithm."""
        if n > 0:
            for x in self.hanoi(n-1, src, temp, dest): yield None
            yield self.move(src, dest)
            for x in self.hanoi(n-1, temp, dest, src): yield None

    def move(self, src_tower, dest_tower):
        """moves uppermost disc of source tower to top of destination
        tower."""
        self.moveCnt += 1
        self.moveDisplay(self.moveCnt)
        disc = src_tower.pop()
        x1, y1 = src_tower.top()
        x2, y2 = dest_tower.top()
        disc.move_to(x1,20, self.speed)
        disc.move_to(x2,20, self.speed)
        disc.move_to(x2,y2, self.speed)
        dest_tower.append(disc)

    def reset(self):
        """Setup of (a new) game."""
        self.moveCnt = 0
        self.moveDisplay(self.moveCnt)
        while self.towerA: self.towerA.pop()
        while self.towerB: self.towerB.pop()
        while self.towerC: self.towerC.pop()
        for s in self.discs:
            self.cv.delete(s.item)
        ## Fancy colouring of discs: red ===> blue
        if self.nrOfDiscs > 1:
            colour_diff = 255 // (self.nrOfDiscs-1)
        else:
            colour_diff = 0
        for i in range(self.nrOfDiscs):  # setup towerA        
            length_diff = 100 // self.nrOfDiscs
            length = 120 - i * length_diff        
            s = Disc( self.cv, self.towerA.top(), length, 13,
                         (255-i*colour_diff, 0, i*colour_diff))
            self.discs.append(s)
            self.towerA.append(s)
        self.HG = self.hanoi(self.nrOfDiscs,
                             self.towerA, self.towerC, self.towerB)
        
    def run(self):
        """runs game ;-)
        returns True if game is over, else False"""
        self.running = True
        try:
            while self.running:
                result = self.step()
            return result  # True iff done
        except StopIteration:  # game over
            return True

    def step(self):
        """performs one single step of the game,
        returns True if finished, else False"""
        try:
            self.HG.next()
            return 2**self.nrOfDiscs-1 == self.moveCnt
        except TclError: 
            return False
        
    def stop(self):
        """ ;-) """
        self.running = False


class Hanoi:
    """GUI for animated towers-of-Hanoi-game with upto 10 discs:"""

    def displayMove(self, move):
        """method to be passed to the Hanoi-engine as a callback
        to report move-count"""
        self.moveCntLbl.configure(text = "move:\n%d" % move)
    
    def adjust_nr_of_discs(self, e):
        """callback function for nr-of-discs-scale-widget"""
        self.hEngine.nrOfDiscs = self.discs.get()
        self.reset()

    def adjust_speed(self, e):
        """callback function for speeds-scale-widget"""
        self.hEngine.speed = self.tempo.get()

    def setState(self, STATE):
        """most simple representation of a finite state machine"""
        self.state = STATE
        try:
            if STATE == "START":
                self.discs.configure(state=NORMAL)
                self.discs.configure(fg="black")
                self.discsLbl.configure(fg="black")
                self.resetBtn.configure(state=DISABLED)
                self.startBtn.configure(text="start", state=NORMAL)
                self.stepBtn.configure(state=NORMAL)
            elif STATE == "RUNNING":
                self.discs.configure(state=DISABLED)
                self.discs.configure(fg="gray70")
                self.discsLbl.configure(fg="gray70")
                self.resetBtn.configure(state=DISABLED)
                self.startBtn.configure(text="pause", state=NORMAL)
                self.stepBtn.configure(state=DISABLED)
            elif STATE == "PAUSE":
                self.discs.configure(state=NORMAL)
                self.discs.configure(fg="black")
                self.discsLbl.configure(fg="black")
                self.resetBtn.configure(state=NORMAL)
                self.startBtn.configure(text="resume", state=NORMAL)
                self.stepBtn.configure(state=NORMAL)
            elif STATE == "DONE":
                self.discs.configure(state=NORMAL)
                self.discs.configure(fg="black")
                self.discsLbl.configure(fg="black")
                self.resetBtn.configure(state=NORMAL)
                self.startBtn.configure(text="start", state=DISABLED)
                self.stepBtn.configure(state=DISABLED)
            elif STATE == "TIMEOUT":
                self.discs.configure(state=DISABLED)
                self.discs.configure(fg="gray70")
                self.discsLbl.configure(fg="gray70")
                self.resetBtn.configure(state=DISABLED)
                self.startBtn.configure(state=DISABLED)
                self.stepBtn.configure(state=DISABLED)
        except TclError:
            pass
           
    def reset(self):
        """restores START state for a new game"""
        self.hEngine.reset()
        self.setState("START")
        
    def start(self):
        """callback function for start button, which also serves as
        pause button. Makes hEngine running until done or interrupted"""
        if self.state in ["START", "PAUSE"]:
            self.setState("RUNNING")            
            if self.hEngine.run():
                self.setState("DONE")
            else:
                self.setState("PAUSE")
        elif self.state == "RUNNING":
            self.setState("TIMEOUT")
            self.hEngine.stop()

    def step(self):
        """callback function for step button.
        makes hEngine perform a single step"""
        self.setState("TIMEOUT")
        if self.hEngine.step():
            self.setState("DONE")
        else:
            self.setState("PAUSE")
                
    def __init__(self, nrOfDiscs, speed):
        """builds GUI, constructs Hanoi-engine and puts it into START state
        then launches mainloop()"""
        root = Tk()                            
        root.title("TOWERS OF HANOI")
        #root.protocol("WM_DELETE_WINDOW",root.quit) #counter productive here!?
        cv = Canvas(root,width=440,height=210, bg="gray90") 
        cv.pack()
        
        fnt = ("Arial", 12, "bold")

        attrFrame = Frame(root) #contains scales to adjust game's attributes
        self.discsLbl = Label(attrFrame, width=7, height=2, font=fnt,
                              text="discs:\n")
        self.discs = Scale(attrFrame, from_=1, to_=10, orient=HORIZONTAL,
                           font=fnt, length=75, showvalue=1, repeatinterval=10,
                           command=self.adjust_nr_of_discs)
        self.discs.set(nrOfDiscs)
        self.tempoLbl = Label(attrFrame, width=8,  height=2, font=fnt,
                              text = "   speed:\n")
        self.tempo = Scale(attrFrame, from_=1, to_=10, orient=HORIZONTAL,
                           font=fnt, length=100, showvalue=1,repeatinterval=10,
                           command = self.adjust_speed)
        self.tempo.set(speed)
        self.moveCntLbl= Label(attrFrame, width=5, height=2, font=fnt,
                               padx=20, text=" move:\n0", anchor=CENTER)                        
        for widget in ( self.discsLbl, self.discs, self.tempoLbl, self.tempo,
                                                             self.moveCntLbl ):
            widget.pack(side=LEFT)
        attrFrame.pack(side=TOP)

            
        ctrlFrame = Frame(root) # contains Buttons to control the game 
        self.resetBtn = Button(ctrlFrame, width=11, text="reset", font=fnt,
                               state = DISABLED, padx=15, command = self.reset)
        self.stepBtn  = Button(ctrlFrame, width=11, text="step", font=fnt,
                               state = NORMAL,  padx=15, command = self.step)
        self.startBtn = Button(ctrlFrame, width=11, text="start", font=fnt,
                               state = NORMAL,  padx=15, command = self.start)
        for widget in self.resetBtn, self.stepBtn, self.startBtn:
            widget.pack(side=LEFT)
        ctrlFrame.pack(side=TOP)

        # setup of the scene
        peg1 = cv.create_rectangle(  75,  40,  85, 190, fill='darkgreen')
        peg2 = cv.create_rectangle( 215,  40, 225, 190, fill='darkgreen')
        peg3 = cv.create_rectangle( 355,  40, 365, 190, fill='darkgreen')
        floor = cv.create_rectangle( 10, 191, 430, 200, fill='black')

        self.hEngine = HanoiEngine(cv, nrOfDiscs, speed, self.displayMove)
        self.state = "START"

        root.mainloop()

if __name__  == "__main__":
    Hanoi(4,5)


More information about the Tutor mailing list