A few days ago I saw a nice video on YouTube with prof Thorsten Altenkirch about recursion and the Tower of Hanoi puzzle. Today I tried to reproduce the code from the video and came to this:
A = "A"
B = "B"
C = "C"
def move_one(src, dst):
print("Move piece from {} to {}.".format(src,dst))
def move_multiple(n, src, dst, helper):
if n == 1:
move_one(src, dst)
else:
move_multiple(n-1, src, helper, dst)
move_one(src, dst)
move_multiple(n-1, helper, dst, src)
def main():
move_multiple(4, A, B, C)
if __name__ == "__main__":
main()
This works fine (although I missed the nice if n == 0
then pass
) but it still is a little disappointing that we do not have a robot to actually move discs and instead use a print
statement to indicate the action.
As a compromise I thought it would be cool to change the print
statements to some kind of ASCII animation. I started with a print_world
function and print_disc
, erase_disc
functions that uses ANSI escape codes to create and change the ASCII Hanoi tower world (Works on Linux, mileage on Windows may vary).
After a few hours (with trial an error) my code changed to this:
from time import sleep
import sys
A = "A"
B = "B"
C = "C"
N = 6
UP = "\u001b[A"
DOWN = "\u001b[B"
RIGHT = "\u001b[C"
LEFT = "\u001b[D"
LEAD = " "
SPC = " "
# initial state
state = {
A : list(range(N,0,-1)),
B : [],
C : [],
}
def change_state(src,dst):
global state
state[dst].append(state[src].pop())
def print_world():
"""Print empty Hanoi world, the poles and labels"""
print("\n\n")
print((LEAD + SPC.join([SPC * N + "|" + SPC * N] * 3) + "\n") * N)
print(LEAD + SPC * N + (SPC * (2 * N + 1)).join([A,B,C]))
def print_disc(n, up, right):
"""Print disc of certain size at certain screen coordinates"""
tkn = str(n)
print ( UP * up
+ RIGHT * right
+ SPC * (N-n) + tkn * n + RIGHT + tkn * n + SPC * (N-n)
+ LEFT * (2 * N + 1)
+ LEFT * right
+ DOWN * up,
end = "")
def erase_disc(up, right):
"""Erase disc at certain screen coordinates"""
print ( UP * up
+ RIGHT * right
+ SPC * N + RIGHT + SPC * N
+ LEFT * (2 * N + 1)
+ LEFT * right
+ DOWN * up,
end = "")
def find_screen_pos(pole, height):
up = 3 + height
height = len(LEAD) + (2 * N + 2) * {A:0, B:1, C:2}[pole]
return up, height
def print_state():
global state
for pole, discs in state.items():
for height, disc in enumerate(discs):
up, right = find_screen_pos(pole, height)
print_disc(disc, up, right)
sys.stdout.flush()
def find_screen_path(pole_1, height_1, pole_2, height_2):
cur_up, cur_right = find_screen_pos(pole_1, height_1)
end_up, end_right = find_screen_pos(pole_2, height_2)
direction = 0
if end_right > cur_right : direction = 1
if end_right < cur_right : direction = -1
path = [(cur_up, cur_right)]
while cur_up < N + 4:
cur_up += 1
path.append((cur_up, cur_right))
while cur_right != end_right:
cur_right += direction
path.append((cur_up, cur_right))
while cur_up != end_up:
cur_up -= 1
path.append((cur_up, cur_right))
return path
def animate_move(src, dst):
height_src = len(state[src])-1
height_dst = len(state[dst])
n = state[src][-1]
path = find_screen_path(src, height_src, dst, height_dst)
for old, new in zip(path[:-1], path[1:]):
erase_disc(*old)
print_disc(n, *new)
sys.stdout.flush()
sleep(0.01)
def move_one(src, dst):
animate_move(src, dst)
change_state(src, dst)
sleep(0.2)
def move_multiple(n, src, dst, helper):
if n == 1:
move_one(src, dst)
else:
move_multiple(n-1, src, helper, dst)
move_one(src, dst)
move_multiple(n-1, helper, dst, src)
def main():
print_world()
print_state()
sleep(2.0)
while(True):
move_multiple(N,A,B,C)
sleep(2.0)
move_multiple(N,B,C,A)
sleep(2.0)
move_multiple(N,C,A,B)
sleep(2.0)
if __name__ == "__main__":
main()
This relatively short piece of code (126 lines) works and at this moment I understand it, but do you, and will I in six month time? What should I change to the code to make it more readable and understandable (I know I've sinned against PEP8 sometimes).