Part 9: Refactoring
Contents
Part 9: Refactoring#
In this section we are going to work on refactoring our game. Refactoring is when you change code to make the code better, but without changing what the software does.
It is key to make changes in small, incremental steps which are tested regularly to ensure the program works properly throughout. Avoid tearing out and reworking vast swaths of code at a time, which will often leave you with a hopeless tangle of broken code.
Here we’ll be focusing on the DRY1 principle–don’t repeat yourself. This means that when you notice you’re repeating the same, or nearly the same, code over and over again, find a way to put it somewhere it can be reused. In this case we’ll be adding a few functions, then call those functions in the places where the same code is repeated.
Part 9.1: Add abort()
#
The abort()
function will be similar to the error()
function, except it
will exit the game immediately. This function will be for errors that only
happen if there is a problem with the code, as opposed to errors that can be
caused by something the user typed in.
(This will actually result in a minor change in behavior. Now, when you
encounter any errors using the abort()
function, your game will end
immediately instead of continuing on.)
A. Define abort()
#
The abort()
function will simply call error()
to print an error message,
then exit the program.
When abort()
is called, a return
statement will no longer be needed since
the program will end immediately.
[ ]
define anabort()
function that takes one argumentmessage
[ ]
inabort()
[ ]
callerror()
with the argumentmessage
.[ ]
call the built-inexit()
function and pass it the argument1
Code
113def abort(message):
114 """Print a fatal error message then exit the game."""
115 error(message)
116 exit(1)
B. Modify do_take()
#
Since we always expect to be able to find an item in the ITEMS
dictionary for
a key in the place
items list, if we fail to find one that means that we
messed something up, not the player. This is a perfect place to use abort()
instead of error()
.
Then because abort()
exits the program immediately, we can remove the return
.
[ ]
Callabort()
instead oferror()
when you check ifitem
is falsy[ ]
remove thereturn
statement[ ]
To test, temporarily change the key for"book"
to something else, then typetake book
from home. It should print an error message then exit the program. After verifying that it works, change it back.
Code
270def do_take(args):
271 """Pick up an item and add it to inventory."""
272 debug(f"Trying to take: {args}")
273
274 # make sure the player typed an item
275 if not args:
276 error("What do you want to take?")
277 return
278
279 # get the item name from arguments
280 # and make it lowercase
281 name = args[0].lower()
282
283 # look up where the player is now
284 place_name = PLAYER["place"]
285 place = PLACES[place_name]
286
287 # make sure the item is in this place
288 if name not in place.get("items", []):
289 error(f"Sorry, I don't see a {name!r} here.")
290 return
291
292 # get the item information
293 item = ITEMS.get(name)
294
295 # make sure the item is in the ITEMS dictionary
296 if not item:
297 abort(f"Woops! The information about {name!r} seems to be missing.")
298
C. Modify do_examine()
#
This is nearly exactly the same as the previous section.
[ ]
Callabort()
instead oferror()
when you check ifname
is not inITEMS
[ ]
remove thereturn
statement[ ]
To test, temporarily change the key for"book"
to something else, then typetake book
from home. It should print an error message then exit the program. After verifying that it works, change it back.
Code
192def do_examine(args):
193 """Look at an item in the current place."""
194
195 debug(f"Trying to examine: {args}")
196
197 # make sure the player said what they want to examine
198 if not args:
199 error("What do you want to examine?")
200 return
201
202 # look up where the player is now
203 place_name = PLAYER["place"]
204 place = PLACES[place_name]
205
206 # get the item entered by the user and make it lowercase
207 name = args[0].lower()
208
209 # make sure the item is in this place or in the players inventory
210 if not (name in place.get("items", []) or name in PLAYER["inventory"]):
211 error(f"Sorry, I don't know what this is: {name!r}.")
212 return
213
214 # make sure the item is in the ITEMS dictionary
215 if name not in ITEMS:
216 abort(f"Woops! The information about {name} seems to be missing.")
D. Modify do_go()
#
Similar to the previous two sections, we always expect to be able to find an
place in the PLACES
dictionary for a key from another place
dictionary, so
if we don’t it means there’s an error somewhere in the code.
[ ]
Callabort()
instead oferror()
when you check ifnew_place
is truthy[ ]
remove thereturn
statement[ ]
To test, temporarily change the value forhome["east"]
to something else, then typego east
from home. It should print an error message then exit the program. After verifying that it works, change it back.
Code
224def do_go(args):
225 """Move to a different place"""
226 debug(f"Trying to go: {args}")
227
228 # make sure the player included a direction
229 if not args:
230 error("Which way do you want to go?")
231 return
232
233 # get the direction from arguments
234 # and make it lowercase
235 direction = args[0].lower()
236
237 # make sure it's a valid direction
238 if direction not in ('north', 'south', 'east', 'west'):
239 error(f"Sorry, I don't know how to go: {direction}.")
240 return
241
242 # look up where the player is now
243 old_name = PLAYER["place"]
244 old_place = PLACES[old_name]
245
246 # look up what is in that direction from here
247 new_name = old_place.get(direction)
248
249 # print an error if there is nothing in that direction
250 if not new_name:
251 error(f"Sorry, you can't go {direction} from here.")
252 return
253
254 # look up the place information
255 new_place = PLACES.get(new_name)
256
257 # this should never happen if we write the code correctly
258 # but just in case there is no key in PLACES matching
259 # the new name, print an error
260 if not new_place:
261 abort(f"Woops! The information about {new_name} seems to be missing.")
262
Part 9.2: Add get_place()
#
Since we’re so often needing to get place information from the PLACES
dictionary, we’ll wrap this functionality into a function called get_place()
that we can call from anywhere in our program where we need to get place
information.
A. Define get_place()
#
The get_place()
function will take one argument, the key
to get from the
PLACES
dictionary. We’ll make that argument optional though, and set the
default to None
.
If the user passes a key
argument, then we’ll get the place information from
PLACES
for that key
. If they don’t, we’ll get the key from the PLAYER
dictionary for their current location. That way we can call get_place()
with
no args to get the current place.
This function will also check to make sure the place is found in the PLACES
dictionary, and call abort()
if it is not. That means that anywhere we call
get_place()
will not have to do that error handling over and over.
[ ]
define aget_place()
function that takes one optional argumentkey
with a default value ofNone
[ ]
ifkey
is falsy then assignkey
to the value of thePLAYER
dict associated with the"place"
value[ ]
get the value from thePLACES
dictionary associated from thekey
key and assign it to the variableplace
[ ]
Ifplace
is falsy,[ ]
Use theabort()
function to print an error message like:"Woops! The information about the place name seems to be missing."
[ ]
returnplace
Code
120def get_place(key=None):
121 """Return the place information from the PLACES dictionary, either
122 associated with key, or if none is passed, where the user is
123 currently at. """
124 # get the current player's place if key is not passed
125 if not key:
126 key = PLAYER["place"]
127
128 # get the place info
129 place = PLACES.get(key)
130
131 # this should never happen if we write the code correctly
132 # but just in case there is no key in PLACES matching
133 # the new name, print an error
134 if not place:
135 abort(f"Woops! The information about {key!r} seems to be missing.")
136
137 return place
B. Modify do_go()
#
[ ]
Callget_place()
with no arguments to get the value forold_place
. (This will replace the existingPLACES[old_place]
.)[ ]
Remove the line assigningold_name
since that is taken care of inget_place()
[ ]
Callget_place()
with the argumentnew_name
to get the value fornew_place
. (This will replace the existingPLACES.get(new_place)
.)[ ]
Remove the lines that callsabort()
ifnew_place
is falsy.
Code
244def do_go(args):
245 """Move to a different place"""
246 debug(f"Trying to go: {args}")
247
248 # make sure the player included a direction
249 if not args:
250 error("Which way do you want to go?")
251 return
252
253 # get the direction from arguments
254 # and make it lowercase
255 direction = args[0].lower()
256
257 # make sure it's a valid direction
258 if direction not in ('north', 'south', 'east', 'west'):
259 error(f"Sorry, I don't know how to go: {direction}.")
260 return
261
262 # look up where the player is now
263 old_place = get_place()
264
265 # look up what is in that direction from here
266 new_name = old_place.get(direction)
267
268 # print an error if there is nothing in that direction
269 if not new_name:
270 error(f"Sorry, you can't go {direction} from here.")
271 return
272
273 # look up the place information
274 new_place = get_place(new_name)
275
276 # move the player to the new place
277 PLAYER["place"] = new_name
278
279 # print information about the new place
280 header(f"{new_place['name']}")
281 wrap(new_place["description"])
C. Modify do_look()
#
[ ]
Replace the existing value forplace
with a call toget_place()
.[ ]
Remove the line assigningplace_name
since that is taken care of inget_place()
[ ]
Replace the existing value fordestination
with a call toget_place()
with the argumentname
.
Code
159def do_look():
160 """Look at the current place"""
161
162 debug("Trying to look around.")
163
164 # look up where the player is now
165 place = get_place()
166
167 # print information about the current place
168 header(f"{place['name']}")
169 wrap(place["description"])
170
171 # get the items in the room
172 items = place.get("items", [])
173
174 if items:
175
176 # for each of the place items
177 # get the info from the ITEMS dictionary
178 # and make a list of item names
179 names = []
180 for key in items:
181 item = ITEMS.get(key)
182 names.append(item["name"])
183
184 # remove the last name from the list
185 last = names.pop()
186
187 # construct a sentence that looks like one of:
188 # x, x and y
189 # x and y
190 # y
191 text = ", ".join(names)
192 if text:
193 text += " and "
194 text += last
195
196 # print the list of items.
197 print()
198 wrap(f"You see {text}.\n")
199
200 # add a blank line
201 print()
202
203 # print what is in each direction from here
204 for direction in ("north", "east", "south", "west"):
205 name = place.get(direction)
206 if not name:
207 continue
208
209 destination = get_place(name)
210 write(f"To the {direction} is {destination['name']}.")
D. Modify do_take()
, do_examine()
and do_drop()
#
[ ]
Replace the existing value forplace
with a call toget_place()
.[ ]
Remove the line assigningplace_name
since that is taken care of inget_place()
do_take()
283def do_take(args):
284 """Pick up an item and add it to inventory."""
285 debug(f"Trying to take: {args}")
286
287 # make sure the player typed an item
288 if not args:
289 error("What do you want to take?")
290 return
291
292 # get the item name from arguments
293 # and make it lowercase
294 name = args[0].lower()
295
296 # look up where the player is now
297 place = get_place()
298
299 # make sure the item is in this place
300 if name not in place.get("items", []):
301 error(f"Sorry, I don't see a {name!r} here.")
302 return
303
304 # get the item information
305 item = ITEMS.get(name)
306
307 # make sure the item is in the ITEMS dictionary
308 if not item:
309 abort(f"Woops! The information about {name!r} seems to be missing.")
310
311 if not item.get("can_take"):
312 error(f"You try to pick up {name!r}, but you find you aren't able to lift it.")
313 return
314
315 # add to inventory
316 PLAYER["inventory"].setdefault(name, 0)
317 PLAYER["inventory"][name] += 1
318
319 # remove from place
320 place["items"].remove(name)
321
322 wrap(f"You pick up {item['name']} and put it in your pack.")
do_examine()
212def do_examine(args):
213 """Look at an item in the current place."""
214
215 debug(f"Trying to examine: {args}")
216
217 # make sure the player said what they want to examine
218 if not args:
219 error("What do you want to examine?")
220 return
221
222 # look up where the player is now
223 place = get_place()
224
225 # get the item entered by the user and make it lowercase
226 name = args[0].lower()
227
228 # make sure the item is in this place or in the players inventory
229 if not (name in place.get("items", []) or name in PLAYER["inventory"]):
230 error(f"Sorry, I don't know what this is: {name!r}.")
231 return
232
233 # make sure the item is in the ITEMS dictionary
234 if name not in ITEMS:
235 abort(f"Woops! The information about {name} seems to be missing.")
236
237 # get the item dictionary
238 item = ITEMS[name]
239
240 # print the item information
241 header(item["name"].title())
242 wrap(item["description"])
do_drop()
341def do_drop(args):
342 """Remove an item from inventory"""
343
344 debug(f"Trying to drop: {args}.")
345
346 # make sure the player typed an item
347 if not args:
348 error("What do you want to drop?")
349 return
350
351 # get the item name from arguments
352 # and make it lowercase
353 name = args[0].lower()
354
355 # make sure the item is in inventory
356 if name not in PLAYER["inventory"] or not PLAYER["inventory"][name]:
357 error(f"You don't have any {name!r}.")
358 return
359
360 # remove from inventory
361 PLAYER["inventory"][name] -= 1
362 if not PLAYER["inventory"][name]:
363 PLAYER["inventory"].pop(name)
364
365 # look up where the player is now
366 place = get_place()
367
368 # add to place items
369 place.setdefault("items", [])
370 place["items"].append(name)
371
372 # print a message
373 wrap(f"You set down the {name}.")
Part 9.3: Add get_item()
#
In this section we’ll be adding a get_item()
function which will be very
similar to get_place()
but for items instead of places.
A. Define get_item()
#
The get_item()
function will be almost exactly like get_place()
. It will
also take one argument, the key
to get from the ITEMS
dictionary, but it
will not be optional. We’ll call abort()
if the key
is not in the ITEMS
dictionary and finally return the item
otherwise.
[ ]
define aget_item()
function that takes one argumentkey
[ ]
use the.get()
method on theITEMS
dictionary to get the value associated from thekey
key and assign it to the variableitem
[ ]
Ifitem
is falsy,[ ]
Use theabort()
function to print an error message like:"Woops! The information about the item name seems to be missing."
[ ]
returnitem
Code
139def get_item(key):
140 """Return item information from ITEMS dictionary associated with key. If no
141 item is found, print an error message and return None."""
142 item = ITEMS.get(key)
143
144 if not item:
145 abort(f"Woops! The information about {key!r} seems to be missing.")
146
147 return item
B. Modify do_look()
and do_inventory()
: call get_item()
#
Throughout the rest of the program, you’ll replace anywhere that you get an
item from the ITEMS
dictionary using subscription or the .get()
method with a call to get_item()
.
[ ]
Callget_item()
with the argumentname
orkey
to get the value foritem
. This will replace the existingITEMS.get(key)
orITEMS[key]
.
do_look()
169def do_look():
170 """Look at the current place"""
171
172 debug("Trying to look around.")
173
174 # look up where the player is now
175 place = get_place()
176
177 # print information about the current place
178 header(f"{place['name']}")
179 wrap(place["description"])
180
181 # get the items in the room
182 items = place.get("items", [])
183
184 if items:
185
186 # for each of the place items
187 # get the info from the ITEMS dictionary
188 # and make a list of item names
189 names = []
190 for key in items:
191 item = get_item(key)
192 names.append(item["name"])
193
194 # remove the last name from the list
195 last = names.pop()
196
197 # construct a sentence that looks like one of:
198 # x, x and y
199 # x and y
200 # y
201 text = ", ".join(names)
202 if text:
203 text += " and "
204 text += last
205
206 # print the list of items.
207 print()
208 wrap(f"You see {text}.\n")
209
210 # add a blank line
211 print()
212
213 # print what is in each direction from here
214 for direction in ("north", "east", "south", "west"):
215 name = place.get(direction)
216 if not name:
217 continue
218
219 destination = get_place(name)
220 write(f"To the {direction} is {destination['name']}.")
do_inventory()
326def do_inventory():
327 """Show the players inventory"""
328
329 debug("Trying to show inventory.")
330
331 header("Inventory")
332
333 if not PLAYER["inventory"]:
334 write("Empty.")
335 return
336
337 for name, qty in PLAYER["inventory"].items():
338 item = get_item(name)
339 write(f"(x{qty:>2}) {item['name']}")
340
341 print()
C. Modify do_examine()
and do_take()
: call get_item()
#
In these functions we’ll do a similar replacement as above. Additionally, we’ll
remove error handling that is done in get_item()
.
[ ]
Callget_item()
with the argumentname
orkey
to get the value foritem
. This will replace the existingITEMS.get(key)
orITEMS[key]
.[ ]
Remove the lines that callsabort()
ifitem
is falsy or ifname
is not inITEMS
.
do_examine()
222def do_examine(args):
223 """Look at an item in the current place."""
224
225 debug(f"Trying to examine: {args}")
226
227 # make sure the player said what they want to examine
228 if not args:
229 error("What do you want to examine?")
230 return
231
232 # look up where the player is now
233 place = get_place()
234
235 # get the item entered by the user and make it lowercase
236 name = args[0].lower()
237
238 # make sure the item is in this place or in the players inventory
239 if not (name in place.get("items", []) or name in PLAYER["inventory"]):
240 error(f"Sorry, I don't know what this is: {name!r}.")
241 return
242
243 # get the item dictionary
244 item = get_item(name)
245
246 # print the item information
247 header(item["name"].title())
248 wrap(item["description"])
do_take()
289def do_take(args):
290 """Pick up an item and add it to inventory."""
291 debug(f"Trying to take: {args}")
292
293 # make sure the player typed an item
294 if not args:
295 error("What do you want to take?")
296 return
297
298 # get the item name from arguments
299 # and make it lowercase
300 name = args[0].lower()
301
302 # look up where the player is now
303 place = get_place()
304
305 # make sure the item is in this place
306 if name not in place.get("items", []):
307 error(f"Sorry, I don't see a {name!r} here.")
308 return
309
310 # get the item information
311 item = get_item(name)
312
313 if not item.get("can_take"):
314 error(f"You try to pick up {name!r}, but you find you aren't able to lift it.")
315 return
316
317 # add to inventory
318 PLAYER["inventory"].setdefault(name, 0)
319 PLAYER["inventory"][name] += 1
320
321 # remove from place
322 place["items"].remove(name)
323
324 wrap(f"You pick up {item['name']} and put it in your pack.")
Part 9.4: Validation functions#
In this section we’ll be several functions return True
or False
so that
they can be used for things we commonly need to check. Specifically the
functions player_has()
, place_has()
, and is_for_sale()
.
A. Define player_has()
#
The player_has()
function will return True
if the player has a particular
item in inventory.
[ ]
define aplayer_has()
function that takes one argumentkey
, and an optional argumentqty
with a default value of1
[ ]
Check if thekey
is in the players inventory (stored in thePLAYER
dict with the"inventory"
key), and if so, if the value is greater than or equal toqty
.[ ]
If so, returnTrue
[ ]
If not, returnFalse
Code
151def player_has(key, qty=1):
152 """Return True if the player has at least qty item(s) associated with key in
153 their inventory."""
154 return key in PLAYER["inventory"] and PLAYER["inventory"][key] >= qty
B. Modify do_drop()
: call player_has()
#
Now in our if statements where we check the same thing we can replace it with a
call to player_has()
.
[ ]
Find the if statement where we check to see if the item is in not inventory.[ ]
Replace the part of the condition that checks with a call toplayer_has()
and pass the argumentname()
. (Be sure to keep thenot
.)
Code
359def do_drop(args):
360 """Remove an item from inventory"""
361
362 debug(f"Trying to drop: {args}.")
363
364 # make sure the player typed an item
365 if not args:
366 error("What do you want to drop?")
367 return
368
369 # get the item name from arguments
370 # and make it lowercase
371 name = args[0].lower()
372
373 # make sure the item is in inventory
374 if not player_has(name):
375 error(f"You don't have any {name!r}.")
376 return
377
378 # remove from player inventory
379 PLAYER["inventory"][name] -= 1
380 if not PLAYER["inventory"][name]:
381 PLAYER["inventory"].pop(name)
382
383 # look up where the player is now
384 place = get_place()
385
386 # add to place items
387 place.setdefault("items", [])
388 place["items"].append(name)
389
390 # print a message
391 wrap(f"You set down the {name}.")
C. Call player_has()
from do_examine()
#
[ ]
Find the if statement where we check to see if the item is not in either the current place or the inventory.[ ]
Replace the part of the condition that checks with a call toplayer_has()
and pass the argumentname()
. (Be sure to keep thenot
, as well as the part that checks if the item is in the current place.)
Code
238def do_examine(args):
239 """Look at an item in the current place."""
240
241 debug(f"Trying to examine: {args}")
242
243 # make sure the player said what they want to examine
244 if not args:
245 error("What do you want to examine?")
246 return
247
248 # look up where the player is now
249 place = get_place()
250
251 # get the item entered by the user and make it lowercase
252 name = args[0].lower()
253
254 # make sure the item is in this place or in the players inventory
255 if not (place_has(name) or player_has(name)):
256 error(f"Sorry, I don't know what this is: {name!r}.")
257 return
258
259 # get the item dictionary
260 item = get_item(name)
261
262 # print the item information
263 header(item["name"].title())
264 wrap(item["description"])
D. Define place_has()
#
The place_has()
function will return True
if the place has a particular
item.
[ ]
define aplace_has()
function that takes one argumentitem
.[ ]
In the function get the current place by callingget_place()
assign it to the variableplace
.[ ]
Check if theitem
is in the list of items in the current place by using the.get()
method onplace
with the key"items"
.[ ]
If so, returnTrue
[ ]
If not, returnFalse
Code
156def place_has(item):
157 """Return True if current place has a particular item."""
158 place = get_place()
159 return item in place.get("items", [])
E. Call place_has()
from do_take()
#
Now in our if statements where we check the same thing we can replace it with a
call to place_has()
.
[ ]
Find the if statement where we check to see if the item is not in the current place.[ ]
Replace the part of the condition that checks with a call toplace_has()
and pass the argumentname()
. (Be sure to keep thenot
.)
Code
305def do_take(args):
306 """Pick up an item and add it to inventory."""
307 debug(f"Trying to take: {args}")
308
309 # make sure the player typed an item
310 if not args:
311 error("What do you want to take?")
312 return
313
314 # get the item name from arguments
315 # and make it lowercase
316 name = args[0].lower()
317
318 # look up where the player is now
319 place = get_place()
320
321 # make sure the item is in this place
322 if not place_has(name):
323 error(f"Sorry, I don't see a {name!r} here.")
324 return
325
326 # get the item information
327 item = get_item(name)
328
329 if not item.get("can_take"):
330 error(f"You try to pick up {name!r}, but you find you aren't able to lift it.")
331 return
332
333 # add to inventory
334 PLAYER["inventory"].setdefault(name, 0)
335 PLAYER["inventory"][name] += 1
336
337 # remove from place
338 place["items"].remove(name)
339
340 wrap(f"You pick up {item['name']} and put it in your pack.")
F. Call place_has()
from do_examine()
#
Now in our if statements where we check the same thing we can replace it with a
call to place_has()
.
[ ]
Find the if statement where we check to see if the item is not in the current place.[ ]
Replace the part of the condition that checks with a call toplace_has()
and pass the argumentname
. (Be sure to keep thenot
as well as the part that checks if the player has the item.)
Code
238def do_examine(args):
239 """Look at an item in the current place."""
240
241 debug(f"Trying to examine: {args}")
242
243 # make sure the player said what they want to examine
244 if not args:
245 error("What do you want to examine?")
246 return
247
248 # look up where the player is now
249 place = get_place()
250
251 # get the item entered by the user and make it lowercase
252 name = args[0].lower()
253
254 # make sure the item is in this place or in the players inventory
255 if not (place_has(name) or player_has(name)):
256 error(f"Sorry, I don't know what this is: {name!r}.")
257 return
258
259 # get the item dictionary
260 item = get_item(name)
261
262 # print the item information
263 header(item["name"].title())
264 wrap(item["description"])
G. Define is_for_sale()
#
The is_for_sale()
function will return True
if an item is for sale.
[ ]
Define ais_for_sale()
function that takes one argument,item
.[ ]
Check if the"price"
key is in theitem
dictionary.[ ]
If so, returnTrue
[ ]
If not, returnFalse
Code
161def is_for_sale(item):
162 """Return True if item is for sale (has a price)."""
163 return "price" in item
H. Call is_for_sale()
from do_shop()
#
Now in our if statements where we check the same thing we can replace it with a
call to is_for_sale()
.
[ ]
Find the if statement where we check to see if the"price"
key is in anitem
dictionary[ ]
Replace the part of the condition that checks with a call tois_for_sale()
and pass the argumentitem
. (Be sure to keep thenot
.)
Code
167def do_shop():
168 """List the items for sale."""
169
170 header("Items for sale.")
171
172 for item in ITEMS.values():
173 if not is_for_sale(item):
174 continue
175
176 write(f'{item["name"]:<13} {item["description"]}')
177
178 print()
Part 9.5: Add inventory_change()
#
In this section we’ll be adding an inventory_change()
function that will add
or remove items from the players inventory.
A. Define inventory_change()
#
The inventory_change()
function will handle either adding items to a player’s
inventory or removing from it. We’ll use an optional argument quantity
for
the number to add. (If the number is negative, then it the quantity will be
subtracted.)
In this function we’ll use the .setdefault()
method on the inventory
dictionary. This is kind of like the inverse of the .get()
method–if the key
is not currently in the dictionary, it will set it default
value. Otherwise,
nothing will happen.2
Once we are done changing the quantity in inventory, we’ll remove the entire
item from dictionary if there is 0
(or less) of them left. This way items
with a zero quantity won’t show up in do_inventory()
.
[ ]
Define ainventory_change()
function that takes one argumentkey
, and an optional argumentquantity
with a default value of1
[ ]
Call the.setdefault()
method on the players inventory (accessed in thePLAYER
dict with the"inventory"
key) with the argumentskey
and0
for the default.[ ]
Addquantity
to the players inventory (accessed in thePLAYER
dict with the"inventory"
key) for thekey
key.Hint: Use the
+=
operator.[ ]
Check if the value associated withkey
in the players inventory is falsy, or if it is less than or equal to zero.[ ]
If so, remove that key from the player’s inventory by calling the.pop()
method on the inventory dictionary with thekey
argument.
Code
150def inventory_change(key, quantity=1):
151 """Add item to player inventory."""
152 PLAYER["inventory"].setdefault(key, 0)
153 PLAYER["inventory"][key] += quantity
154
155 # remove from inventory dictionary if quantity is zero
156 if not PLAYER["inventory"][key]:
157 PLAYER["inventory"].pop(key)
B. Call inventory_change()
in do_take()
#
Now we can call inventory_change()
anytime we want to add or remove something
from inventory. Let’s start in the do_take()
function. Since we currently can
only have one of something in a room at a time, we won’t pass the quantity
argument, so it will default to 1
.
[ ]
Find where you add the item to the player’s inventory. Replace those lines with a call toinventory_change()
and pass thename
argument.
Code
315def do_take(args):
316 """Pick up an item and add it to inventory."""
317 debug(f"Trying to take: {args}")
318
319 # make sure the player typed an item
320 if not args:
321 error("What do you want to take?")
322 return
323
324 # get the item name from arguments
325 # and make it lowercase
326 name = args[0].lower()
327
328 # look up where the player is now
329 place = get_place()
330
331 # make sure the item is in this place
332 if not place_has(name):
333 error(f"Sorry, I don't see a {name!r} here.")
334 return
335
336 # get the item information
337 item = get_item(name)
338
339 if not item.get("can_take"):
340 error(f"You try to pick up {name!r}, but you find you aren't able to lift it.")
341 return
342
343 # add to inventory
344 inventory_change(name)
345
346 # remove from place
347 place["items"].remove(name)
348
349 wrap(f"You pick up {item['name']} and put it in your pack.")
C. Call inventory_change()
in do_drop()
#
Next, we’ll call it from do_drop()
.
Here we’ll be subtracting from inventory by passing in a negative value for
quantity by putting a -
in front of the value. Then when the negative number
is added to the current inventory value in inventory_change()
, it will be the
same as if we had subtracted a positive number.
We run into a little issue, since we can have more than one of something in the
player’s inventory, but only one of something in a place. To account for this,
we’ll zero out that item in inventory and only add one item to place. (Someday
we may want to make the place "items"
a dictionary instead of a list so we
can have more than one of a thing in a particular place, but this will have to
do for now.)
[ ]
Find where you remove the item from the player’s inventory. Right above that, get the amount the player has in their inventory (stored in thePLAYER
dict with the"inventory"
key) using thename
key and assign it to theqty
variable.[ ]
Replace the lines where you add to the inventory with a call toinventory_change()
with the argumentsname
and-qty
.
Code
368def do_drop(args):
369 """Remove an item from inventory"""
370
371 debug(f"Trying to drop: {args}.")
372
373 # make sure the player typed an item
374 if not args:
375 error("What do you want to drop?")
376 return
377
378 # get the item name from arguments
379 # and make it lowercase
380 name = args[0].lower()
381
382 # make sure the item is in inventory
383 if not player_has(name):
384 error(f"You don't have any {name!r}.")
385 return
386
387 # get the amount currently in inventory
388 qty = PLAYER["inventory"][name]
389
390 # remove from player inventory
391 inventory_change(name, -qty)
392
393 # look up where the player is now
394 place = get_place()
395
396 # add to place items
397 place.setdefault("items", [])
398 place["items"].append(name)
399
400 # print a message
401 wrap(f"You set down the {name}.")
Part 9.6: Add place_add()
#
In this section we’ll be adding an place_add()
function that will add
an item to the current place.
A. Define place_add()
#
The place_add()
function will take care of looking up the current place,
making sure that the places "items"
dictionary is set to an empty list if
it’s missing, making sure that it’s not already in the place, and finally
adding the item key to the place list.
[ ]
Define aplace_add()
function that takes one argumentkey
.[ ]
Get the current place by callingget_place()
and assign it to the variableplace
.[ ]
Call.setdefault()
onplace
with the arguments"items"
and an empty list.[ ]
Check ifkey
is in theplace["items"]
list[ ]
If not, appendkey
to theplace["items"]
dict
Code
159def place_add(key):
160 """Add an item to the current place."""
161 # get the current place
162 place = get_place()
163
164 # add the item key to the place items list
165 place.setdefault("items", [])
166 if key not in place["items"]:
167 place["items"].append(key)
B. Call place_add()
in do_drop()
#
Now we can call place_add()
anytime we want to add something to a place.
Right now, this only happens in the do_drop()
function.
[ ]
Find where you add the item to the place. Replace those lines with a call toplace_add()
and pass thename
argument.[ ]
You can also remove the line where you get the current place using theget_place()
function.
Code
378def do_drop(args):
379 """Remove an item from inventory"""
380
381 debug(f"Trying to drop: {args}.")
382
383 # make sure the player typed an item
384 if not args:
385 error("What do you want to drop?")
386 return
387
388 # get the item name from arguments
389 # and make it lowercase
390 name = args[0].lower()
391
392 # make sure the item is in inventory
393 if not player_has(name):
394 error(f"You don't have any {name!r}.")
395 return
396
397 # get the amount currently in inventory
398 qty = PLAYER["inventory"][name]
399
400 # remove from player inventory
401 inventory_change(name, -qty)
402
403 # add to place items
404 place_add(name)
405
406 # print a message
407 wrap(f"You set down the {name}.")
Part 9.7: Add place_remove()
#
In this section we’ll be adding an place_remove()
function that will remove
an item from the current place.
A. Define place_remove()
#
The place_remove()
function will take care of looking up the current place,
making sure that the item is in the place, and finally removing the item key
from the place list.
[ ]
Define aplace_remove()
function that takes one argumentkey
.[ ]
Get the current place by callingget_place()
and assign it to the variableplace
.[ ]
Check ifkey
is in the current place by calling.get()
onplace
and passing the argumentskey
and an empty list for the default value.[ ]
If so, call.remove()
onplace["items"]
with thekey
argument
Code
169def place_remove(key):
170 """Remove an item from the current place."""
171 # get the current place
172 place = get_place()
173
174 # remove from place
175 if key in place.get("items", []):
176 place["items"].remove(key)
B. Call place_remove()
in do_take()
#
Now we can call place_remove()
anytime we want to remove something from a
place. Right now, this only happens in the do_take()
function.
[ ]
Find where you remove the item from the place. Replace those lines with a call toplace_remove()
and pass thename
argument.[ ]
You can also remove the line where you get the current place using theget_place()
function.
Code
334def do_take(args):
335 """Pick up an item and add it to inventory."""
336 debug(f"Trying to take: {args}")
337
338 # make sure the player typed an item
339 if not args:
340 error("What do you want to take?")
341 return
342
343 # get the item name from arguments
344 # and make it lowercase
345 name = args[0].lower()
346
347 # make sure the item is in this place
348 if not place_has(name):
349 error(f"Sorry, I don't see a {name!r} here.")
350 return
351
352 # get the item information
353 item = get_item(name)
354
355 if not item.get("can_take"):
356 error(f"You try to pick up {name!r}, but you find you aren't able to lift it.")
357 return
358
359 # add to inventory
360 inventory_change(name)
361
362 # remove from place
363 place_remove(name)
364
365 wrap(f"You pick up {item['name']} and put it in your pack.")