A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://inventwithpython.com/bigbookpython/project45.html below:

Maze Runner 3D

#45
Maze Runner 3D

This three-dimensional maze runner provides the player with a first-person view from inside a maze. Try to find your way out! You can generate maze files by following the instructions in Project 44, “Maze Runner 2D,” or by downloading maze files from https://invpy.com/mazes/.

The Program in Action

When you run mazerunner3d.py, the output will look like this:

Maze Runner 3D, by Al Sweigart [email protected]
(Maze files are generated by mazemakerrec.py)
Enter the filename of the maze (or LIST or QUIT):
> maze75x11s1.txt
░░░░░░░░░░░░░░░░░░░
░  \           /  ░
░   \_________/   ░
░    |       |    ░
░    |       |    ░
░    |       |    ░
░    |       |    ░
░    |       |    ░
░    |       |    ░
░    |       |    ░
░    |       |    ░
░    |_______|    ░
░   /         \   ░
░  /           \  ░
░░░░░░░░░░░░░░░░░░░
Location (1, 1)  Direction: NORTH
                   (W)
Enter direction: (A) (D)  or QUIT.
> d
░░░░░░░░░░░░░░░░░░░
░  \              ░
░   \_____________░
░    |            ░
░    |            ░
░    |            ░
░    |            ░
░    |            ░
░    |            ░
░    |            ░
░    |            ░
░    |____________░
░   /             ░
░  /              ░
░░░░░░░░░░░░░░░░░░░
Location (1, 1)  Direction: EAST
--snip--
How It Works

This 3D-perspective ASCII art starts with the multiline string stored in ALL_OPEN. This string depicts a position in which no paths are closed off by walls. The program then draws the walls, stored in the CLOSED dictionary, on top of the ALL_OPEN string to generate the ASCII art for any possible combination of closed-off paths. For example, here’s how the program generates the view in which the wall is to the left of the player:

                            \               \
____         ____            \_              \_        ____
   |\       /|                |               |       /|
   ||       ||                |               |       ||
   ||__   __||                |               |__   __||
   || |\ /| ||                |               | |\ /| ||
   || | X | ||        +       |       =       | | X | ||
   || |/ \| ||                |               | |/ \| ||
   ||_/   \_||                |               |_/   \_||
   ||       ||                |               |       ||
___|/       \|___             |               |       \|___
                             /               /
                            /               /

The periods in the ASCII art in the source code get removed before the strings are displayed; they only exist to make entering the code easier, so you don’t insert or leave out blank spaces.

