17db96d56Sopenharmony_ci#!/usr/bin/env python3
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ci"""
47db96d56Sopenharmony_ciAnimated Towers of Hanoi using Tk with optional bitmap file in background.
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_ciUsage: hanoi.py [n [bitmapfile]]
77db96d56Sopenharmony_ci
87db96d56Sopenharmony_cin is the number of pieces to animate; default is 4, maximum 15.
97db96d56Sopenharmony_ci
107db96d56Sopenharmony_ciThe bitmap file can be any X11 bitmap file (look in /usr/include/X11/bitmaps for
117db96d56Sopenharmony_cisamples); it is displayed as the background of the animation.  Default is no
127db96d56Sopenharmony_cibitmap.
137db96d56Sopenharmony_ci"""
147db96d56Sopenharmony_ci
157db96d56Sopenharmony_cifrom tkinter import Tk, Canvas
167db96d56Sopenharmony_ci
177db96d56Sopenharmony_ci# Basic Towers-of-Hanoi algorithm: move n pieces from a to b, using c
187db96d56Sopenharmony_ci# as temporary.  For each move, call report()
197db96d56Sopenharmony_cidef hanoi(n, a, b, c, report):
207db96d56Sopenharmony_ci    if n <= 0: return
217db96d56Sopenharmony_ci    hanoi(n-1, a, c, b, report)
227db96d56Sopenharmony_ci    report(n, a, b)
237db96d56Sopenharmony_ci    hanoi(n-1, c, b, a, report)
247db96d56Sopenharmony_ci
257db96d56Sopenharmony_ci
267db96d56Sopenharmony_ci# The graphical interface
277db96d56Sopenharmony_ciclass Tkhanoi:
287db96d56Sopenharmony_ci
297db96d56Sopenharmony_ci    # Create our objects
307db96d56Sopenharmony_ci    def __init__(self, n, bitmap=None):
317db96d56Sopenharmony_ci        self.n = n
327db96d56Sopenharmony_ci        self.tk = tk = Tk()
337db96d56Sopenharmony_ci        self.canvas = c = Canvas(tk)
347db96d56Sopenharmony_ci        c.pack()
357db96d56Sopenharmony_ci        width, height = tk.getint(c['width']), tk.getint(c['height'])
367db96d56Sopenharmony_ci
377db96d56Sopenharmony_ci        # Add background bitmap
387db96d56Sopenharmony_ci        if bitmap:
397db96d56Sopenharmony_ci            self.bitmap = c.create_bitmap(width//2, height//2,
407db96d56Sopenharmony_ci                                          bitmap=bitmap,
417db96d56Sopenharmony_ci                                          foreground='blue')
427db96d56Sopenharmony_ci
437db96d56Sopenharmony_ci        # Generate pegs
447db96d56Sopenharmony_ci        pegwidth = 10
457db96d56Sopenharmony_ci        pegheight = height//2
467db96d56Sopenharmony_ci        pegdist = width//3
477db96d56Sopenharmony_ci        x1, y1 = (pegdist-pegwidth)//2, height*1//3
487db96d56Sopenharmony_ci        x2, y2 = x1+pegwidth, y1+pegheight
497db96d56Sopenharmony_ci        self.pegs = []
507db96d56Sopenharmony_ci        p = c.create_rectangle(x1, y1, x2, y2, fill='black')
517db96d56Sopenharmony_ci        self.pegs.append(p)
527db96d56Sopenharmony_ci        x1, x2 = x1+pegdist, x2+pegdist
537db96d56Sopenharmony_ci        p = c.create_rectangle(x1, y1, x2, y2, fill='black')
547db96d56Sopenharmony_ci        self.pegs.append(p)
557db96d56Sopenharmony_ci        x1, x2 = x1+pegdist, x2+pegdist
567db96d56Sopenharmony_ci        p = c.create_rectangle(x1, y1, x2, y2, fill='black')
577db96d56Sopenharmony_ci        self.pegs.append(p)
587db96d56Sopenharmony_ci        self.tk.update()
597db96d56Sopenharmony_ci
607db96d56Sopenharmony_ci        # Generate pieces
617db96d56Sopenharmony_ci        pieceheight = pegheight//16
627db96d56Sopenharmony_ci        maxpiecewidth = pegdist*2//3
637db96d56Sopenharmony_ci        minpiecewidth = 2*pegwidth
647db96d56Sopenharmony_ci        self.pegstate = [[], [], []]
657db96d56Sopenharmony_ci        self.pieces = {}
667db96d56Sopenharmony_ci        x1, y1 = (pegdist-maxpiecewidth)//2, y2-pieceheight-2
677db96d56Sopenharmony_ci        x2, y2 = x1+maxpiecewidth, y1+pieceheight
687db96d56Sopenharmony_ci        dx = (maxpiecewidth-minpiecewidth) // (2*max(1, n-1))
697db96d56Sopenharmony_ci        for i in range(n, 0, -1):
707db96d56Sopenharmony_ci            p = c.create_rectangle(x1, y1, x2, y2, fill='red')
717db96d56Sopenharmony_ci            self.pieces[i] = p
727db96d56Sopenharmony_ci            self.pegstate[0].append(i)
737db96d56Sopenharmony_ci            x1, x2 = x1 + dx, x2-dx
747db96d56Sopenharmony_ci            y1, y2 = y1 - pieceheight-2, y2-pieceheight-2
757db96d56Sopenharmony_ci            self.tk.update()
767db96d56Sopenharmony_ci            self.tk.after(25)
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ci    # Run -- never returns
797db96d56Sopenharmony_ci    def run(self):
807db96d56Sopenharmony_ci        while True:
817db96d56Sopenharmony_ci            hanoi(self.n, 0, 1, 2, self.report)
827db96d56Sopenharmony_ci            hanoi(self.n, 1, 2, 0, self.report)
837db96d56Sopenharmony_ci            hanoi(self.n, 2, 0, 1, self.report)
847db96d56Sopenharmony_ci            hanoi(self.n, 0, 2, 1, self.report)
857db96d56Sopenharmony_ci            hanoi(self.n, 2, 1, 0, self.report)
867db96d56Sopenharmony_ci            hanoi(self.n, 1, 0, 2, self.report)
877db96d56Sopenharmony_ci
887db96d56Sopenharmony_ci    # Reporting callback for the actual hanoi function
897db96d56Sopenharmony_ci    def report(self, i, a, b):
907db96d56Sopenharmony_ci        if self.pegstate[a][-1] != i: raise RuntimeError # Assertion
917db96d56Sopenharmony_ci        del self.pegstate[a][-1]
927db96d56Sopenharmony_ci        p = self.pieces[i]
937db96d56Sopenharmony_ci        c = self.canvas
947db96d56Sopenharmony_ci
957db96d56Sopenharmony_ci        # Lift the piece above peg a
967db96d56Sopenharmony_ci        ax1, ay1, ax2, ay2 = c.bbox(self.pegs[a])
977db96d56Sopenharmony_ci        while True:
987db96d56Sopenharmony_ci            x1, y1, x2, y2 = c.bbox(p)
997db96d56Sopenharmony_ci            if y2 < ay1: break
1007db96d56Sopenharmony_ci            c.move(p, 0, -1)
1017db96d56Sopenharmony_ci            self.tk.update()
1027db96d56Sopenharmony_ci
1037db96d56Sopenharmony_ci        # Move it towards peg b
1047db96d56Sopenharmony_ci        bx1, by1, bx2, by2 = c.bbox(self.pegs[b])
1057db96d56Sopenharmony_ci        newcenter = (bx1+bx2)//2
1067db96d56Sopenharmony_ci        while True:
1077db96d56Sopenharmony_ci            x1, y1, x2, y2 = c.bbox(p)
1087db96d56Sopenharmony_ci            center = (x1+x2)//2
1097db96d56Sopenharmony_ci            if center == newcenter: break
1107db96d56Sopenharmony_ci            if center > newcenter: c.move(p, -1, 0)
1117db96d56Sopenharmony_ci            else: c.move(p, 1, 0)
1127db96d56Sopenharmony_ci            self.tk.update()
1137db96d56Sopenharmony_ci
1147db96d56Sopenharmony_ci        # Move it down on top of the previous piece
1157db96d56Sopenharmony_ci        pieceheight = y2-y1
1167db96d56Sopenharmony_ci        newbottom = by2 - pieceheight*len(self.pegstate[b]) - 2
1177db96d56Sopenharmony_ci        while True:
1187db96d56Sopenharmony_ci            x1, y1, x2, y2 = c.bbox(p)
1197db96d56Sopenharmony_ci            if y2 >= newbottom: break
1207db96d56Sopenharmony_ci            c.move(p, 0, 1)
1217db96d56Sopenharmony_ci            self.tk.update()
1227db96d56Sopenharmony_ci
1237db96d56Sopenharmony_ci        # Update peg state
1247db96d56Sopenharmony_ci        self.pegstate[b].append(i)
1257db96d56Sopenharmony_ci
1267db96d56Sopenharmony_ci
1277db96d56Sopenharmony_cidef main():
1287db96d56Sopenharmony_ci    import sys
1297db96d56Sopenharmony_ci
1307db96d56Sopenharmony_ci    # First argument is number of pegs, default 4
1317db96d56Sopenharmony_ci    if sys.argv[1:]:
1327db96d56Sopenharmony_ci        n = int(sys.argv[1])
1337db96d56Sopenharmony_ci    else:
1347db96d56Sopenharmony_ci        n = 4
1357db96d56Sopenharmony_ci
1367db96d56Sopenharmony_ci    # Second argument is bitmap file, default none
1377db96d56Sopenharmony_ci    if sys.argv[2:]:
1387db96d56Sopenharmony_ci        bitmap = sys.argv[2]
1397db96d56Sopenharmony_ci        # Reverse meaning of leading '@' compared to Tk
1407db96d56Sopenharmony_ci        if bitmap[0] == '@': bitmap = bitmap[1:]
1417db96d56Sopenharmony_ci        else: bitmap = '@' + bitmap
1427db96d56Sopenharmony_ci    else:
1437db96d56Sopenharmony_ci        bitmap = None
1447db96d56Sopenharmony_ci
1457db96d56Sopenharmony_ci    # Create the graphical objects...
1467db96d56Sopenharmony_ci    h = Tkhanoi(n, bitmap)
1477db96d56Sopenharmony_ci
1487db96d56Sopenharmony_ci    # ...and run!
1497db96d56Sopenharmony_ci    h.run()
1507db96d56Sopenharmony_ci
1517db96d56Sopenharmony_ci
1527db96d56Sopenharmony_ci# Call main when run as script
1537db96d56Sopenharmony_ciif __name__ == '__main__':
1547db96d56Sopenharmony_ci    main()
155