Tower of Hanoi

Tags: short, game, puzzle

The Tower of Hanoi is a stack-moving puzzle game that features three poles on which you can stack various-sized disks. The object of the game is to move one tower of disks to another pole. However, only one disk can be moved at a time, and larger disks cannot be placed on top of smaller ones. Figuring out a certain pattern will help you solve this puzzle. Can you discover it? (Hint: Try setting the TOTAL_DISKS variable to 3 or 4 to solve an easier version first.)

tower_of_hanoi.py
  1"""The Tower of Hanoi, by Al Sweigart al@inventwithpython.com
  2A stack-moving puzzle game.
  3This code is available at https://nostarch.com/big-book-small-python-programming
  4Tags: short, game, puzzle"""
  5
  6import copy
  7import sys
  8
  9TOTAL_DISKS = 5  # More disks means a more difficult puzzle.
 10
 11# Start with all disks on tower A:
 12COMPLETE_TOWER = list(range(TOTAL_DISKS, 0, -1))
 13
 14
 15def main():
 16    print("""The Tower of Hanoi, by Al Sweigart al@inventwithpython.com
 17
 18Move the tower of disks, one disk at a time, to another tower. Larger
 19disks cannot rest on top of a smaller disk.
 20
 21More info at https://en.wikipedia.org/wiki/Tower_of_Hanoi
 22"""
 23    )
 24
 25    # Set up the towers. The end of the list is the top of the tower.
 26    towers = {'A': copy.copy(COMPLETE_TOWER), 'B': [], 'C': []}
 27
 28    while True:  # Run a single turn.
 29        # Display the towers and disks:
 30        displayTowers(towers)
 31
 32        # Ask the user for a move:
 33        fromTower, toTower = askForPlayerMove(towers)
 34
 35        # Move the top disk from fromTower to toTower:
 36        disk = towers[fromTower].pop()
 37        towers[toTower].append(disk)
 38
 39        # Check if the user has solved the puzzle:
 40        if COMPLETE_TOWER in (towers['B'], towers['C']):
 41            displayTowers(towers)  # Display the towers one last time.
 42            print('You have solved the puzzle! Well done!')
 43            sys.exit()
 44
 45
 46def askForPlayerMove(towers):
 47    """Asks the player for a move. Returns (fromTower, toTower)."""
 48
 49    while True:  # Keep asking player until they enter a valid move.
 50        print('Enter the letters of "from" and "to" towers, or QUIT.')
 51        print('(e.g. AB to moves a disk from tower A to tower B.)')
 52        response = input('> ').upper().strip()
 53
 54        if response == 'QUIT':
 55            print('Thanks for playing!')
 56            sys.exit()
 57
 58        # Make sure the user entered valid tower letters:
 59        if response not in ('AB', 'AC', 'BA', 'BC', 'CA', 'CB'):
 60            print('Enter one of AB, AC, BA, BC, CA, or CB.')
 61            continue  # Ask player again for their move.
 62
 63        # Syntactic sugar - Use more descriptive variable names:
 64        fromTower, toTower = response[0], response[1]
 65
 66        if len(towers[fromTower]) == 0:
 67            # The "from" tower cannot be an empty tower:
 68            print('You selected a tower with no disks.')
 69            continue  # Ask player again for their move.
 70        elif len(towers[toTower]) == 0:
 71            # Any disk can be moved onto an empty "to" tower:
 72            return fromTower, toTower
 73        elif towers[toTower][-1] < towers[fromTower][-1]:
 74            print('Can\'t put larger disks on top of smaller ones.')
 75            continue  # Ask player again for their move.
 76        else:
 77            # This is a valid move, so return the selected towers:
 78            return fromTower, toTower
 79
 80
 81def displayTowers(towers):
 82    """Display the current state."""
 83
 84    # Display the three towers:
 85    for level in range(TOTAL_DISKS, -1, -1):
 86        for tower in (towers['A'], towers['B'], towers['C']):
 87            if level >= len(tower):
 88                displayDisk(0)  # Display the bare pole with no disk.
 89            else:
 90                displayDisk(tower[level])  # Display the disk.
 91        print()
 92
 93    # Display the tower labels A, B, and C.
 94    emptySpace = ' ' * (TOTAL_DISKS)
 95    print('{0} A{0}{0} B{0}{0} C\n'.format(emptySpace))
 96
 97
 98def displayDisk(width):
 99    """Display a disk of the given width. A width of 0 means no disk."""
100    emptySpace = ' ' * (TOTAL_DISKS - width)
101
102    if width == 0:
103        # Display a pole segment without a disk:
104        print(emptySpace + '||' + emptySpace, end='')
105    else:
106        # Display the disk:
107        disk = '@' * width
108        numLabel = str(width).rjust(2, '_')
109        print(emptySpace + disk + numLabel + disk + emptySpace, end='')
110
111
112# If the program is run (instead of imported), run the game:
113if __name__ == '__main__':
114    main()

https://inventwithpython.com/bigbookpython/project77.html