Here is the source code for the 3D maze:

  1. """Maze 3D, by Al Sweigart [email protected]
  2. Move around a maze and try to escape... in 3D!
  3. View this code at https://nostarch.com/big-book-small-python-projects
  4. Tags: extra-large, artistic, game, maze"""
  5.
  6. import copy, sys, os
  7.
  8. # Set up the constants:
  9. WALL = '#'
 10. EMPTY = ' '
 11. START = 'S'
 12. EXIT = 'E'
 13. BLOCK = chr(9617)  # Character 9617 is '░'
 14. NORTH = 'NORTH'
 15. SOUTH = 'SOUTH'
 16. EAST = 'EAST'
 17. WEST = 'WEST'
 18.
 19.
 20. def wallStrToWallDict(wallStr):
 21.     """Takes a string representation of a wall drawing (like those in
 22.     ALL_OPEN or CLOSED) and returns a representation in a dictionary
 23.     with (x, y) tuples as keys and single-character strings of the
 24.     character to draw at that x, y location."""
 25.     wallDict = {}
 26.     height = 0
 27.     width = 0
 28.     for y, line in enumerate(wallStr.splitlines()):
 29.         if y > height:
 30.             height = y
 31.         for x, character in enumerate(line):
 32.             if x > width:
 33.                 width = x
 34.             wallDict[(x, y)] = character
 35.     wallDict['height'] = height + 1
 36.     wallDict['width'] = width + 1
 37.     return wallDict
 38.
 39. EXIT_DICT = {(0, 0): 'E', (1, 0): 'X', (2, 0): 'I',
 40.              (3, 0): 'T', 'height': 1, 'width': 4}
 41.
 42. # The way we create the strings to display is by converting the pictures
 43. # in these multiline strings to dictionaries using wallStrToWallDict().
 44. # Then we compose the wall for the player's location and direction by
 45. # "pasting" the wall dictionaries in CLOSED on top of the wall dictionary
 46. # in ALL_OPEN.
 47.
 48. ALL_OPEN = wallStrToWallDict(r'''
 49. .................
 50. ____.........____
 51. ...|\......./|...
 52. ...||.......||...
 53. ...||__...__||...
 54. ...||.|\./|.||...
 55. ...||.|.X.|.||...
 56. ...||.|/.\|.||...
 57. ...||_/...\_||...
 58. ...||.......||...
 59. ___|/.......\|___
 60. .................
 61. .................'''.strip())
 62. # The strip() call is used to remove the newline
 63. # at the start of this multiline string.
 64.
 65. CLOSED = {}
 66. CLOSED['A'] = wallStrToWallDict(r'''
 67. _____
 68. .....
 69. .....
 70. .....
 71. _____'''.strip()) # Paste to 6, 4.
 72.
 73. CLOSED['B'] = wallStrToWallDict(r'''
 74. .\.
 75. ..\
 76. ...
 77. ...
 78. ...
 79. ../
 80. ./.'''.strip()) # Paste to 4, 3.
 81.
 82. CLOSED['C'] = wallStrToWallDict(r'''
 83. ___________
 84. ...........
 85. ...........
 86. ...........
 87. ...........
 88. ...........
 89. ...........
 90. ...........
 91. ...........
 92. ___________'''.strip()) # Paste to 3, 1.
 93.
 94. CLOSED['D'] = wallStrToWallDict(r'''
 95. ./.
 96. /..
 97. ...
 98. ...
 99. ...
100. \..
101. .\.'''.strip()) # Paste to 10, 3.
102.
103. CLOSED['E'] = wallStrToWallDict(r'''
104. ..\..
105. ...\_
106. ....|
107. ....|
108. ....|
109. ....|
110. ....|
111. ....|
112. ....|
113. ....|
114. ....|
115. .../.
116. ../..'''.strip()) # Paste to 0, 0.
117.
118. CLOSED['F'] = wallStrToWallDict(r'''
119. ../..
120. _/...
121. |....
122. |....
123. |....
124. |....
125. |....
126. |....
127. |....
128. |....
129. |....
130. .\...
131. ..\..'''.strip()) # Paste to 12, 0.
132.
133. def displayWallDict(wallDict):
134.     """Display a wall dictionary, as returned by wallStrToWallDict(), on
135.     the screen."""
136.     print(BLOCK * (wallDict['width'] + 2))
137.     for y in range(wallDict['height']):
138.         print(BLOCK, end='')
139.         for x in range(wallDict['width']):
140.             wall = wallDict[(x, y)]
141.             if wall == '.':
142.                 wall = ' '
143.             print(wall, end='')
144.         print(BLOCK)  # Print block with a newline.
145.     print(BLOCK * (wallDict['width'] + 2))
146.
147.
148. def pasteWallDict(srcWallDict, dstWallDict, left, top):
149.     """Copy the wall representation dictionary in srcWallDict on top of
150.     the one in dstWallDict, offset to the position given by left, top."""
151.     dstWallDict = copy.copy(dstWallDict)
152.     for x in range(srcWallDict['width']):
153.         for y in range(srcWallDict['height']):
154.             dstWallDict[(x + left, y + top)] = srcWallDict[(x, y)]
155.     return dstWallDict
156.
157.
158. def makeWallDict(maze, playerx, playery, playerDirection, exitx, exity):
159.     """From the player's position and direction in the maze (which has
160.     an exit at exitx, exity), create the wall representation dictionary
161.     by pasting wall dictionaries on top of ALL_OPEN, then return it."""
162.
163.     # The A-F "sections" (which are relative to the player's direction)
164.     # determine which walls in the maze we check to see if we need to
165.     # paste them over the wall representation dictionary we're creating.
166.
167.     if playerDirection == NORTH:
168.         # Map of the sections, relative  A
169.         # to the player @:              BCD (Player facing north)
170.         #                               E@F
171.         offsets = (('A', 0, -2), ('B', -1, -1), ('C', 0, -1),
172.                    ('D', 1, -1), ('E', -1, 0), ('F', 1, 0))
173.     if playerDirection == SOUTH:
174.         # Map of the sections, relative F@E
175.         # to the player @:              DCB (Player facing south)
176.         #                                A
177.         offsets = (('A', 0, 2), ('B', 1, 1), ('C', 0, 1),
178.                    ('D', -1, 1), ('E', 1, 0), ('F', -1, 0))
179.     if playerDirection == EAST:
180.         # Map of the sections, relative EB
181.         # to the player @:              @CA (Player facing east)
182.         #                               FD
183.         offsets = (('A', 2, 0), ('B', 1, -1), ('C', 1, 0),
184.                    ('D', 1, 1), ('E', 0, -1), ('F', 0, 1))
185.     if playerDirection == WEST:
186.         # Map of the sections, relative  DF
187.         # to the player @:              AC@ (Player facing west)
188.         #                                BE
189.         offsets = (('A', -2, 0), ('B', -1, 1), ('C', -1, 0),
190.                    ('D', -1, -1), ('E', 0, 1), ('F', 0, -1))
191.
192.     section = {}
193.     for sec, xOff, yOff in offsets:
194.         section[sec] = maze.get((playerx + xOff, playery + yOff), WALL)
195.         if (playerx + xOff, playery + yOff) == (exitx, exity):
196.             section[sec] = EXIT
197.
198.     wallDict = copy.copy(ALL_OPEN)
199.     PASTE_CLOSED_TO = {'A': (6, 4), 'B': (4, 3), 'C': (3, 1),
200.                        'D': (10, 3), 'E': (0, 0), 'F': (12, 0)}
201.     for sec in 'ABDCEF':
202.         if section[sec] == WALL:
203.             wallDict = pasteWallDict(CLOSED[sec], wallDict,
204.                 PASTE_CLOSED_TO[sec][0], PASTE_CLOSED_TO[sec][1])
205.
206.     # Draw the EXIT sign if needed:
207.     if section['C'] == EXIT:
208.         wallDict = pasteWallDict(EXIT_DICT, wallDict, 7, 9)
209.     if section['E'] == EXIT:
210.         wallDict = pasteWallDict(EXIT_DICT, wallDict, 0, 11)
211.     if section['F'] == EXIT:
212.         wallDict = pasteWallDict(EXIT_DICT, wallDict, 13, 11)
213.
214.     return wallDict
215.
216.
217. print('Maze Runner 3D, by Al Sweigart [email protected]')
218. print('(Maze files are generated by mazemakerrec.py)')
219.
220. # Get the maze file's filename from the user:
221. while True:
222.     print('Enter the filename of the maze (or LIST or QUIT):')
223.     filename = input('> ')
224.
225.     # List all the maze files in the current folder:
226.     if filename.upper() == 'LIST':
227.         print('Maze files found in', os.getcwd())
228.         for fileInCurrentFolder in os.listdir():
229.             if (fileInCurrentFolder.startswith('maze')
230.             and fileInCurrentFolder.endswith('.txt')):
231.                 print('  ', fileInCurrentFolder)
232.         continue
233.
234.     if filename.upper() == 'QUIT':
235.         sys.exit()
236.
237.     if os.path.exists(filename):
238.         break
239.     print('There is no file named', filename)
240.
241. # Load the maze from a file:
242. mazeFile = open(filename)
243. maze = {}
244. lines = mazeFile.readlines()
245. px = None
246. py = None
247. exitx = None
248. exity = None
249. y = 0
250. for line in lines:
251.     WIDTH = len(line.rstrip())
252.     for x, character in enumerate(line.rstrip()):
253.         assert character in (WALL, EMPTY, START, EXIT), 'Invalid character
             at column {}, line {}'.format(x + 1, y + 1)
