Tower of Hanoi
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()