Part 12: Read things
Contents
Part 12: Read things#
In this section we’ll add the read command, which the player can use to read a clue from the book item.
In this section we’ll also start following an approach to coding called TDD, or Test-Driven Development. When following this process, you write your test first, then write the code that makes it pass.
This technique has many advantages. You are forced to be very deliberate about exactly what you are trying to accomplish, which tends to lead to clearer thinking and cleaner code. You can be more confident that your code is working as intended and that it won’t break in the future without you noticing.
Part 12.1: Add command#
In this section we’ll be adding the read command.
A. In test_game.py
define test_do_read()
#
First we’ll write the test, which we expect to fail.
Demo
[ ]
Import thedo_read
function[ ]
Addtest_do_read()
function with one parametercapsys
[ ]
Calldo_read()
with an empty list as an argument[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
[ ]
Write an assert statement that checks that the debug message"Trying to read: []"
is inoutput
[ ]
Run your tests, either at the command line or in VS Code. Since we haven’t writtendo_read()
yet, you will get an error.
Code
1from copy import deepcopy
2
3import pytest
4
5import adventure
6from adventure import (
7 debug,
8 do_drop,
9 do_read,
10 error,
11 header,
12 inventory_change,
13 is_for_sale,
14 place_has,
15 player_has,
16 write,
17)
18
19
170def test_do_read(capsys):
171 do_read([])
172
173 output = capsys.readouterr().out
174
175 assert "Trying to read: []" in output, \
176 "Debug message should be in output"
B. In adventure.py
define do_read()
#
Now we’ll write the code to make it work.
Demo
[ ]
Add ado_read()
function with one parameterargs
[ ]
In it, use thedebug()
function to print something like"Trying to read args."
.[ ]
Run your test again. It should now pass.
Code
522def do_read(args):
523 """Read an item with a message."""
524
525 debug(f"Trying to read: {args}")
526
C. In adventure.py
modify main()
#
Finally, we’ll add the code to make the command work in the game.
[ ]
Add anelif
that checks ifcommand
is"read"
.[ ]
if so, calldo_read()
and passargs
.
Demo
Code
527def main():
528 header("Welcome!")
529
530 while True:
531 debug(f"You are at: {PLAYER['place']}")
532
533 reply = input(fg.cyan("> ")).strip()
534 args = reply.split()
535
536 if not args:
537 continue
538
539 command = args.pop(0)
540 debug(f"Command: {command!r}, args: {args!r}")
541
542 if command in ("q", "quit", "exit"):
543 do_quit()
544
545 elif command in ("shop"):
546 do_shop()
547
548 elif command in ("g", "go"):
549 do_go(args)
550
551 elif command in ("x", "exam", "examine"):
552 do_examine(args)
553
554 elif command in ("l", "look"):
555 do_look()
556
557 elif command in ("t", "take", "grab"):
558 do_take(args)
559
560 elif command in ("i", "inventory"):
561 do_inventory()
562
563 elif command in ("r", "read"):
564 do_read(args)
565
566 elif command == "drop":
567 do_drop(args)
568
569 elif command == "buy":
570 do_buy(args)
571
572 else:
573 error("No such command.")
574 continue
575
576 # print a blank line no matter what
577 print()
578
Part 12.2: Ensure item#
In this section we’ll make sure that the player entered the item that they want to read.
Demo
A. In test_game.py
modify test_do_read()
#
Now we’ll add an assertion to check the output for an error message.
[ ]
Change the name oftest_do_read()
totest_do_read_no_args()
[ ]
Add an assertion that checks if"Error What do you want to read?"
is inoutput
[ ]
Run your test. It should fail.
Demo
Code
170def test_do_read_no_args(capsys):
171 do_read([])
172
173 output = capsys.readouterr().out
174
175 assert "Trying to read: []" in output, \
176 "Debug message should be in output"
177
178 assert "Error What do you want to read?" in output, \
179 "User error should be in output"
B. In adventure.py
modify do_read()
#
Now we’ll write the code to make our test pass.
[ ]
Check ifargs
is falsy. If it is, use theerror()
function to print an error that says:"What do you want to read?"
thenreturn
[ ]
Run your test again. It should pass.
Demo
Code
522def do_read(args):
523 """Read an item with a message."""
524
525 debug(f"Trying to read: {args}")
526
527 # make sure the player said what they want to read
528 if not args:
529 error("What do you want to read?")
530 return
531
Part 12.3: Ensure item is here#
In this section we’ll make sure the item that the player typed is either in this place or in inventory.
Demo
A. In test_game.py
define test_do_read_missing_item()
#
In this section we’ll write a new test to make sure that the item is either in this place or in inventory.
Demo
[ ]
Addtest_do_read_missing_item()
function with the parametercapsys
[ ]
Calldo_read()
with a list and a any string that is not an item key for an argument[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
[ ]
Write an assert statement that checks that the debug message"Trying to read: []"
is inoutput
[ ]
Write an assert statement that checks that the message"Sorry, I don't know what this is'"
is inoutput
[ ]
Run your test. It should fail.
Code
181def test_do_read_missing_item(capsys):
182 do_read(["missing"])
183
184 output = capsys.readouterr().out
185
186 assert "Trying to read: ['missing']" in output, \
187 "Debug message should be in output"
188
189 assert "Error Sorry, I don't know what this is: 'missing'" in output, \
190 "User error should be in output"
B. In adventure.py
modify do_read()
#
Now we’ll add the code to make the test pass.
Demo
[ ]
assign the first item of theargs
list to the variablename
and make it lowercase[ ]
Write an if statement that checks if either the place or player has the item. If not:[ ]
Use theerror()
function to print a message like:"Sorry, I don't know what this is: name'"
[ ]
return
[ ]
Run your test again. It should pass.
Code
522def do_read(args):
523 """Read an item with a message."""
524
525 debug(f"Trying to read: {args}")
526
527 # make sure the player said what they want to read
528 if not args:
529 error("What do you want to read?")
530 return
531
532 # get the item entered by the user and make it lowercase
533 name = args[0].lower()
534
535 # make sure the item is in this place or in the players inventory
536 if not (place_has(name) or player_has(name)):
537 error(f"Sorry, I don't know what this is: {name!r}.")
538 return
539
Part 12.4: Ensure item is readable#
In this section we’ll make sure that the item the player wants to be read can, in fact, be read.
Demo
A. In test_game.py
define test_do_read_unreadable_item()
#
In this section we’ll be adding a test for when the player tries to read an item that cannot be read. It should:
[ ]
Add a fake unreadable item dictionary to the current place. An item is readable if it has a"message"
key, so for a fake item all we really need is an empty dictionary.[ ]
Calldo_read()
with the arguments that would be passed to it if the player had typed"read FAKE_THING"
.[ ]
Check the output for the appropriate error message
Demo
[ ]
import theplace_add
function[ ]
Addtest_do_read_unreadable_item()
function with the parametercapsys
[ ]
Add a fake item toadventure.ITEMS
with the key of your choice[ ]
Use theplace_add()
function to add your fake item to the current place[ ]
Calldo_read()
with a list containing your new key as the argument[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
[ ]
Write an assert statement that checks that the message"Sorry, I don't can't read 'key'"
is inoutput
[ ]
Run your test. It should fail.
Code
193def test_do_read_unreadable_item(capsys):
194 adventure.ITEMS["your mind"] = {"name": "Your mind"}
195 place_add("your mind")
196
197 do_read(["your mind"])
198 output = capsys.readouterr().out
199
200 assert "Error Sorry, I can't read 'your mind'" in output, \
201 "User error should be in output"
B. In adventure.py
modify do_read()
#
Now we’ll write the code to make the test pass.
Demo
[ ]
Use theget_item()
function to get the item dictionary and assign it to the variableitem
[ ]
Check if the"message"
key is in the item dictionary. If not:[ ]
Use theerror()
function to print an error message like:"Sorry, I can't read 'name'"
[ ]
return
[ ]
Run your test again. It should pass.
Code
522def do_read(args):
523 """Read an item with a message."""
524
525 debug(f"Trying to read: {args}")
526
527 # make sure the player said what they want to read
528 if not args:
529 error("What do you want to read?")
530 return
531
532 # get the item entered by the user and make it lowercase
533 name = args[0].lower()
534
535 # make sure the item is in this place or in the players inventory
536 if not (place_has(name) or player_has(name)):
537 error(f"Sorry, I don't know what this is: {name!r}.")
538 return
539
540 # get the item dictionary
541 item = get_item(name)
542
543 # make sure it is an item you can read
544 if "writing" not in item:
545 error(f"Sorry, I can't read {name!r}.")
546 return
547
Part 12.5: Read things#
In this section we’ll finally provide a readable item and the read
command
will read it.
A. In test_game.py
define test_do_read_in_place()
#
In this section we’ll write a new test where we’ll set up a fake item with
"message"
and "title"
keys containing the text to be read and add it to the
current place. Then we’ll call do_read()
and check for the expected output.
Demo
[ ]
Add a functiontest_do_read_in_place()
[ ]
Create a dictionary representing a fake item item with the keys"title"
and"message"
and whatever text you’d like for the values.[ ]
Use theplace_add()
function to add it to the current place[ ]
Calldo_read()
with the key for your fake item[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
[ ]
Write an assert statement that the title is inoutput
[ ]
Write an assert statement that the message is inoutput
[ ]
Run your test. It should fail.
Code
204def test_do_read_in_place(capsys):
205 title = "After studying your palm I see..."
206 message = "The break in your line of fate may indicate a change in location or career"
207
208 adventure.ITEMS["your palm"] = {
209 "name": "Your palm",
210 "writing": {
211 "title": title,
212 "message": message,
213 },
214 }
215
216 place_add("your palm")
217
218 do_read(["your palm"])
219 output = capsys.readouterr().out
220
221 assert "After studying your palm I see..." in output, \
222 "The writing title {title!r} should be in output"
223
224 assert "The break in your line of fate" in output, \
225 "The writing message {message!r} should be in output"
226
227
B. In adventure.py
modify do_read()
#
Now we’ll write the code to actually read the item.
Demo
[ ]
If the"title"
key exists in theitem
dictionary, Use theheader()
function to print that value. Otherwise print something like"It reads..."
.[ ]
Use thewrap()
function to print the"message"
value from theitem
dictionary.
Code
539def do_read(args):
540 """Read an item with a message."""
541
542 debug(f"Trying to read: {args}")
543
544 # make sure the player said what they want to read
545 if not args:
546 error("What do you want to read?")
547 return
548
549 # get the item entered by the user and make it lowercase
550 name = args[0].lower()
551
552 # make sure the item is in this place or in the players inventory
553 if not (place_has(name) or player_has(name)):
554 error(f"Sorry, I don't know what this is: {name!r}.")
555 return
556
557 # get the item dictionary
558 item = get_item(name)
559
560 # make sure it is an item you can read
561 if "writing" not in item:
562 error(f"Sorry, I can't read {name!r}.")
563 return
564
565 # get the information to read
566 writing = item["writing"]
567
568 # print the item header
569 header(writing["title"])
570
571 # print the quantity message
572 wrap(writing.get("message"))
573
C. Modify ITEMS
: add to "book"
#
Now we’ll finally give the player something to read.
[ ]
In the"book"
dictionary inITEMS
, add the key"title"
with whatever text you’d like[ ]
In the"book"
dictionary inITEMS
, add the key"message"
with whatever text you’d like
Demo
Code
94 "book": {
95 "key": "book",
96 "can_take": True,
97 "name": "a book",
98 "description": (
99 "A hefty leather-bound tome open to an interesting passage."
100 ),
101 "writing": {
102 "title": (
103 "The book is open to a page that reads:"
104 ),
105 "message": (
106 "At the edge of the woods is a cave that is home to a three "
107 "headed dragon, each with a different temperament. "
108
109 "Legend says that if you happen upon the dragon sleeping, the "
110 "brave may pet one of its three heads. "
111
112 "Choose the right head and you will be rewarded with great "
113 "fortunes. "
114
115 "But beware, choose poorly and it will surely mean your doom! "
116 ),
117 },
118 },
D. In test_game.py
define test_do_read_in_inventory()
#
Can you write a test_do_read_in_inventory()
function on your own, to test that
you can read a book if it is in your inventory but not in the current place?
Code
228def test_do_read_in_inventory(capsys):
229 title = "After studying the leaves I see..."
230 message = "Your future is uncertain."
231
232 adventure.ITEMS["tea leaves"] = {
233 "name": "Tea Leaves",
234 "writing": {
235 "title": title,
236 "message": message,
237 },
238 }
239
240 inventory_change("tea leaves")
241
242 do_read(["tea leaves"])
243 output = capsys.readouterr().out
244
245 assert "After studying the leaves I see..." in output, \
246 "The writing title {title!r} should be in output"
247
248 assert "Your future is uncertain" in output, \
249 "The writing message {message!r} should be in output"
Part 12.6: Indent message#
In this section we’re going to indent the message part of the text an extra level.
Demo
A. In test_game.py
modify test_do_read_in_inventory()
#
In this section we’re going to modify our test_do_read_in_inventory()
function to test that the message has been indented an extra level.
[ ]
Call.splitlines()
onoutput
and assign it to the variablelines
[ ]
Write anassert
statement that checks if the last item inlines
equals your fake items message with 4 spaces at the beginning. (Assuming your message is short enough.)[ ]
Run your test. It should fail.
Demo
Tip
When you run pytest
at the command line, you can run a single test by adding
::test_name
after the filename. For example, here I used pytest test_game.py::test_do_read_in_inventory
.
Code
278def test_do_read_in_inventory(capsys):
279 title = "After studying the leaves I see..."
280 message = "Your future is uncertain."
281
282 adventure.ITEMS["tea leaves"] = {
283 "name": "Tea Leaves",
284 "writing": {
285 "title": title,
286 "message": message,
287 },
288 }
289
290 inventory_change("tea leaves")
291
292 do_read(["tea leaves"])
293 output = capsys.readouterr().out
294 lines = output.splitlines()
295
296 assert "After studying the leaves I see..." in output, \
297 "The writing title {title!r} should be in output"
298
299 assert lines[-1] == " Your future is uncertain.", \
300 "The writing message {message!r} should be indented an extra level."
B. In test_game.py
define test_wrap()
#
Before we change any code in do_read()
, we’re actually going to modify the
wrap()
function to optionally add indentation. And since we don’t have any
tests for wrap()
yet, we’ll one before we change it.
Since we’re testing pre-existing behavior here, this is one we’ll expect to pass.
Demo
[ ]
Add a functiontest_wrap()
that takes one parametercapsys
[ ]
Callwrap()
with a string that is longer than theWIDTH
in your game.[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
[ ]
Call.splitlines()
onoutput
and assign it to the variablelines
[ ]
Write anassert
statement that tests that the length oflines
is greater than1
[ ]
Write anassert
statement that tests thatoutput
contains the first few words of the text argument.[ ]
Write anassert
statement that tests thatoutput
ends with the last few words of the text argument, followed by a newline.[ ]
Make sure that each string inlines
starts with two, and no more than two, spaces.[ ]
Run your test. It should pass.
Code
92def test_wrap(capsys):
93 wrap(
94 "I pass through many Me's in the course of my day, "
95 "each one selfish with his time. The Lying-in-Bed "
96 "me and the Enjoying-the-Hot-Shower Me are particularly "
97 "selfish. The Late Me loathes the pair of them."
98 )
99 output = capsys.readouterr().out
100 lines = output.splitlines()
101
102 assert "I pass through" in output, \
103 "wrap() should print the text indented."
104
105 assert output.endswith("pair of them.\n"), \
106 "wrap() should print the text"
107
108 assert len(lines) > 1, "wrap() should break long text onto multiple lines"
109
110 assert all([line.startswith(" ") for line in lines]), \
111 "wrap() should indent all lines"
112
113 assert all([line[2] != " " for line in lines]), \
114 "wrap() should indent all lines 2 spaces"
115
C. In test_game.py
define test_wrap_with_indent()
#
Now that we have the normal wrap()
behavior tested, we’ll add a (failing)
test for the new behavior with the optional indent
parameter we will be
adding.
This is going to be nearly identical to the test_wrap()
function, except
we’ll be adding the keyword argument indent=2
when we call wrap()
, then
testing that the lines are indented to 4
spaces instead of 2
.
Demo
[ ]
Add a functiontest_wrap_with_indent()
that takes one parametercapsys
[ ]
Callwrap()
with a string that is longer than theWIDTH
in your game, followed by a keyword argumentindent
with a value of2
.[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
[ ]
Call.splitlines()
onoutput
and assign it to the variablelines
[ ]
Write anassert
statement that tests that the length oflines
is greater than1
[ ]
Write anassert
statement that tests thatoutput
contains the first few words of the text argument.[ ]
Write anassert
statement that tests thatoutput
ends with the last few words of the text argument, followed by a newline.[ ]
Make sure that each string inlines
starts with four, and no more than four, spaces.[ ]
Run your test. (Or however many extra spaces you would like.) It should fail.
Code
116def test_wrap_with_indent(capsys):
117 wrap(
118 "To the absolutist in every craftsman, "
119 "each imperfection is a failure; "
120 "to the practitioner, obsession with "
121 "perfection seems a perception for failure.",
122 indent=2,
123 )
124 output = capsys.readouterr().out
125 lines = output.splitlines()
126
127 assert "To the absolutist" in output, \
128 "wrap() should print the text."
129
130 assert output.endswith("perception for failure.\n"), \
131 "wrap() should print the text"
132
133 assert len(lines) > 1, "wrap() should break long text onto multiple lines"
134
135 assert all([line.startswith(" ") for line in lines]), \
136 "wrap() should indent all lines"
137
138 assert all([line[4] != " " for line in lines]), \
139 "wrap() should indent all lines 4 spaces"
140
D. In adventure.py
modify wrap()
#
We’re finally ready to modify the wrap()
function to handle indention for us.
[ ]
Add an optionalindent
parameter towrap
with a default value of1
[ ]
When calculatingmargin
, multiply the existing value byindent
[ ]
Run yourtest_wrap_with_indent()
test. It should pass.[ ]
Run yourtest_wrap()
test. It should pass.
Demo
Tip
When you run pytest
at the command line, you can use the
-k KEYWORD
flag to run all tests with names that contain
KEYWORD
. For example, here I use pytest test_game.py -k test_wrap
to
run both test_wrap()
and test_wrap_with_indent()
, but no other tests.
Code
136def wrap(text, indent=1):
137 """Print wrapped and indented text."""
138
139 # calculate the indentation
140 margin = (MARGIN * " ") * indent
141
142 # wrap the text
143 paragraph = textwrap.fill(
144 text,
145 WIDTH,
146 initial_indent=margin,
147 subsequent_indent=margin,
148 )
149
150 # print the wrapped text
151 print(paragraph)
152
E. In adventure.py
modify do_read()
#
Now we’re finally ready to modify our do_read()
function to use the new
indent
keyword argument in wrap()
.
[ ]
When you print the item message by callingwrap()
add a keyword argumentindent
with a value of2
. (Or more, to your taste. Just be sure you update your test.)[ ]
Run yourtest_do_read_in_inventory
test. It should pass.[ ]
Run all your tests. They should pass.
Demo
Code
541def do_read(args):
542 """Read an item with a message."""
543
544 debug(f"Trying to read: {args}")
545
546 # make sure the player said what they want to read
547 if not args:
548 error("What do you want to read?")
549 return
550
551 # get the item entered by the user and make it lowercase
552 name = args[0].lower()
553
554 # make sure the item is in this place or in the players inventory
555 if not (place_has(name) or player_has(name)):
556 error(f"Sorry, I don't know what this is: {name!r}.")
557 return
558
559 # get the item dictionary
560 item = get_item(name)
561
562 # make sure it is an item you can read
563 if "writing" not in item:
564 error(f"Sorry, I can't read {name!r}.")
565 return
566
567 # get the information to read
568 writing = item["writing"]
569
570 # print the item header
571 header(writing["title"])
572
573 # print the item message
574 wrap(writing.get("message"), indent=3)
575
Part 12.7: Allow for stanzas#
In this section we’ll add functionality to break up a long message (in
particular, our book
message) into multiple stanzas.
To accomplish this, we’ll modify wrap()
so that for its text
argument it can
take either a string or an iterable (a tuple or a list) of strings. If text
is a string, it should print just the same as it does now. If text
is a
tuple
or a list
, each item should be wrapped separately and printed with a
blank line between each one.
Demo
A. In test_game.py
modify test_do_read_in_place()
#
In this section we’ll modify the test_do_read_in_place()
test so that the
"message"
in our fake item is either a tuple or a list, where each item
represents a stanza. Then we’ll add an assert statement to check that there are
two "\n"
before one of our stanzas.
We’ll leave the test_do_read_in_inventory()
test alone, which will make sure
that it still works if message is a string.
Demo
[ ]
Modify the value corresponding to the"message"
key in your fake item dictionary to be either a tuple or a list of strings with multiple items.[ ]
Add anassert
statement that checks to make sure that output contains two blank lines, followed by the number of indentation spaces, followed by the first few words of one of your message items.[ ]
Run the test. It should fail.
Code
278def test_do_read_in_place(capsys):
279 title = "After studying your palm I see..."
280 message = (
281 "The break in your line of fate may indicate "
282 "a change in location or career.",
283
284 "You have more than one life line, which may "
285 "indicate you are a cat",
286 )
287
288 adventure.ITEMS["your palm"] = {
289 "name": "Your palm",
290 "title": title,
291 "message": message,
292 }
293
294 place_add("your palm")
295
296 do_read(["your palm"])
297 output = capsys.readouterr().out
298
299 assert "After studying your palm I see..." in output, \
300 "The writing title {title!r} should be in output"
301
302 assert "The break in your line of fate" in output, \
303 "The writing message {message!r} should be in output"
304
305 assert "\n\n You have more than" in output, \
306 "When message is an iterable, there should be two lines between each item"
307
308
B. In test_game.py
define test_wrap_with_iterable()
#
Once again, the real heavy lifting will be done in the wrap()
function. So
before we write any code we’ll write a new wrap()
test for when text
is an
iterable.
Demo
[ ]
Add a functiontest_wrap_with_iterable()
with one parametercapsys
[ ]
Set the variablemessage
to a list or a tuple of strings. (Consider using a few lyrics of a short song or rhyme.)[ ]
Call thewrap()
function with the argumentmessage
[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
[ ]
Write anassert
statement that makes sure part of one of ourmessage
items is inoutput
[ ]
Write anassert
statement to make sure that a parenthesis is not inoutput
if message is atuple
, or that a square bracket is not inoutput
ifmessage
is a list. This is to make sure that we are not mistakenly printing the entire iterable instead of each string in the iterable.[ ]
Add anassert
statement that checks to make sure that output contains two blank lines, followed by the number of indentation spaces, followed by the first few words of one of yourmessage
items.[ ]
Run the test. It should fail.
Code
141def test_wrap_with_iterable(capsys):
142 message = (
143 "The exercise for centering oneself is a simple one.",
144
145 "Stop thinking of what you intend to do. Stop thinking of what you "
146 "have just done. Then stop thinking that you have stopped thinking of "
147 "those things.",
148
149 "Then you will find the now. The time that stretches eternal, and is "
150 "really the only time there is.",
151 )
152 wrap(message)
153
154 output = capsys.readouterr().out
155
156 assert "The exercise for centering oneself" in output, \
157 "wrap() should print the text."
158
159 assert "(" not in output, \
160 "wrap() should not contain parenthesis representing a tuple"
161
162 assert "\n\n Stop thinking" in output, \
163 "There should be two newlines between each printed message item"
164
C. In adventure.py
modify wrap()
#
Now we are ready to change the wrap()
function.
First we’ll turn text
into a one-item iterable if it was a string, that way
we always have something that we can safely iterate over.
Then we’ll iterate over each string in our new text
iterable, wrap it the
same way we already do, and append it to a list called blocks
.
Finally, we’ll print the blocks
list with two "\n"
between each one.
Demo
Note
We don’t need to make any changes to do_read()
for this feature, since it
already calls wrap()
with whatever was in the item dictionary for
"message"
. So once you’re done with this, all your tests should pass.
[ ]
Write anif
statement to check iftext
is a string using theisinstance()
function.[ ]
If so, make a list or tuple containingtext
and assign it to the same variable,text
.
[ ]
Make an empty list and assign it to the variableblocks
[ ]
Use afor
loop to iterate overtext
using the variable namestanza
[ ]
Indent the line(s) where you assignparagraph
to be inside yourfor
loop[ ]
In your call totextwrap.fill()
, instead of usestanza
for the first argument instead oftext
.[ ]
Appendparagraph
toblocks
[ ]
You can either:[ ]
Use argument unpacking to send all of the items in theblocks
list as separate arguments to theprint()
function, and the keyword argumentsep
to print two newlines between each argument.[ ]
Join theblocks
list using two newlines as the delimiter, then print the result.
[ ]
Run yourtest_wrap_with_iterable()
test. It should pass.[ ]
Run yourtest_do_read_in_place()
test. It should pass.[ ]
Run all your tests. They should pass.
Code
134def wrap(text, indent=1):
135 """Print wrapped and indented text."""
136
137 # calculate the indentation
138 margin = (MARGIN * " ") * indent
139
140 # if a string was passed, turn it into a single item tuple
141 if isinstance(text, str):
142 text = (text,)
143
144 # make an empty list for the wrapped blocks
145 blocks = []
146
147 # iterate over items
148 for stanza in text:
149
150 # wrap the text
151 paragraph = textwrap.fill(
152 stanza,
153 WIDTH,
154 initial_indent=margin,
155 subsequent_indent=margin,
156 )
157
158 blocks.append(paragraph)
159
160 # print the wrapped text
161 print(*blocks, sep="\n\n")
162
D. In adventure.py
modify ITEMS
#
Finally, change the "message"
in your "book"
item to be a list or a tuple
of strings.
And now that we’re all literate, feel free to scatter scrolls, signs and sticky notes all over your game!
Code
94 "book": {
95 "key": "book",
96 "can_take": True,
97 "name": "a book",
98 "description": (
99 "A hefty leather-bound tome open to an interesting passage."
100 ),
101 "title": (
102 "The book is open to a page that reads:"
103 ),
104 "message": (
105 "At the edge of the woods is a cave that is home to a three "
106 "headed dragon, each with a different temperament. ",
107
108 "Legend says that if you happen upon the dragon sleeping, the "
109 "brave may pet one of its three heads. ",
110
111 "Choose the right head and you will be rewarded with great "
112 "fortunes. ",
113
114 "But beware, choose poorly and it will surely mean your doom!",
115 ),
116 },