221 lines
7.6 KiB
Python
221 lines
7.6 KiB
Python
# -*- 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('<Return>', self._ent_return)
|
|
self._ent_disks.bind('<FocusOut>', 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() |