Illustrate Tower of Hanoi with ASCII

Question:

I am familiar with the recursive function of the Hanoi Tower.

Now I need to visualize the movements, representing the discs with asterisks (I guess disc number = number of asterisks makes sense).

Does anyone have a hint or example for how to draw the disc movements step by step using just

        *                           
       ***                        ***
      *****                      *****            *
     --------------------       -----------------------     ...    

or similar?

This is the sample code my professor provided and I do understand how the recursion works. But after one lecture in Python I must say that I’m somewhat overwhelmed with the visualization task.

def hanoi(n, p1, p2, p3):
    if n==1:
        print("move from %d to %d" %(p1, p3))
    else:
        hanoi(n-1, p1, p3, p2)
        print("move from %d to %d " %(p1, p3))
        hanoi(n-1, p2, p1, p3)

    return

if __name__=='__main__':
    j=int(input('Input the number of disk to be moved:n'))
    print('Number of disk to be move is %d n'%j)
    hanoi(j, 1, 2, 3)

Help would be much appreciated!

Asked By: Phi Lipp

||

Answers:

As Phi Lipp mentioned, you need an idea of state.

State could be represented in many ways, but the end goal is an X by Y grid of characters that you print to the screen.

For example, if we wanted to print

  *
 ***
*****

we could store this in code as a 3×5 array.

[
  [' ', ' ', '*', ' ', ' '],
  [' ', '*', '*', '*', ' '],
  ['*', '*', '*', '*', '*'],
]

Then you can loop over this and print it.

for row in state:
    for character in row:
        print(character, end='')

An ugly shortcut would be

print('n'.join(''.join(y) for y in x))

Now if you think of this like a movie, every frame of your animation is this X by Y array. Every frame is also one of your print statements in the code. The difficulty lies in transforming an action e.g. "Move from 1 to 3", into a state representation.

If we used an X by Y array as the state, it would be difficult to move from space1 to space2 because an array of characters doesn’t tell you anything about how many rings and how large the ring is in space1. This is a sign that we need a better state.

This is pretty open ended, but one solution is if we apply object oriented programming, we can define Ring and Tower objects, where a tower is a collection of rings. An action therefore moves rings between towers, and the state of Rings and Tower objects is used to create the X by Y array.

I’ll be lazy and represent a ring as an integer and towers are an array of 3 deques. The initial state can look something like

from collections import deque
towers = [deque([1,2,3]), deque(), deque()]

Now you need a function to move from tower U to tower V:

def move_ring(from, to):
    top_ring = from.popleft()
    to.append(top_right)

Finally, you need to go from your towers/rings state to a printable X by Y array. It would be easier to render each tower at a time, and each ring within:

def render_ring(ring):
    result = '*' * ring  # the character * repeated ring times.
    return result.center(user_input) # add the spaces required

def render_tower(tower):
    result = []
    for ring in tower:
        result.append(render_ring(ring))
    return result

Finally, we’ll want to combine the towers together into one printable array. You can use zip for this.

def render_final(towers):
    tower_results = []
    for tower in towers:
        tower_results.append(render_tower(tower))
    result = []
    for all_rows in zip(result):
        result.append(''.join(all_rows))
    return result

Now you should be able to print out the result and see the frame of your animation.

This should help you get on the right track. Please treat the code above as pseudocode as it is not tested. I also do not recommend using an integer and a list of deques as your state, as that does not contribute to code cleanliness.

One more thing, if you print out the result, it won’t be pretty since the text will be printing and scrolling down. If you use the built in curses library however, you can get a beautiful output.

Answered By: wonton