blob: 8db895c244581048fb18df21362e0740c894497f [file] [log] [blame]
Georg Brandl856898b2010-12-30 22:11:50 +00001#!/usr/bin/env python3
Guido van Rossum50df3811994-06-28 13:52:31 +00002
Georg Brandl856898b2010-12-30 22:11:50 +00003"""
4Animated Towers of Hanoi using Tk with optional bitmap file in background.
Guido van Rossum50df3811994-06-28 13:52:31 +00005
Georg Brandl856898b2010-12-30 22:11:50 +00006Usage: hanoi.py [n [bitmapfile]]
7
8n is the number of pieces to animate; default is 4, maximum 15.
9
10The bitmap file can be any X11 bitmap file (look in /usr/include/X11/bitmaps for
11samples); it is displayed as the background of the animation. Default is no
12bitmap.
13"""
14
15from tkinter import Tk, Canvas
Guido van Rossum50df3811994-06-28 13:52:31 +000016
17# Basic Towers-of-Hanoi algorithm: move n pieces from a to b, using c
18# as temporary. For each move, call report()
19def hanoi(n, a, b, c, report):
Tim Peters182b5ac2004-07-18 06:16:08 +000020 if n <= 0: return
21 hanoi(n-1, a, c, b, report)
22 report(n, a, b)
23 hanoi(n-1, c, b, a, report)
Guido van Rossum50df3811994-06-28 13:52:31 +000024
25
26# The graphical interface
27class Tkhanoi:
28
Tim Peters182b5ac2004-07-18 06:16:08 +000029 # Create our objects
Julin Sa4aeb332019-10-23 08:53:48 +053030 def __init__(self, n, bitmap=None):
Tim Peters182b5ac2004-07-18 06:16:08 +000031 self.n = n
32 self.tk = tk = Tk()
33 self.canvas = c = Canvas(tk)
34 c.pack()
35 width, height = tk.getint(c['width']), tk.getint(c['height'])
Guido van Rossum50df3811994-06-28 13:52:31 +000036
Tim Peters182b5ac2004-07-18 06:16:08 +000037 # Add background bitmap
38 if bitmap:
Benjamin Petersond7b03282008-09-13 15:58:53 +000039 self.bitmap = c.create_bitmap(width//2, height//2,
Tim Peters182b5ac2004-07-18 06:16:08 +000040 bitmap=bitmap,
41 foreground='blue')
Guido van Rossum50df3811994-06-28 13:52:31 +000042
Tim Peters182b5ac2004-07-18 06:16:08 +000043 # Generate pegs
44 pegwidth = 10
Benjamin Petersond7b03282008-09-13 15:58:53 +000045 pegheight = height//2
46 pegdist = width//3
47 x1, y1 = (pegdist-pegwidth)//2, height*1//3
Tim Peters182b5ac2004-07-18 06:16:08 +000048 x2, y2 = x1+pegwidth, y1+pegheight
49 self.pegs = []
50 p = c.create_rectangle(x1, y1, x2, y2, fill='black')
51 self.pegs.append(p)
52 x1, x2 = x1+pegdist, x2+pegdist
53 p = c.create_rectangle(x1, y1, x2, y2, fill='black')
54 self.pegs.append(p)
55 x1, x2 = x1+pegdist, x2+pegdist
56 p = c.create_rectangle(x1, y1, x2, y2, fill='black')
57 self.pegs.append(p)
58 self.tk.update()
Guido van Rossum50df3811994-06-28 13:52:31 +000059
Tim Peters182b5ac2004-07-18 06:16:08 +000060 # Generate pieces
Benjamin Petersond7b03282008-09-13 15:58:53 +000061 pieceheight = pegheight//16
62 maxpiecewidth = pegdist*2//3
Tim Peters182b5ac2004-07-18 06:16:08 +000063 minpiecewidth = 2*pegwidth
64 self.pegstate = [[], [], []]
65 self.pieces = {}
Benjamin Petersond7b03282008-09-13 15:58:53 +000066 x1, y1 = (pegdist-maxpiecewidth)//2, y2-pieceheight-2
Tim Peters182b5ac2004-07-18 06:16:08 +000067 x2, y2 = x1+maxpiecewidth, y1+pieceheight
Benjamin Petersond7b03282008-09-13 15:58:53 +000068 dx = (maxpiecewidth-minpiecewidth) // (2*max(1, n-1))
Tim Peters182b5ac2004-07-18 06:16:08 +000069 for i in range(n, 0, -1):
70 p = c.create_rectangle(x1, y1, x2, y2, fill='red')
71 self.pieces[i] = p
72 self.pegstate[0].append(i)
73 x1, x2 = x1 + dx, x2-dx
74 y1, y2 = y1 - pieceheight-2, y2-pieceheight-2
75 self.tk.update()
76 self.tk.after(25)
Guido van Rossum50df3811994-06-28 13:52:31 +000077
Tim Peters182b5ac2004-07-18 06:16:08 +000078 # Run -- never returns
79 def run(self):
Julin Sa4aeb332019-10-23 08:53:48 +053080 while True:
Tim Peters182b5ac2004-07-18 06:16:08 +000081 hanoi(self.n, 0, 1, 2, self.report)
82 hanoi(self.n, 1, 2, 0, self.report)
83 hanoi(self.n, 2, 0, 1, self.report)
84 hanoi(self.n, 0, 2, 1, self.report)
85 hanoi(self.n, 2, 1, 0, self.report)
86 hanoi(self.n, 1, 0, 2, self.report)
Guido van Rossum50df3811994-06-28 13:52:31 +000087
Tim Peters182b5ac2004-07-18 06:16:08 +000088 # Reporting callback for the actual hanoi function
89 def report(self, i, a, b):
90 if self.pegstate[a][-1] != i: raise RuntimeError # Assertion
91 del self.pegstate[a][-1]
92 p = self.pieces[i]
93 c = self.canvas
Guido van Rossum50df3811994-06-28 13:52:31 +000094
Tim Peters182b5ac2004-07-18 06:16:08 +000095 # Lift the piece above peg a
96 ax1, ay1, ax2, ay2 = c.bbox(self.pegs[a])
Julin Sa4aeb332019-10-23 08:53:48 +053097 while True:
Tim Peters182b5ac2004-07-18 06:16:08 +000098 x1, y1, x2, y2 = c.bbox(p)
99 if y2 < ay1: break
100 c.move(p, 0, -1)
101 self.tk.update()
Guido van Rossum50df3811994-06-28 13:52:31 +0000102
Tim Peters182b5ac2004-07-18 06:16:08 +0000103 # Move it towards peg b
104 bx1, by1, bx2, by2 = c.bbox(self.pegs[b])
Benjamin Petersond7b03282008-09-13 15:58:53 +0000105 newcenter = (bx1+bx2)//2
Julin Sa4aeb332019-10-23 08:53:48 +0530106 while True:
Tim Peters182b5ac2004-07-18 06:16:08 +0000107 x1, y1, x2, y2 = c.bbox(p)
Benjamin Petersond7b03282008-09-13 15:58:53 +0000108 center = (x1+x2)//2
Tim Peters182b5ac2004-07-18 06:16:08 +0000109 if center == newcenter: break
110 if center > newcenter: c.move(p, -1, 0)
111 else: c.move(p, 1, 0)
112 self.tk.update()
Guido van Rossum50df3811994-06-28 13:52:31 +0000113
Tim Peters182b5ac2004-07-18 06:16:08 +0000114 # Move it down on top of the previous piece
115 pieceheight = y2-y1
116 newbottom = by2 - pieceheight*len(self.pegstate[b]) - 2
Julin Sa4aeb332019-10-23 08:53:48 +0530117 while True:
Tim Peters182b5ac2004-07-18 06:16:08 +0000118 x1, y1, x2, y2 = c.bbox(p)
119 if y2 >= newbottom: break
120 c.move(p, 0, 1)
121 self.tk.update()
Guido van Rossum50df3811994-06-28 13:52:31 +0000122
Tim Peters182b5ac2004-07-18 06:16:08 +0000123 # Update peg state
124 self.pegstate[b].append(i)
Guido van Rossum50df3811994-06-28 13:52:31 +0000125
126
Guido van Rossum50df3811994-06-28 13:52:31 +0000127def main():
Georg Brandl856023a2010-10-25 17:50:20 +0000128 import sys
Guido van Rossum50df3811994-06-28 13:52:31 +0000129
Tim Peters182b5ac2004-07-18 06:16:08 +0000130 # First argument is number of pegs, default 4
131 if sys.argv[1:]:
Georg Brandl856023a2010-10-25 17:50:20 +0000132 n = int(sys.argv[1])
Tim Peters182b5ac2004-07-18 06:16:08 +0000133 else:
134 n = 4
Guido van Rossum50df3811994-06-28 13:52:31 +0000135
Tim Peters182b5ac2004-07-18 06:16:08 +0000136 # Second argument is bitmap file, default none
137 if sys.argv[2:]:
138 bitmap = sys.argv[2]
139 # Reverse meaning of leading '@' compared to Tk
140 if bitmap[0] == '@': bitmap = bitmap[1:]
141 else: bitmap = '@' + bitmap
142 else:
143 bitmap = None
Guido van Rossum50df3811994-06-28 13:52:31 +0000144
Tim Peters182b5ac2004-07-18 06:16:08 +0000145 # Create the graphical objects...
146 h = Tkhanoi(n, bitmap)
Guido van Rossum50df3811994-06-28 13:52:31 +0000147
Tim Peters182b5ac2004-07-18 06:16:08 +0000148 # ...and run!
149 h.run()
Guido van Rossum50df3811994-06-28 13:52:31 +0000150
151
152# Call main when run as script
153if __name__ == '__main__':
Tim Peters182b5ac2004-07-18 06:16:08 +0000154 main()