"""The Tower of Hanoi, by Al Sweigart al@inventwithpython.com
A stack-moving puzzle game.
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: short, game, puzzle"""
import copy
import sys
TOTAL_DISKS = 5 # More disks means a more difficult puzzle.
# Start with all disks on tower A:
COMPLETE_TOWER = list(range(TOTAL_DISKS, 0, -1))
def main():
print("""The Tower of Hanoi, by Al Sweigart al@inventwithpython.com
Move the tower of disks, one disk at a time, to another tower. Larger
disks cannot rest on top of a smaller disk.
More info at https://en.wikipedia.org/wiki/Tower_of_Hanoi
"""
)
# Set up the towers. The end of the list is the top of the tower.
towers = {'A': copy.copy(COMPLETE_TOWER), 'B': [], 'C': []}
while True: # Run a single turn.
# Display the towers and disks:
displayTowers(towers)
# Ask the user for a move:
fromTower, toTower = askForPlayerMove(towers)
# Move the top disk from fromTower to toTower:
disk = towers[fromTower].pop()
towers[toTower].append(disk)
# Check if the user has solved the puzzle:
if COMPLETE_TOWER in (towers['B'], towers['C']):
displayTowers(towers) # Display the towers one last time.
print('You have solved the puzzle! Well done!')
sys.exit()
def askForPlayerMove(towers):
"""Asks the player for a move. Returns (fromTower, toTower)."""
while True: # Keep asking player until they enter a valid move.
print('Enter the letters of "from" and "to" towers, or QUIT.')
print('(e.g. AB to moves a disk from tower A to tower B.)')
response = input('> ').upper().strip()
if response == 'QUIT':
print('Thanks for playing!')
sys.exit()
# Make sure the user entered valid tower letters:
if response not in ('AB', 'AC', 'BA', 'BC', 'CA', 'CB'):
print('Enter one of AB, AC, BA, BC, CA, or CB.')
continue # Ask player again for their move.
# Syntactic sugar - Use more descriptive variable names:
fromTower, toTower = response[0], response[1]
if len(towers[fromTower]) == 0:
# The "from" tower cannot be an empty tower:
print('You selected a tower with no disks.')
continue # Ask player again for their move.
elif len(towers[toTower]) == 0:
# Any disk can be moved onto an empty "to" tower:
return fromTower, toTower
elif towers[toTower][-1] < towers[fromTower][-1]:
print('Can\'t put larger disks on top of smaller ones.')
continue # Ask player again for their move.
else:
# This is a valid move, so return the selected towers:
return fromTower, toTower
def displayTowers(towers):
"""Display the current state."""
# Display the three towers:
for level in range(TOTAL_DISKS, -1, -1):
for tower in (towers['A'], towers['B'], towers['C']):
if level >= len(tower):
displayDisk(0) # Display the bare pole with no disk.
else:
displayDisk(tower[level]) # Display the disk.
print()
# Display the tower labels A, B, and C.
emptySpace = ' ' * (TOTAL_DISKS)
print('{0} A{0}{0} B{0}{0} C\n'.format(emptySpace))
def displayDisk(width):
"""Display a disk of the given width. A width of 0 means no disk."""
emptySpace = ' ' * (TOTAL_DISKS - width)
if width == 0:
# Display a pole segment without a disk:
print(emptySpace + '||' + emptySpace, end='')
else:
# Display the disk:
disk = '@' * width
numLabel = str(width).rjust(2, '_')
print(emptySpace + disk + numLabel + disk + emptySpace, end='')
# If the program is run (instead of imported), run the game:
if __name__ == '__main__':
main()