# -*- coding: utf-8 -*- """ Created on Sat Apr 25 21:18:50 2020 @author: cpan """ import tkinter as tk import time gbl_num_disks = 3 GAP = 2 CANVAS_W = 500 CANVAS_H = 400 gbl_speed = 0.001 # in seconds gbl_step = 2 gbl_pegs = [] gbl_pegstate = [[], [], []] gbl_disks = [] gbl_tkroot = tk.Tk() gbl_canvas = None gbl_ready = True def setup_gui(): global gbl_canvas def ent_return(event): event.widget.tk_focusNext().focus() def get_ent_focus(event): global gbl_num_disks try: new_num = int(ent_disks.get()) gbl_num_disks = new_num draw_init_setting() except Exception as err: ent_disks.delete(0, tk.END) ent_disks.insert(0, str(gbl_num_disks)) print(repr(err)) # print(gbl_num_disks) # options row tk_row = tk.Frame(gbl_tkroot) tk.Label(tk_row, text="No. of Disks: ").pack(side=tk.LEFT) ent_disks = tk.Entry(tk_row, width=5) ent_disks.insert(0, str(gbl_num_disks)) ent_disks.bind('', ent_return) ent_disks.bind('', get_ent_focus) ent_disks.pack(side=tk.LEFT, padx=5) btn_start = tk.Button(tk_row, text="Start", width=6, command=run_hanoi) # btn_start.bind('', run_hanoi) btn_start.pack(side=tk.LEFT, padx=5) btn_reset = tk.Button(tk_row, text="Reset", width=6, command=reset_hanoi) # btn_reset.bind('', reset_hanoi) btn_reset.pack(side=tk.LEFT, padx=5) tk.Label(tk_row, text="Speed: ").pack(side=tk.LEFT) tk_var = tk.StringVar(gbl_tkroot) tk_var.set('normal') tk_opt_menu = tk.OptionMenu(tk_row, tk_var, *{'slow', 'normal', 'fastest'}) tk_opt_menu.config(width=6, takefocus=True) tk_opt_menu.pack(side=tk.LEFT) tk_row.pack(side=tk.TOP, pady=5) # animation canvas gbl_canvas = tk.Canvas(gbl_tkroot, width=CANVAS_W, height=CANVAS_H) gbl_canvas.pack() btn_start.focus_set() def change_speed(*args): global gbl_speed, gbl_step speed_opt = {'slow': 0.001, 'normal': 0.001, 'fastest': 0} step_opt = {'slow': 1, 'normal': 2, 'fastest': 1} gbl_speed = speed_opt.get(tk_var.get()) gbl_step = step_opt.get(tk_var.get()) # print(gbl_speed) tk_var.trace('w', change_speed) def run_hanoi(): global gbl_ready # t0 = time.time() if gbl_ready: gbl_ready = False try: hanoi(gbl_num_disks-1, 0, 1, 2) except Exception as err: print(f'forced reset: {repr(err)}') else: pass # t1 = time.time() # print(t1-t0) def reset_hanoi(): draw_init_setting() def draw_init_setting(): global gbl_pegs, gbl_disks, gbl_pegstate, gbl_ready gbl_pegs = [] gbl_pegstate = [[], [], []] gbl_disks = [] gbl_canvas.delete('all') # draw the three pegs a, b, c peg_w = 10 peg_h = CANVAS_H // 2 peg_dist = CANVAS_W // 3 x1, y1 = (peg_dist - peg_w) // 2, CANVAS_H // 3 x2, y2 = x1 + peg_w, y1 + peg_h for i in range(3): gbl_pegs.append(gbl_canvas.create_rectangle(x1, y1, x2, y2, fill = 'black')) x1 += peg_dist x2 += peg_dist gbl_tkroot.update_idletasks() gbl_tkroot.update() # create disks on the first peg disk_h = peg_h // 16 disk_maxw = peg_dist * 2 // 3 disk_minw = 2 * peg_w x1, y1 = (peg_dist - disk_maxw) // 2, y2 - disk_h - GAP x2, y2 = x1 + disk_maxw, y1 + disk_h dx = (disk_maxw - disk_minw) // (2 * max(1, gbl_num_disks-1)) dx = min(dx, 2 * peg_w) for i in range(gbl_num_disks): gbl_disks.insert(0, gbl_canvas.create_rectangle(x1, y1, x2, y2, fill = 'red')) gbl_pegstate[0].append(gbl_num_disks - i - 1) x1 += dx x2 -= dx y1 -= disk_h + GAP y2 -= disk_h + GAP gbl_tkroot.update_idletasks() gbl_tkroot.update() gbl_ready = True def move_disk(i, a, b): global gbl_pegstate, gbl_canvas disk_num = gbl_pegstate[a].pop() if disk_num != i: raise RuntimeError(f'Trying to move disk piece #{i} from peg {a}\ to peg {b} that does not exist.') disk = gbl_disks[i] # Lift the disk above peg a px1, py1, px2, py2 = gbl_canvas.bbox(gbl_pegs[a]) while True: x1, y1, x2, y2 = gbl_canvas.bbox(disk) if y2 < py1: break gbl_canvas.move(disk, 0, -gbl_step) gbl_tkroot.update_idletasks() gbl_tkroot.update() time.sleep(gbl_speed) # Move to peg b px1, py1, px2, py2 = gbl_canvas.bbox(gbl_pegs[b]) new_center = (px1 + px2) // 2 x1, y1, x2, y2 = gbl_canvas.bbox(disk) center = (x1 + x2) // 2 while center < new_center: gbl_canvas.move(disk, gbl_step, 0) gbl_tkroot.update_idletasks() gbl_tkroot.update() time.sleep(0.7*gbl_speed) # faster x1, y1, x2, y2 = gbl_canvas.bbox(disk) center = (x1 + x2) // 2 while center > new_center: gbl_canvas.move(disk, -gbl_step, 0) gbl_tkroot.update_idletasks() gbl_tkroot.update() time.sleep(0.7*gbl_speed) # faster x1, y1, x2, y2 = gbl_canvas.bbox(disk) center = (x1 + x2) // 2 # Drop down disk_h = y2 - y1 new_bottom = py2 - disk_h * len(gbl_pegstate[b]) - GAP while True: x1, y1, x2, y2 = gbl_canvas.bbox(disk) if y2 >= new_bottom: break gbl_canvas.move(disk, 0, gbl_step) gbl_tkroot.update_idletasks() gbl_tkroot.update() time.sleep(gbl_speed) gbl_pegstate[b].append(i) def hanoi(n, a, b, c): ''' 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 hanoi(n-1, a, c, b) move_disk(n, a, c) # move nth piece from peg a to peg c hanoi(n-1, b, a, c) def main(): setup_gui() draw_init_setting() # input("Press Enter to continue") # try: # hanoi(gbl_num_disks-1, 0, 1, 2) # except Exception as err: # print("aborted: " + repr(err)) gbl_tkroot.mainloop() if __name__ == "__main__": main()