Hungry Robots
You are trapped in a maze with hungry robots! You don’t know why robots need to eat, but you don’t want to find out. The robots are badly programmed and will move directly toward you, even if blocked by walls. You must trick the robots into crashing into each other (or dead robots) without being caught.
hungry_robots.py
1"""Hungry Robots, by Al Sweigart al@inventwithpython.com
2Escape the hungry robots by making them crash into each other.
3This code is available at https://nostarch.com/big-book-small-python-programming
4Tags: large, game"""
5
6import random, sys
7
8# Set up the constants:
9WIDTH = 40 # (!) Try changing this to 70 or 10.
10HEIGHT = 20 # (!) Try changing this to 10.
11NUM_ROBOTS = 10 # (!) Try changing this to 1 or 30.
12NUM_TELEPORTS = 2 # (!) Try changing this to 0 or 9999.
13NUM_DEAD_ROBOTS = 2 # (!) Try changing this to 0 or 20.
14NUM_WALLS = 100 # (!) Try changing this to 0 or 300.
15
16EMPTY_SPACE = ' ' # (!) Try changing this to '.'.
17PLAYER = '@' # (!) Try changing this to 'R'.
18ROBOT = 'R' # (!) Try changing this to '@'.
19DEAD_ROBOT = 'X' # (!) Try changing this to 'R'.
20
21# (!) Try changing this to '#' or 'O' or ' ':
22WALL = chr(9617) # Character 9617 is '░'
23
24
25def main():
26 print('''Hungry Robots, by Al Sweigart al@inventwithpython.com
27
28You are trapped in a maze with hungry robots! You don't know why robots
29need to eat, but you don't want to find out. The robots are badly
30programmed and will move directly toward you, even if blocked by walls.
31You must trick the robots into crashing into each other (or dead robots)
32without being caught. You have a personal teleporter device, but it only
33has enough battery for {} trips. Keep in mind, you and robots can slip
34through the corners of two diagonal walls!
35'''.format(NUM_TELEPORTS))
36
37 input('Press Enter to begin...')
38
39 # Set up a new game:
40 board = getNewBoard()
41 robots = addRobots(board)
42 playerPosition = getRandomEmptySpace(board, robots)
43 while True: # Main game loop.
44 displayBoard(board, robots, playerPosition)
45
46 if len(robots) == 0: # Check if the player has won.
47 print('All the robots have crashed into each other and you')
48 print('lived to tell the tale! Good job!')
49 sys.exit()
50
51 # Move the player and robots:
52 playerPosition = askForPlayerMove(board, robots, playerPosition)
53 robots = moveRobots(board, robots, playerPosition)
54
55 for x, y in robots: # Check if the player has lost.
56 if (x, y) == playerPosition:
57 displayBoard(board, robots, playerPosition)
58 print('You have been caught by a robot!')
59 sys.exit()
60
61
62def getNewBoard():
63 """Returns a dictionary that represents the board. The keys are
64 (x, y) tuples of integer indexes for board positions, the values are
65 WALL, EMPTY_SPACE, or DEAD_ROBOT. The dictionary also has the key
66 'teleports' for the number of teleports the player has left.
67 The living robots are stored separately from the board dictionary."""
68 board = {'teleports': NUM_TELEPORTS}
69
70 # Create an empty board:
71 for x in range(WIDTH):
72 for y in range(HEIGHT):
73 board[(x, y)] = EMPTY_SPACE
74
75 # Add walls on the edges of the board:
76 for x in range(WIDTH):
77 board[(x, 0)] = WALL # Make top wall.
78 board[(x, HEIGHT - 1)] = WALL # Make bottom wall.
79 for y in range(HEIGHT):
80 board[(0, y)] = WALL # Make left wall.
81 board[(WIDTH - 1, y)] = WALL # Make right wall.
82
83 # Add the random walls:
84 for i in range(NUM_WALLS):
85 x, y = getRandomEmptySpace(board, [])
86 board[(x, y)] = WALL
87
88 # Add the starting dead robots:
89 for i in range(NUM_DEAD_ROBOTS):
90 x, y = getRandomEmptySpace(board, [])
91 board[(x, y)] = DEAD_ROBOT
92 return board
93
94
95def getRandomEmptySpace(board, robots):
96 """Return a (x, y) integer tuple of an empty space on the board."""
97 while True:
98 randomX = random.randint(1, WIDTH - 2)
99 randomY = random.randint(1, HEIGHT - 2)
100 if isEmpty(randomX, randomY, board, robots):
101 break
102 return (randomX, randomY)
103
104
105def isEmpty(x, y, board, robots):
106 """Return True if the (x, y) is empty on the board and there's also
107 no robot there."""
108 return board[(x, y)] == EMPTY_SPACE and (x, y) not in robots
109
110
111def addRobots(board):
112 """Add NUM_ROBOTS number of robots to empty spaces on the board and
113 return a list of these (x, y) spaces where robots are now located."""
114 robots = []
115 for i in range(NUM_ROBOTS):
116 x, y = getRandomEmptySpace(board, robots)
117 robots.append((x, y))
118 return robots
119
120
121def displayBoard(board, robots, playerPosition):
122 """Display the board, robots, and player on the screen."""
123 # Loop over every space on the board:
124 for y in range(HEIGHT):
125 for x in range(WIDTH):
126 # Draw the appropriate character:
127 if board[(x, y)] == WALL:
128 print(WALL, end='')
129 elif board[(x, y)] == DEAD_ROBOT:
130 print(DEAD_ROBOT, end='')
131 elif (x, y) == playerPosition:
132 print(PLAYER, end='')
133 elif (x, y) in robots:
134 print(ROBOT, end='')
135 else:
136 print(EMPTY_SPACE, end='')
137 print() # Print a newline.
138
139
140def askForPlayerMove(board, robots, playerPosition):
141 """Returns the (x, y) integer tuple of the place the player moves
142 next, given their current location and the walls of the board."""
143 playerX, playerY = playerPosition
144
145 # Find which directions aren't blocked by a wall:
146 q = 'Q' if isEmpty(playerX - 1, playerY - 1, board, robots) else ' '
147 w = 'W' if isEmpty(playerX + 0, playerY - 1, board, robots) else ' '
148 e = 'E' if isEmpty(playerX + 1, playerY - 1, board, robots) else ' '
149 d = 'D' if isEmpty(playerX + 1, playerY + 0, board, robots) else ' '
150 c = 'C' if isEmpty(playerX + 1, playerY + 1, board, robots) else ' '
151 x = 'X' if isEmpty(playerX + 0, playerY + 1, board, robots) else ' '
152 z = 'Z' if isEmpty(playerX - 1, playerY + 1, board, robots) else ' '
153 a = 'A' if isEmpty(playerX - 1, playerY + 0, board, robots) else ' '
154 allMoves = (q + w + e + d + c + x + a + z + 'S')
155
156 while True:
157 # Get player's move:
158 print('(T)eleports remaining: {}'.format(board["teleports"]))
159 print(' ({}) ({}) ({})'.format(q, w, e))
160 print(' ({}) (S) ({})'.format(a, d))
161 print('Enter move or QUIT: ({}) ({}) ({})'.format(z, x, c))
162
163 move = input('> ').upper()
164 if move == 'QUIT':
165 print('Thanks for playing!')
166 sys.exit()
167 elif move == 'T' and board['teleports'] > 0:
168 # Teleport the player to a random empty space:
169 board['teleports'] -= 1
170 return getRandomEmptySpace(board, robots)
171 elif move != '' and move in allMoves:
172 # Return the new player position based on their move:
173 return {'Q': (playerX - 1, playerY - 1),
174 'W': (playerX + 0, playerY - 1),
175 'E': (playerX + 1, playerY - 1),
176 'D': (playerX + 1, playerY + 0),
177 'C': (playerX + 1, playerY + 1),
178 'X': (playerX + 0, playerY + 1),
179 'Z': (playerX - 1, playerY + 1),
180 'A': (playerX - 1, playerY + 0),
181 'S': (playerX, playerY)}[move]
182
183
184def moveRobots(board, robotPositions, playerPosition):
185 """Return a list of (x, y) tuples of new robot positions after they
186 have tried to move toward the player."""
187 playerx, playery = playerPosition
188 nextRobotPositions = []
189
190 while len(robotPositions) > 0:
191 robotx, roboty = robotPositions[0]
192
193 # Determine the direction the robot moves.
194 if robotx < playerx:
195 movex = 1 # Move right.
196 elif robotx > playerx:
197 movex = -1 # Move left.
198 elif robotx == playerx:
199 movex = 0 # Don't move horizontally.
200
201 if roboty < playery:
202 movey = 1 # Move up.
203 elif roboty > playery:
204 movey = -1 # Move down.
205 elif roboty == playery:
206 movey = 0 # Don't move vertically.
207
208 # Check if the robot would run into a wall, and adjust course:
209 if board[(robotx + movex, roboty + movey)] == WALL:
210 # Robot would run into a wall, so come up with a new move:
211 if board[(robotx + movex, roboty)] == EMPTY_SPACE:
212 movey = 0 # Robot can't move horizontally.
213 elif board[(robotx, roboty + movey)] == EMPTY_SPACE:
214 movex = 0 # Robot can't move vertically.
215 else:
216 # Robot can't move.
217 movex = 0
218 movey = 0
219 newRobotx = robotx + movex
220 newRoboty = roboty + movey
221
222 if (board[(robotx, roboty)] == DEAD_ROBOT
223 or board[(newRobotx, newRoboty)] == DEAD_ROBOT):
224 # Robot is at a crash site, remove it.
225 del robotPositions[0]
226 continue
227
228 # Check if it moves into a robot, then destroy both robots:
229 if (newRobotx, newRoboty) in nextRobotPositions:
230 board[(newRobotx, newRoboty)] = DEAD_ROBOT
231 nextRobotPositions.remove((newRobotx, newRoboty))
232 else:
233 nextRobotPositions.append((newRobotx, newRoboty))
234
235 # Remove robots from robotPositions as they move.
236 del robotPositions[0]
237 return nextRobotPositions
238
239
240# If this program was run (instead of imported), run the game:
241if __name__ == '__main__':
242 main()