254.         if character in (WALL, EMPTY):
255.             maze[(x, y)] = character
256.         elif character == START:
257.             px, py = x, y
258.             maze[(x, y)] = EMPTY
259.         elif character == EXIT:
260.             exitx, exity = x, y
261.             maze[(x, y)] = EMPTY
262.     y += 1
263. HEIGHT = y
264.
265. assert px != None and py != None, 'No start point in file.'
266. assert exitx != None and exity != None, 'No exit point in file.'
267. pDir = NORTH
268.
269.
270. while True:  # Main game loop.
271.     displayWallDict(makeWallDict(maze, px, py, pDir, exitx, exity))
272.
273.     while True: # Get user move.
274.         print('Location ({}, {})  Direction: {}'.format(px, py, pDir))
275.         print('                   (W)')
276.         print('Enter direction: (A) (D)  or QUIT.')
277.         move = input('> ').upper()
278.
279.         if move == 'QUIT':
280.             print('Thanks for playing!')
281.             sys.exit()
282.
283.         if (move not in ['F', 'L', 'R', 'W', 'A', 'D']
284.             and not move.startswith('T')):
285.             print('Please enter one of F, L, or R (or W, A, D).')
286.             continue
287.
288.         # Move the player according to their intended move:
289.         if move == 'F' or move == 'W':
290.             if pDir == NORTH and maze[(px, py - 1)] == EMPTY:
291.                 py -= 1
292.                 break
293.             if pDir == SOUTH and maze[(px, py + 1)] == EMPTY:
294.                 py += 1
295.                 break
296.             if pDir == EAST and maze[(px + 1, py)] == EMPTY:
297.                 px += 1
298.                 break
299.             if pDir == WEST and maze[(px - 1, py)] == EMPTY:
300.                 px -= 1
301.                 break
302.         elif move == 'L' or move == 'A':
303.             pDir = {NORTH: WEST, WEST: SOUTH,
304.                     SOUTH: EAST, EAST: NORTH}[pDir]
305.             break
306.         elif move == 'R' or move == 'D':
307.             pDir = {NORTH: EAST, EAST: SOUTH,
308.                     SOUTH: WEST, WEST: NORTH}[pDir]
309.             break
310.         elif move.startswith('T'):  # Cheat code: 'T x,y'
311.             px, py = move.split()[1].split(',')
312.             px = int(px)
313.             py = int(py)
314.             break
315.         else:
316.             print('You cannot move in that direction.')
317.
318.     if (px, py) == (exitx, exity):
319.         print('You have reached the exit! Good job!')
320.         print('Thanks for playing!')
321.         sys.exit()
Exploring the Program

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.

  1. What bug do you cause if you change move == 'QUIT' on line 279 to move == 'quit'?
  2. How can you remove the teleportation cheat?

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4