This classic puzzle relies on a 4 × 4 board with 15 numbered tiles and one free space. The objective is to slide the tiles until the numbers are in the correct order, going left to right and top to bottom. Tiles can only slide; you’re not allowed to directly pick them up and rearrange them. Some versions of this puzzle toy feature scrambled images that form a complete picture once solved.
More information about sliding tile puzzles can be found at https://en.wikipedia.org/wiki/Sliding_puzzle.
When you run slidingtilepuzzle.py, the output will look like this:
Sliding Tile Puzzle, by Al Sweigart [email protected]
Use the WASD keys to move the tiles
back into their original order:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15
Press Enter to begin...
+------+------+------+------+
| | | | |
| 5 | 10 | | 11 |
| | | | |
+------+------+------+------+
| | | | |
| 6 | 3 | 7 | 2 |
| | | | |
+------+------+------+------+
| | | | |
| 14 | 1 | 15 | 8 |
| | | | |
+------+------+------+------+
| | | | |
| 9 | 13 | 4 | 12 |
| | | | |
+------+------+------+------+
(W)
Enter WASD (or QUIT): (A) ( ) (D)
> w
+------+------+------+------+
| | | | |
| 5 | 10 | 7 | 11 |
| | | | |
+------+------+------+------+
| | | | |
| 6 | 3 | | 2 |
| | | | |
+------+------+------+------+
| | | | |
| 14 | 1 | 15 | 8 |
| | | | |
+------+------+------+------+
| | | | |
| 9 | 13 | 4 | 12 |
| | | | |
+------+------+------+------+
(W)
Enter WASD (or QUIT): (A) (S) (D)
--snip--
The data structure that represents the sliding tile game board is a list of lists. The inner lists each represent one column of the 4 × 4 board and contain strings for the numbered tiles (or the BLANK
string to represent the blank space). The getNewBoard()
function returns this list of lists with all tiles in their starting positions and the blank space in the lower-right corner.
Python can swap the values in two variables with a statement like a, b = b, a
. The program uses this technique on lines 101 to 108 to swap the blank space and a neighboring tile and simulate sliding a numbered tile into the blank space. The getNewPuzzle()
function generates new puzzles by performing 200 of these swaps randomly.
1. """Sliding Tile Puzzle, by Al Sweigart [email protected]
2. Slide the numbered tiles into the correct order.
3. This code is available at https://nostarch.com/big-book-small-python-programming
4. Tags: large, game, puzzle"""
5.
6. import random, sys
7.
8. BLANK = ' ' # Note: This string is two spaces, not one.
9.
10.
11. def main():
12. print('''Sliding Tile Puzzle, by Al Sweigart [email protected]
13.
14. Use the WASD keys to move the tiles
15. back into their original order:
16. 1 2 3 4
17. 5 6 7 8
18. 9 10 11 12
19. 13 14 15 ''')
20. input('Press Enter to begin...')
21.
22. gameBoard = getNewPuzzle()
23.
24. while True:
25. displayBoard(gameBoard)
26. playerMove = askForPlayerMove(gameBoard)
27. makeMove(gameBoard, playerMove)
28.
29. if gameBoard == getNewBoard():
30. print('You won!')
31. sys.exit()
32.
33.
34. def getNewBoard():
35. """Return a list of lists that represents a new tile puzzle."""
36. return [['1 ', '5 ', '9 ', '13'], ['2 ', '6 ', '10', '14'],
37. ['3 ', '7 ', '11', '15'], ['4 ', '8 ', '12', BLANK]]
38.
39.
40. def displayBoard(board):
41. """Display the given board on the screen."""
42. labels = [board[0][0], board[1][0], board[2][0], board[3][0],
43. board[0][1], board[1][1], board[2][1], board[3][1],
44. board[0][2], board[1][2], board[2][2], board[3][2],
45. board[0][3], board[1][3], board[2][3], board[3][3]]
46. boardToDraw = """
47. +------+------+------+------+
48. | | | | |
49. | {} | {} | {} | {} |
50. | | | | |
51. +------+------+------+------+
52. | | | | |
53. | {} | {} | {} | {} |
54. | | | | |
55. +------+------+------+------+
56. | | | | |
57. | {} | {} | {} | {} |
58. | | | | |
59. +------+------+------+------+
60. | | | | |
61. | {} | {} | {} | {} |
62. | | | | |
63. +------+------+------+------+
64. """.format(*labels)
65. print(boardToDraw)
66.
67.
68. def findBlankSpace(board):
69. """Return an (x, y) tuple of the blank space's location."""
70. for x in range(4):
71. for y in range(4):
72. if board[x][y] == ' ':
73. return (x, y)
74.
75.
76. def askForPlayerMove(board):
77. """Let the player select a tile to slide."""
78. blankx, blanky = findBlankSpace(board)
79.
80. w = 'W' if blanky != 3 else ' '
81. a = 'A' if blankx != 3 else ' '
82. s = 'S' if blanky != 0 else ' '
83. d = 'D' if blankx != 0 else ' '
84.
85. while True:
86. print(' ({})'.format(w))
87. print('Enter WASD (or QUIT): ({}) ({}) ({})'.format(a, s, d))
88.
89. response = input('> ').upper()
90. if response == 'QUIT':
91. sys.exit()
92. if response in (w + a + s + d).replace(' ', ''):
93. return response
94.
95.
96. def makeMove(board, move):
97. """Carry out the given move on the given board."""
98. # Note: This function assumes that the move is valid.
99. bx, by = findBlankSpace(board)
100.
101. if move == 'W':
102. board[bx][by], board[bx][by+1] = board[bx][by+1], board[bx][by]
103. elif move == 'A':
104. board[bx][by], board[bx+1][by] = board[bx+1][by], board[bx][by]
105. elif move == 'S':
106. board[bx][by], board[bx][by-1] = board[bx][by-1], board[bx][by]
107. elif move == 'D':
108. board[bx][by], board[bx-1][by] = board[bx-1][by], board[bx][by]
109.
110.
111. def makeRandomMove(board):
112. """Perform a slide in a random direction."""
113. blankx, blanky = findBlankSpace(board)
114. validMoves = []
115. if blanky != 3:
116. validMoves.append('W')
117. if blankx != 3:
118. validMoves.append('A')
119. if blanky != 0:
120. validMoves.append('S')
121. if blankx != 0:
122. validMoves.append('D')
123.
124. makeMove(board, random.choice(validMoves))
125.
126.
127. def getNewPuzzle(moves=200):
128. """Get a new puzzle by making random slides from a solved state."""
129. board = getNewBoard()
130.
131. for i in range(moves):
132. makeRandomMove(board)
133. return board
134.
135.
136. # If this program was run (instead of imported), run the game:
137. if __name__ == '__main__':
138. main()
After entering the source code and running it a few times, try making experimental changes to it. On your own, you can also try to figure out how to do the following:
Try to find the answers to the following questions. Experiment with some modifications to the code and rerun the program to see what effect the changes have.
getNewPuzzle()
on line 22 to getNewPuzzle(1)
?getNewPuzzle()
on line 22 to getNewPuzzle(0)
?sys.exit()
on line 31?