# -*- coding: utf-8 -*- """ Created on Sat Apr 25 21:18:50 2020 @author: cpan """ import tkinter as tk import time class Hanoi: def __init__(self, tkroot, num_disks=3, canvas_w=500, canvas_h=400): self.num_disks = num_disks self.canvas_w = canvas_w self.canvas_h = canvas_h self.peg_w = 10 self.peg_h = canvas_h // 2 self.peg_dist = canvas_w // 3 self.disk_h = self.peg_h // 16 self.disk_maxw = self.peg_dist * 2 // 3 self.disk_minw = 2 * self.peg_w self._gap = 2 self._speed = 0.001 # in seconds self._step = 2 self._ready = False # not ready to run self._tkroot = tkroot self._frame = tk.Frame(self._tkroot) self._lbl_disks = tk.Label(self._frame, text="No. of Disks: ") self._ent_disks = tk.Entry(self._frame, width=5) self._btn_start = tk.Button(self._frame, text="Start", width=6, command=self._cmd_start) self._btn_reset = tk.Button(self._frame, text="Reset", width=6, command=self._cmd_reset) self._lbl_speed = tk.Label(self._frame, text="Speed: ") self._var_speed = tk.StringVar(self._tkroot) self._opt_speed = tk.OptionMenu(self._frame, self._var_speed, *{'slow', 'normal', 'fastest'}) self._canvas = tk.Canvas(self._tkroot, width=canvas_w, height=canvas_h) self._init_tkwindow() self._setup_pegs() def _init_tkwindow(self): self._tkroot.title('Hanoi Tower') self._lbl_disks.pack(side=tk.LEFT) self._ent_disks.insert(0, str(self.num_disks)) self._ent_disks.bind('', self._ent_return) self._ent_disks.bind('', self._ent_focus) self._ent_disks.pack(side=tk.LEFT, padx=5) self._btn_start.pack(side=tk.LEFT, padx=5) self._btn_reset.pack(side=tk.LEFT, padx=5) self._lbl_speed.pack(side=tk.LEFT) self._var_speed.set('normal') self._opt_speed.config(width=6, takefocus=True) self._opt_speed.pack(side=tk.LEFT) self._frame.pack(side=tk.TOP, pady=5) self._canvas.pack() self._btn_start.focus_set() self._var_speed.trace('w', self._var_speed_changed) self._tkroot.lift() self._tkroot.focus_force() def _setup_pegs(self): self._pegs = [] self._disks = [] self._pegstate = [[], [], []] self._canvas.delete('all') # draw the three pegs a, b, c x1, y1 = (self.peg_dist - self.peg_w) // 2, self.canvas_h // 3 x2, y2 = x1 + self.peg_w, y1 + self.peg_h for i in range(3): self._pegs.append(self._canvas.create_rectangle(x1, y1, x2, y2, fill = 'black')) x1 += self.peg_dist x2 += self.peg_dist # create disks on the first peg x1, y1 = (self.peg_dist - self.disk_maxw) // 2, y2 - self.disk_h - self._gap x2, y2 = x1 + self.disk_maxw, y1 + self.disk_h dx = (self.disk_maxw - self.disk_minw) // (2 * max(1, self.num_disks-1)) dx = min(dx, 2 * self.peg_w) for i in range(self.num_disks): self._disks.insert(0, self._canvas.create_rectangle(x1, y1, x2, y2, fill = 'red')) self._pegstate[0].append(self.num_disks - i - 1) x1 += dx x2 -= dx y1 -= self.disk_h + self._gap y2 -= self.disk_h + self._gap self._tkroot.update_idletasks() self._tkroot.update() self._ready = True def _var_speed_changed(self, *args): self._opt_speed.focus_set() speed_opt = {'slow': 0.001, 'normal': 0.001, 'fastest': 0} step_opt = {'slow': 1, 'normal': 2, 'fastest': 1} self._speed = speed_opt.get(self._var_speed.get()) self._step = step_opt.get(self._var_speed.get()) def _cmd_start(self): focus_widget = self._tkroot.focus_get() if focus_widget != self._ent_disks: self._btn_reset.focus_set() self._btn_start.config(state='disabled') self._run_hanoi() else: self._btn_start.focus_set() def _cmd_reset(self): self._btn_start.focus_set() self._btn_start.config(state='normal') self._setup_pegs() def _run_hanoi(self): if self._ready: self._ready = False try: self.hanoi(self.num_disks - 1, 0, 1, 2) except Exception as err: print(f'forced reset: {repr(err)}') def _ent_return(self, event): self._ent_disks.tk_focusNext().focus() def _ent_focus(self, event): try: new_num = int(self._ent_disks.get()) if new_num < 1: new_num = 1 if new_num > 12: new_num =12 self.num_disks = new_num self._cmd_reset() except Exception as err: print(repr(err)) finally: self._ent_disks.delete(0, tk.END) self._ent_disks.insert(0, str(self.num_disks)) def _move_disk(self, i, a, b): # move disk i from peg a to peg b disk_num = self._pegstate[a].pop() if disk_num != i: # should not happen raise RuntimeError(f'Trying to move disk piece #{i} from peg {a}\ to peg {b} that does not exist.') disk = self._disks[i] # Lift the disk above peg a px1, py1, px2, py2 = self._canvas.bbox(self._pegs[a]) while True: x1, y1, x2, y2 = self._canvas.bbox(disk) if y2 < py1: break self._canvas.move(disk, 0, -self._step) self._tkroot.update_idletasks() self._tkroot.update() time.sleep(self._speed) # Move to peg b px1, py1, px2, py2 = self._canvas.bbox(self._pegs[b]) new_center = (px1 + px2) // 2 x1, y1, x2, y2 = self._canvas.bbox(disk) center = (x1 + x2) // 2 while center < new_center: self._canvas.move(disk, self._step, 0) self._tkroot.update_idletasks() self._tkroot.update() time.sleep(0.7*self._speed) # faster x1, y1, x2, y2 = self._canvas.bbox(disk) center = (x1 + x2) // 2 while center > new_center: self._canvas.move(disk, -self._step, 0) self._tkroot.update_idletasks() self._tkroot.update() time.sleep(0.7*self._speed) # faster x1, y1, x2, y2 = self._canvas.bbox(disk) center = (x1 + x2) // 2 # Drop down disk_h = self.disk_h new_bottom = py2 - disk_h * len(self._pegstate[b]) - self._gap while True: x1, y1, x2, y2 = self._canvas.bbox(disk) if y2 >= new_bottom: break self._canvas.move(disk, 0, self._step) self._tkroot.update_idletasks() self._tkroot.update() time.sleep(self._speed) self._pegstate[b].append(i) def hanoi(self, n :int, a :int, b :int, c :int): ''' Hanoi algorithm: move n pieces from a to c, using b as buffer. For each move, call animate() to show the move ''' if n < 0: return self.hanoi(n-1, a, c, b) self._move_disk(n, a, c) # move nth piece from peg a to peg c self.hanoi(n-1, b, a, c) def main(): tkroot = tk.Tk() Hanoi(tkroot) tkroot.mainloop() if __name__ == "__main__": main()