When using tkinter in python, there seems to be some sutble conflict when using tk.update() and tk.mainloop() at the same time.
215 lines
5.8 KiB
Python
215 lines
5.8 KiB
Python
# -*- 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_pegs = []
|
|
gbl_pegstate = [[], [], []]
|
|
gbl_disks = []
|
|
gbl_tkroot = tk.Tk()
|
|
gbl_canvas = None
|
|
gbl_ready = True
|
|
|
|
|
|
def setup_gui():
|
|
global gbl_canvas
|
|
|
|
def get_ent(event):
|
|
global gbl_num_disks
|
|
|
|
try:
|
|
new_num = int(ent_disks.get())
|
|
gbl_num_disks = new_num
|
|
event.widget.tk_focusNext().focus()
|
|
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('<Return>', get_ent)
|
|
ent_disks.bind('<FocusOut>', get_ent)
|
|
ent_disks.pack(side=tk.LEFT, padx=5)
|
|
btn_start = tk.Button(tk_row, text="Start", width=6)
|
|
btn_start.bind('<ButtonRelease-1>', run_hanoi)
|
|
btn_start.pack(side=tk.LEFT, padx=5)
|
|
btn_reset = tk.Button(tk_row, text="Reset", width=6)
|
|
btn_reset.bind('<ButtonRelease-1>', 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)
|
|
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()
|
|
|
|
def change_speed(*args):
|
|
global gbl_speed
|
|
speed_opt = {'slow': 0.002, 'normal': 0.001, 'fastest': 0}
|
|
gbl_speed = speed_opt.get(tk_var.get())
|
|
# print(gbl_speed)
|
|
|
|
tk_var.trace('w', change_speed)
|
|
|
|
|
|
def run_hanoi(event):
|
|
global gbl_ready
|
|
|
|
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
|
|
|
|
|
|
def reset_hanoi(event):
|
|
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, -1)
|
|
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
|
|
while True:
|
|
x1, y1, x2, y2 = gbl_canvas.bbox(disk)
|
|
center = (x1 + x2) // 2
|
|
if center == new_center:
|
|
break
|
|
if center > new_center:
|
|
gbl_canvas.move(disk, -1, 0)
|
|
else:
|
|
gbl_canvas.move(disk, 1, 0)
|
|
gbl_tkroot.update_idletasks()
|
|
gbl_tkroot.update()
|
|
time.sleep(0.7*gbl_speed) # faster
|
|
|
|
# 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, 1)
|
|
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() |