hanoi.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. #!/usr/bin/env python3
  2. """
  3. Animated Towers of Hanoi using Tk with optional bitmap file in background.
  4. Usage: hanoi.py [n [bitmapfile]]
  5. n is the number of pieces to animate; default is 4, maximum 15.
  6. The bitmap file can be any X11 bitmap file (look in /usr/include/X11/bitmaps for
  7. samples); it is displayed as the background of the animation. Default is no
  8. bitmap.
  9. """
  10. from tkinter import Tk, Canvas
  11. # Basic Towers-of-Hanoi algorithm: move n pieces from a to b, using c
  12. # as temporary. For each move, call report()
  13. def hanoi(n, a, b, c, report):
  14. if n <= 0: return
  15. hanoi(n-1, a, c, b, report)
  16. report(n, a, b)
  17. hanoi(n-1, c, b, a, report)
  18. # The graphical interface
  19. class Tkhanoi:
  20. # Create our objects
  21. def __init__(self, n, bitmap = None):
  22. self.n = n
  23. self.tk = tk = Tk()
  24. self.canvas = c = Canvas(tk)
  25. c.pack()
  26. width, height = tk.getint(c['width']), tk.getint(c['height'])
  27. # Add background bitmap
  28. if bitmap:
  29. self.bitmap = c.create_bitmap(width//2, height//2,
  30. bitmap=bitmap,
  31. foreground='blue')
  32. # Generate pegs
  33. pegwidth = 10
  34. pegheight = height//2
  35. pegdist = width//3
  36. x1, y1 = (pegdist-pegwidth)//2, height*1//3
  37. x2, y2 = x1+pegwidth, y1+pegheight
  38. self.pegs = []
  39. p = c.create_rectangle(x1, y1, x2, y2, fill='black')
  40. self.pegs.append(p)
  41. x1, x2 = x1+pegdist, x2+pegdist
  42. p = c.create_rectangle(x1, y1, x2, y2, fill='black')
  43. self.pegs.append(p)
  44. x1, x2 = x1+pegdist, x2+pegdist
  45. p = c.create_rectangle(x1, y1, x2, y2, fill='black')
  46. self.pegs.append(p)
  47. self.tk.update()
  48. # Generate pieces
  49. pieceheight = pegheight//16
  50. maxpiecewidth = pegdist*2//3
  51. minpiecewidth = 2*pegwidth
  52. self.pegstate = [[], [], []]
  53. self.pieces = {}
  54. x1, y1 = (pegdist-maxpiecewidth)//2, y2-pieceheight-2
  55. x2, y2 = x1+maxpiecewidth, y1+pieceheight
  56. dx = (maxpiecewidth-minpiecewidth) // (2*max(1, n-1))
  57. for i in range(n, 0, -1):
  58. p = c.create_rectangle(x1, y1, x2, y2, fill='red')
  59. self.pieces[i] = p
  60. self.pegstate[0].append(i)
  61. x1, x2 = x1 + dx, x2-dx
  62. y1, y2 = y1 - pieceheight-2, y2-pieceheight-2
  63. self.tk.update()
  64. self.tk.after(25)
  65. # Run -- never returns
  66. def run(self):
  67. while 1:
  68. hanoi(self.n, 0, 1, 2, self.report)
  69. hanoi(self.n, 1, 2, 0, self.report)
  70. hanoi(self.n, 2, 0, 1, self.report)
  71. hanoi(self.n, 0, 2, 1, self.report)
  72. hanoi(self.n, 2, 1, 0, self.report)
  73. hanoi(self.n, 1, 0, 2, self.report)
  74. # Reporting callback for the actual hanoi function
  75. def report(self, i, a, b):
  76. if self.pegstate[a][-1] != i: raise RuntimeError # Assertion
  77. del self.pegstate[a][-1]
  78. p = self.pieces[i]
  79. c = self.canvas
  80. # Lift the piece above peg a
  81. ax1, ay1, ax2, ay2 = c.bbox(self.pegs[a])
  82. while 1:
  83. x1, y1, x2, y2 = c.bbox(p)
  84. if y2 < ay1: break
  85. c.move(p, 0, -1)
  86. self.tk.update()
  87. # Move it towards peg b
  88. bx1, by1, bx2, by2 = c.bbox(self.pegs[b])
  89. newcenter = (bx1+bx2)//2
  90. while 1:
  91. x1, y1, x2, y2 = c.bbox(p)
  92. center = (x1+x2)//2
  93. if center == newcenter: break
  94. if center > newcenter: c.move(p, -1, 0)
  95. else: c.move(p, 1, 0)
  96. self.tk.update()
  97. # Move it down on top of the previous piece
  98. pieceheight = y2-y1
  99. newbottom = by2 - pieceheight*len(self.pegstate[b]) - 2
  100. while 1:
  101. x1, y1, x2, y2 = c.bbox(p)
  102. if y2 >= newbottom: break
  103. c.move(p, 0, 1)
  104. self.tk.update()
  105. # Update peg state
  106. self.pegstate[b].append(i)
  107. def main():
  108. import sys
  109. # First argument is number of pegs, default 4
  110. if sys.argv[1:]:
  111. n = int(sys.argv[1])
  112. else:
  113. n = 4
  114. # Second argument is bitmap file, default none
  115. if sys.argv[2:]:
  116. bitmap = sys.argv[2]
  117. # Reverse meaning of leading '@' compared to Tk
  118. if bitmap[0] == '@': bitmap = bitmap[1:]
  119. else: bitmap = '@' + bitmap
  120. else:
  121. bitmap = None
  122. # Create the graphical objects...
  123. h = Tkhanoi(n, bitmap)
  124. # ...and run!
  125. h.run()
  126. # Call main when run as script
  127. if __name__ == '__main__':
  128. main()