This two-dimensional maze runner shows the player a top-down, bird’s-eye view of a maze file you create in a text editor, such as the IDE you use to write your .py files. Using the WASD keys, the player can move up, left, down, and right, respectively, to navigate the @
symbol toward the exit marked by the X
character.
To make a maze file, open a text editor and create the following pattern. Don’t type the numbers along the top and left side; they are only there for reference:
123456789
1#########
2#S# # # #
3#########
4# # # # #
5#########
6# # # # #
7#########
8# # # #E#
9#########
The # characters represent walls, the S
marks the start, and the E
marks the exit. The # characters in bold represent walls that you can remove to form your maze. Don’t remove the walls at odd-numbered columns and odd-numbered rows, and don’t remove the borders of the maze. When you’re done, save the maze as a .txt (text) file. It could look something like this:
#########
#S # #
# ### # #
# # # #
# ##### #
# # #
### # # #
# #E#
#########
Of course, this is a simple maze. You can make maze files of any size as long as they have an odd number of rows and columns. Be sure it’ll still fit on the screen, though! You can also download maze files from https://invpy.com/mazes/.
When you run mazerunner2d.py, the output will look like this:
Maze Runner 2D, by Al Sweigart [email protected]
(Maze files are generated by mazemakerrec.py)
Enter the filename of the maze (or LIST or QUIT):
> maze65x11s1.txt
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░@░ ░ ░ ░ ░ ░ ░
░ ░░░░░ ░ ░░░ ░ ░ ░░░░░░░ ░░░░░ ░░░░░░░░░░░░░░░░░░░░░ ░░░ ░ ░ ░ ░
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
░ ░ ░ ░░░░░ ░ ░ ░░░░░ ░ ░░░░░ ░░░ ░ ░░░░░░░░░ ░ ░░░ ░░░ ░ ░░░░░ ░
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
░░░░░░░░░ ░░░ ░░░ ░ ░░░░░░░░░░░ ░░░░░ ░ ░░░░░ ░ ░ ░░░ ░░░░░ ░░░░░
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
░ ░ ░ ░░░ ░ ░░░ ░ ░ ░ ░░░░░░░░░░░ ░░░░░░░░░░░░░ ░ ░░░░░ ░ ░░░░░ ░
░ ░ ░ ░ ░ ░ X░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
W
Enter direction, or QUIT: ASD
--snip--
The program loads the data for the maze’s walls from a text file and into a dictionary stored in the maze
variable. This dictionary has (x, y)
tuples for keys and the string in the WALL
, EMPTY
, START
, or EXIT
constants for values. Project 45, “Maze Runner 3D,” uses a similar dictionary representation of the maze. The difference between the projects is in the code that renders that maze on the screen. Since Maze Runner 2D is simpler, I recommend becoming familiar with this program first before moving on to Maze Runner 3D.
1. """Maze Runner 2D, by Al Sweigart [email protected]
2. Move around a maze and try to escape. Maze files are generated by
3. mazemakerrec.py.
4. This code is available at https://nostarch.com/big-book-small-python-programming
5. Tags: large, game, maze"""
6.
7. import sys, os
8.
9. # Maze file constants:
10. WALL = '#'
11. EMPTY = ' '
12. START = 'S'
13. EXIT = 'E'
14.
15. PLAYER = '@' # (!) Try changing this to '+' or 'o'.
16. BLOCK = chr(9617) # Character 9617 is '░'
17.
18.
19. def displayMaze(maze):
20. # Display the maze:
21. for y in range(HEIGHT):
22. for x in range(WIDTH):
23. if (x, y) == (playerx, playery):
24. print(PLAYER, end='')
25. elif (x, y) == (exitx, exity):
26. print('X', end='')
27. elif maze[(x, y)] == WALL:
28. print(BLOCK, end='')
29. else:
30. print(maze[(x, y)], end='')
31. print() # Print a newline after printing the row.
32.
33.
34. print('''Maze Runner 2D, by Al Sweigart [email protected]
35.
36. (Maze files are generated by mazemakerrec.py)''')
37.
38. # Get the maze file's filename from the user:
39. while True:
40. print('Enter the filename of the maze (or LIST or QUIT):')
41. filename = input('> ')
42.
43. # List all the maze files in the current folder:
44. if filename.upper() == 'LIST':
45. print('Maze files found in', os.getcwd())
46. for fileInCurrentFolder in os.listdir():
47. if (fileInCurrentFolder.startswith('maze') and
48. fileInCurrentFolder.endswith('.txt')):
49. print(' ', fileInCurrentFolder)
50. continue
51.
52. if filename.upper() == 'QUIT':
53. sys.exit()
54.
55. if os.path.exists(filename):
56. break
57. print('There is no file named', filename)
58.
59. # Load the maze from a file:
60. mazeFile = open(filename)
61. maze = {}
62. lines = mazeFile.readlines()
63. playerx = None
64. playery = None
65. exitx = None
66. exity = None
67. y = 0
68. for line in lines:
69. WIDTH = len(line.rstrip())
70. for x, character in enumerate(line.rstrip()):
71. assert character in (WALL, EMPTY, START, EXIT), 'Invalid character at column {}, line {}'.format(x + 1, y + 1)
72. if character in (WALL, EMPTY):
73. maze[(x, y)] = character
74. elif character == START:
75. playerx, playery = x, y
76. maze[(x, y)] = EMPTY
77. elif character == EXIT:
78. exitx, exity = x, y
79. maze[(x, y)] = EMPTY
80. y += 1
81. HEIGHT = y
82.
83. assert playerx != None and playery != None, 'No start in maze file.'
84. assert exitx != None and exity != None, 'No exit in maze file.'
85.
86. while True: # Main game loop.
87. displayMaze(maze)
88.
89. while True: # Get user move.
90. print(' W')
91. print('Enter direction, or QUIT: ASD')
92. move = input('> ').upper()
93.
94. if move == 'QUIT':
95. print('Thanks for playing!')
96. sys.exit()
97.
98. if move not in ['W', 'A', 'S', 'D']:
99. print('Invalid direction. Enter one of W, A, S, or D.')
100. continue
101.
102. # Check if the player can move in that direction:
103. if move == 'W' and maze[(playerx, playery - 1)] == EMPTY:
104. break
105. elif move == 'S' and maze[(playerx, playery + 1)] == EMPTY:
106. break
107. elif move == 'A' and maze[(playerx - 1, playery)] == EMPTY:
108. break
109. elif move == 'D' and maze[(playerx + 1, playery)] == EMPTY:
110. break
111.
112. print('You cannot move in that direction.')
113.
114. # Keep moving in this direction until you encounter a branch point.
115. if move == 'W':
116. while True:
117. playery -= 1
118. if (playerx, playery) == (exitx, exity):
119. break
120. if maze[(playerx, playery - 1)] == WALL:
121. break # Break if we've hit a wall.
122. if (maze[(playerx - 1, playery)] == EMPTY
123. or maze[(playerx + 1, playery)] == EMPTY):
124. break # Break if we've reached a branch point.
125. elif move == 'S':
126. while True:
127. playery += 1
128. if (playerx, playery) == (exitx, exity):
129. break
130. if maze[(playerx, playery + 1)] == WALL:
131. break # Break if we've hit a wall.
132. if (maze[(playerx - 1, playery)] == EMPTY
133. or maze[(playerx + 1, playery)] == EMPTY):
134. break # Break if we've reached a branch point.
135. elif move == 'A':
136. while True:
137. playerx -= 1
138. if (playerx, playery) == (exitx, exity):
139. break
140. if maze[(playerx - 1, playery)] == WALL:
141. break # Break if we've hit a wall.
142. if (maze[(playerx, playery - 1)] == EMPTY
143. or maze[(playerx, playery + 1)] == EMPTY):
144. break # Break if we've reached a branch point.
145. elif move == 'D':
146. while True:
147. playerx += 1
148. if (playerx, playery) == (exitx, exity):
149. break
150. if maze[(playerx + 1, playery)] == WALL:
151. break # Break if we've hit a wall.
152. if (maze[(playerx, playery - 1)] == EMPTY
153. or maze[(playerx, playery + 1)] == EMPTY):
154. break # Break if we've reached a branch point.
155.
156. if (playerx, playery) == (exitx, exity):
157. displayMaze(maze)
158. print('You have reached the exit! Good job!')
159. print('Thanks for playing!')
160. sys.exit()
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.
character == START
on line 74 to character == EXIT
?playery + 1
on line 105 to playery – 1
?(exitx, exity)
on line 156 to (None, None)
?while True:
on line 89 to while False:
?break
on line 104 to continue
?break
on line 121 to continue
?