Part 14: Dragons
Contents
Part 14: Dragons#
In this section we will add a cave with a three headed dragon and the command to pet them.
Part 14.1: Add command#
In this section we’ll add the pet command.
Demo
A. In test_game.py
define test_do_pet()
#
First we’ll write the test which we expect to fail. It will just test that when
you call do_pet()
a debug message is printed.
Need help?
[ ]
Import thedo_pet
function[ ]
Addtest_do_pet()
function with one parametercapsys
[ ]
Calldo_pet()
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 pet: []"
is inoutput
[ ]
Run your tests. They should fail.
Code
6from adventure import (
7 debug,
8 do_drop,
9 do_read,
10 do_pet,
11 error,
12 header,
13 health_change,
14 inventory_change,
15 is_for_sale,
16 place_has,
17 player_has,
18 place_add,
19 wrap,
20 write,
21 MAX_HEALTH,
22)
441def test_do_pet(capsys):
442 # WHEN: You call do_pet()
443 do_pet([])
444 output = capsys.readouterr().out
445
446 # THEN: A debug message should be printed
447 assert "Trying to pet: []" in output
B. In adventure.py
define do_pet()
#
Now we’ll add the do_pet()
function to our game. It should print a debug
message like Trying to pet:
Need help?
[ ]
Add ado_pet()
function with one parameterargs
[ ]
In it, use thedebug()
function to print something like"Trying to pet args."
.[ ]
Run your tests again. They should now pass.
do_pet()
652def do_pet(args):
653 """Pet dragons"""
654
655 debug(f"Trying to pet: {args}")
C. In adventure.py
modify main()
: add pet#
Finally, add the code in main()
so that when the player types "pet"
, the
do_pet()
function will be called.
Need help?
[ ]
Add an elif that checks if command is"pet"
.[ ]
if so, calldo_pet()
and passargs
.
main()
673 if command in ("q", "quit", "exit"):
674 do_quit()
675
676 elif command in ("shop"):
677 do_shop()
678
679 elif command in ("g", "go"):
680 do_go(args)
681
682 elif command in ("x", "exam", "examine"):
683 do_examine(args)
684
685 elif command in ("l", "look"):
686 do_look()
687
688 elif command in ("t", "take", "grab"):
689 do_take(args)
690
691 elif command in ("i", "inventory"):
692 do_inventory()
693
694 elif command in ("r", "read"):
695 do_read(args)
696
697 elif command == "drop":
698 do_drop(args)
699
700 elif command == "buy":
701 do_buy(args)
702
703 elif command == "pet":
704 do_pet(args)
705
706 else:
707 error("No such command.")
708 continue
Part 14.2: Is petting allowed?#
In this section we’ll check to make sure petting is allowed in the current place.
Demo
A. In test_game.py
define test_do_pet_cant_pet()
#
In this section we’ll write a test_do_pet_cant_pet()
function. It should
check that if the player tries to pet something when in a place where they
aren’t allowed (as defined by the place dictionary "can"
list), they’ll see
an error message.
Need help?
1. GIVEN: The player is in a place where they can’t pet anything
…
[ ]
ChangePLAYER
to put the player in a fake place[ ]
Add a matching fake place dictionary toPLACES
. The"can"
key should be an empty list.
2. WHEN: They try to pet something
…
[ ]
Calldo_pet()
with a list containing any string[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
3. THEN: An error message should be printed
…
[ ]
assert that an error message like"You can't do that"
is inoutput
4. Run your tests. They should fail.
test_do_pet_cant_pet()
450def test_do_pet_cant_pet(capsys):
451 # GIVEN: The player is in a place where they can't pet anything
452 adventure.PLAYER["place"] = "nowhere"
453 adventure.PLACES["nowhere"] = {
454 "name": "The Void",
455 "can": [],
456 }
457
458 # WHEN: They try to pet something
459 do_pet(["red", "dragon"])
460 output = capsys.readouterr().out
461
462 # THEN: An error message should be printed
463 assert "You can't do that" in output
B. In adventure.py
modify do_pet()
: can pet#
Now we’ll modify do_pet()
function to check that if the current place is not
able to use the pet command (as defined by the place dictionary "can"
list)
an error message will be printed and the function will return.
Need help?
[ ]
Use theplace_can()
function to check if the place can"pet"
. If not:[ ]
Print an error message like"You can't do that here."
[ ]
return
do_pet()
699def do_pet(args):
700 """Pet dragons"""
701
702 debug(f"Trying to pet: {args}")
703
704 # make sure they are somewhere they can pet dragons
705 if not place_can("pet"):
706 error("You can't do that here.")
707 return
C. In adventure.py
modify PLACES
#
Now update the PLACES
dictionary to add a cave where you can pet a dragon,
and modify your other places so that you can get to it.
Need help?
[ ]
Add a place calledcave
with the"can"
key set to a list that includes the string"pet"
[ ]
Modify the"east"
,"west"
,"north"
, and"south"
key(s) of your other places so that the player can get to the cave.
PLACES
53PLACES = {
54 "home": {
55 "key": "home",
56 "name": "Your Cottage",
57 "east": "town-square",
58 "description": "A cozy stone cottage with a desk and a neatly made bed.",
59 "items": ["desk", "book"],
60 },
61 "town-square": {
62 "key": "town-square",
63 "name": "The Town Square",
64 "west": "home",
65 "east": "woods",
66 "north": "market",
67 "description": (
68 "A large open space surrounded by buildings with a burbling "
69 "fountain in the center."
70 ),
71 },
72 "market": {
73 "key": "market",
74 "name": "The Market",
75 "south": "town-square",
76 "items": ["elixir", "dagger"],
77 "can": ["shop", "buy"],
78 "description": (
79 "A tidy store with shelves full of goods to buy. A wooden hand "
80 "painted menu hangs on the wall."
81 ),
82 },
83 "woods": {
84 "key": "woods",
85 "name": "The Woods",
86 "east": "hill",
87 "west": "town-square",
88 "description": (
89 "A dirt road meanders under a canopy of autumn leaves in brilliant "
90 "hues of gold and crimson.",
91
92 "You hear a stream burbling somewhere out of sight. Leaves crunch "
93 "under your feet on the sun dappled forest floor.",
94
95 "You see an ancient moss-covered hollow tree, its gnarled and twisted "
96 "branches looming over you. On the opposite side, a fallen log juts "
97 "partway into the road.",
98 ),
99 "items": [],
100 },
101 "hill": {
102 "key": "hill",
103 "name": "A grassy hill",
104 "west": "woods",
105 "south": "cave",
106 "description": (
107 "A winding path leads up the slope of a grassy hill. The air is "
108 "warm here.",
109 "At the top of the hill, you see that the path continues to the "
110 "down to the south. In that direction you can make out a cave by "
111 "the shore of a lake."
112 ),
113 "items": [],
114 },
115 "cave": {
116 "key": "cave",
117 "name": "A cave",
118 "north": "hill",
119 "description": (
120 "Your footsteps echo as you step into the vast cavern.",
121 "Shafts of sunlight slice through the gloom, playing against the "
122 "landscape of glittering treasure.",
123 "Resting atop a mound of gold, a collosal dragon rests curled up snugly. "
124 "Its three enormous heads snore softly, each in turn.",
125 ),
126 "items": [],
127 "can": ["pet"],
128 },
129}
130
Part 14.3: Ensure args#
In this section we’ll make sure that the player typed what they want to pet, or print an error if they didn’t.
Demo
A. In test_game.py
define test_do_pet_no_args()
#
In this section we’ll write a test_do_pet_no_args()
function. It should check
that if the player does not type anything after "pet"
, they’ll see an error
message.
Need help?
1. GIVEN: The player is in a place where they can pet things
…
[ ]
ChangePLAYER
to put the player in a fake place[ ]
Add a matching fake place dictionary toPLACES
. The"can"
key should be a list containing the string"pet"
2. WHEN: the player types “pet” with no arguments
…
[ ]
Calldo_pet()
with an empty list[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
3. THEN: an error message should be printed
…
[ ]
assert that an error message like"What do you want to pet"
is inoutput
4. Run your tests. They should fail.
test_do_pet_no_args()
466def test_do_pet_no_args(capsys):
467 # GIVEN: The player is in a place where they can pet things
468 adventure.PLAYER["place"] = "somewhere"
469 adventure.PLACES["somewhere"] = {
470 "name": "Somewhere out there",
471 "can": ["pet"],
472 }
473
474 # WHEN: the player types "pet" with no arguments
475 do_pet([])
476 output = capsys.readouterr().out
477
478 # THEN: an error message should be printed
479 assert "What do you want to pet" in output
B. In adventure.py
modify do_pet()
: ensure args#
Need help?
[ ]
Check ifargs
is empty. If so:[ ]
Print an error message like"What do you want to pet?"
[ ]
return
do_pet()
699def do_pet(args):
700 """Pet dragons"""
701
702 debug(f"Trying to pet: {args}")
703
704 # make sure they are somewhere they can pet dragons
705 if not place_can("pet"):
706 error("You can't do that here.")
707 return
708
709 # make sure the player said what they want to pet
710 if not args:
711 error("What do you want to pet?")
712 return
Part 14.4: Ensure color#
This command is a little different from previous commands, because we want the player to be able to type a few different things.
We expect the player to type something like:
pet red dragon
But we would also accept:
pet red dragon head
Or:
pet red head
Or even:
pet red
Demo
So we’ll need to make sure that the player typed something in addition to
"dragon"
and "head"
and that it is a valid color.
A. In test_game.py
define test_do_pet_no_color()
#
In this section we’ll write a test_do_pet_no_color()
test. It should
check that the player typed something in addition to "dragon"
and/or
"head"
.
Need help?
1. GIVEN: The player is in a place where they can pet things
…
[ ]
ChangePLAYER
to put the player in a fake place[ ]
Add a matching fake place dictionary toPLACES
. The"can"
key should be a list containing the string"pet"
2. WHEN: the player types “pet” without typing a color
…
[ ]
Calldo_pet()
with a list containing the words"dragon"
and/or"head"
[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
3. THEN: an error message should be printed
…
[ ]
assert that an error message like"What do you want to pet"
is inoutput
4. Run your tests. They should fail.
test_do_pet_no_color()
482def test_do_pet_no_color(capsys):
483 # GIVEN: The player is in a place where they can pet things
484 adventure.PLAYER["place"] = "somewhere"
485 adventure.PLACES["somewhere"] = {
486 "name": "Somewhere out there",
487 "can": ["pet"],
488 }
489
490 # WHEN: the player types "pet" with only the words "dragon" and/or "head"
491 do_pet(["dragon", "head"])
492 output = capsys.readouterr().out
493
494 # THEN: an error message should be printed
495 assert "What do you want to pet" in output
B. In adventure.py
modify do_pet()
: remove ignored args#
To support extra words like "dragon"
and "head"
, we’re simply going to
remove them from args
.
If we do this before we check to make sure that args
is not empty, then
we’ll get the same error message if they type pet
as when they type pet dragon
.
Need help?
Do this before the line with if not args:
[ ]
Make a list of allowed words like["dragon", "head"]
and iterate over it. For each one:[ ]
Check if the word is inargs
. If so:[ ]
Remove it fromargs
[ ]
Run your tests. They should pass.
do_pet()
701def do_pet(args):
702 """Pet dragons"""
703
704 debug(f"Trying to pet: {args}")
705
706 # make sure they are somewhere they can pet dragons
707 if not place_can("pet"):
708 error("You can't do that here.")
709 return
710
711 # remove the expected words from args
712 for word in ["dragon", "head"]:
713 if word in args:
714 args.remove(word)
715
716 # make sure the player said what they want to pet
717 if not args:
718 error("What do you want to pet?")
719 return
720
C. In test_game.py
define test_do_pet_invalid_color()
#
We’ll add a new test test_do_pet_invalid_color()
to make sure the color is
valid. We’ll use a global variable adventure.COLORS
to store the list of
valid colors.
Need help?
1. GIVEN: There are three colors of dragon heads
…
[ ]
Assignadventure.COLORS
to a list of colors
1. AND: The player is in a place where they can pet things
…
[ ]
ChangePLAYER
to put the player in a fake place[ ]
Add a matching fake place dictionary toPLACES
. The"can"
key should be an empty list.
2. WHEN: They try to pet a dragon with a color that doesn’t exist
…
[ ]
Calldo_pet()
with a list containing the a word that is not a color[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
3. THEN: an error message should be printed
…
[ ]
assert that an error message like"I don't see that dragon"
is inoutput
4. Run your tests. They should fail.
test_do_pet_invalid_color()
498def test_do_pet_invalid_color(capsys):
499 # GIVEN: There are three colors of dragon heads
500 adventure.COLORS = ["red", "green", "blue"]
501
502 # AND: The player is in a place where they can pet dragons
503 adventure.PLAYER["place"] = "cave"
504 adventure.PLACES["cave"] = {
505 "name": "A cave",
506 "can": ["pet"],
507 }
508
509 # WHEN: They try to pet a dragon with a color that doesn't exist
510 do_pet(["purple"])
511 output = capsys.readouterr().out
512
513 # THEN: An error message should be printed
514 assert "I don't see a dragon" in output
D. In adventure.py
add COLORS
#
At the top of your script where your other global variables are, add a new
global variable COLORS
and set it to a list with three colors in it.
COLORS
24import textwrap
25
26from console import fg, fx
27from console.progress import ProgressBar
28from console.screen import sc
29from console.utils import clear_line
30
31WIDTH = 45
32
33MARGIN = 2
34
35DEBUG = True
36
37MAX_HEALTH = 100
38
39BAR = ProgressBar(
40 total=(MAX_HEALTH + 0.1),
41 width=(WIDTH - len("Health") - len("100%")),
42 clear_left=False,
43)
44
45# ## Game World Data #########################################################
46
47COLORS = ["red", "black", "silver"]
48
E. In adventure.py
modify do_pet()
: ensure valid color#
We can now assume that anything left in the args
list is the color. We’ll
check that it is in the COLORS
list, or print an error message if it is not.
Need help?
[ ]
Assign the first item fromargs
to the variablecolor
[ ]
Check to make sure thatcolor
is in the list ofCOLORS
. If not:[ ]
Print an error message like:"I don't see a dragon head that looks like that."
[ ]
return
do_pet()
701def do_pet(args):
702 """Pet dragons"""
703
704 debug(f"Trying to pet: {args}")
705
706 # make sure they are somewhere they can pet dragons
707 if not place_can("pet"):
708 error("You can't do that here.")
709 return
710
711 # remove the expected words from args
712 for word in ["dragon", "head"]:
713 if word in args:
714 args.remove(word)
715
716 # make sure the player said what they want to pet
717 if not args:
718 error("What do you want to pet?")
719 return
720
721 color = args[0].lower()
722
723 # make sure they typed in a real color
724 if color not in COLORS:
725 error("I don't see a dragon that looks like that.")
726 return
Part 14.5: Pick a dragon#
In this section we’ll randomly pick a dragon mood and print a debug message about it.
We’ll make a global list DRAGONS
to store information about each dragon in
dictionaries. We’ll add more to this later, but for now each dictionary just
needs a single key "mood"
with a string for the dragon’s mood, for example
"cheerful"
.
Demo
Then when the player pets one of the dragon’s heads, randomly select one of the dragon dictionaries and print a debug message that says which dragon was selected.
A. In test_game.py
define test_do_pet_cheerful_dragon()
#
In this section we’ll start a test for when the player pets a cheerful dragon head and simply assert that a debug message was printed.
In order to make sure we always get the cheerful dragon in the test, we’ll set
COLORS
and DRAGONS
to only contain one color and dragon dictionary
respectively.
Need help?
1. GIVEN: The player is in a place where they can pet a dragon
…
[ ]
ChangePLAYER
to put the player in a fake place[ ]
Add a matching fake place dictionary toPLACES
. The"can"
key should be a list containing the string"pet"
2. AND: There is one color of dragon heads
…
[ ]
Assignadventure.COLORS
to a list containing one color
3. AND: There is one dragon
…
[ ]
Assignadventure.DRAGONS
to a list containing one dictionary. The dictionary should have the key"mood"
and the string"cheerful"
for the value.
4. WHEN: The player pets that head
…
[ ]
Calldo_pet()
with a list that contains the same color that is inCOLORS
[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
5. THEN: A debug message should print
…
[ ]
assert that an debug message like"You picked the dragon's cheerful red head."
is inoutput
6. Run your tests. They should fail.
test_do_pet_cheerful_dragon()
517def test_do_pet_cheerful_dragon(capsys):
518 # GIVEN: The player is in a place where they can pet a dragon
519 adventure.PLAYER["place"] = "cave"
520 adventure.PLACES["cave"] = {
521 "name": "A cave",
522 "can": ["pet"]
523 }
524
525 # AND: There is one color of dragon heads
526 adventure.COLORS = ["red"]
527
528 # AND: There is one dragon
529 adventure.DRAGONS = [
530 {"mood": "cheerful"}
531 ]
532
533 # WHEN: The player pets that head
534 do_pet(["red", "dragon"])
535 output = capsys.readouterr().out
536
537 # THEN: A debug message should print
538 assert "You picked the cheerful red dragon." in output
B. At the top of adventure.py
: import random
#
In order to randomly select a dragon dictionary from DRAGONS
we’ll need to
import the random
module.
Need help?
[ ]
import therandom
module
Code
24import random
25import textwrap
26
27from console import fg, fx
28from console.progress import ProgressBar
29from console.screen import sc
C. At the top of adventure.py
: add DRAGONS
#
Add the global variable DRAGONS
and assign it to a list where each item is a
dictionary containing information about each of the dragon’s heads. For now each
dictionary will only have one key "mood"
.
Add three dragon dictionaries for the moods "cheerful"
, "grumpy"
and
"lonely"
.
Need help?
[ ]
Create global variableDRAGONS
and assign it to a list. The list should contain:[ ]
Three dictionaries. Each dictionary should contain:[ ]
The key"mood"
and string with the mood of that dragon, ie"cheerful"
COLORS
48COLORS = ["red", "black", "silver"]
49
50DRAGONS = [
51 {"mood": "cheerful"},
52 {"mood": "grumpy"},
53 {"mood": "lonely"},
54]
55
56PLAYER = {
57 "place": "home",
58 "inventory": {"gems": 50},
59 "health": MAX_HEALTH,
60}
61
D. In adventure.py
modify do_pet()
#
In this section we’ll randomly select one of the dragons from DRAGONS
using the
random.choice()
function. We’ll add the "color"
that the player selected to
that dictionary, then print a debug message with information about the dragon.
Need help?
[ ]
Callrandom.choice()
with the argumentDRAGONS
and assign it to the variabledragon
.[ ]
Setdragon["color"]
tocolor
[ ]
Print a debug message like"You picked the dragon's MOOD COLOR head."
do_pet()
708def do_pet(args):
709 """Pet dragons"""
710
711 debug(f"Trying to pet: {args}")
712
713 # make sure they are somewhere they can pet dragons
714 if not place_can("pet"):
715 error("You can't do that here.")
716 return
717
718 # remove the expected words from args
719 for word in ["dragon", "head"]:
720 if word in args:
721 args.remove(word)
722
723 # make sure the player said what they want to pet
724 if not args:
725 error("What do you want to pet?")
726 return
727
728 color = args[0].lower()
729
730 # make sure they typed in a real color
731 if color not in COLORS:
732 error("I don't see a dragon that looks like that.")
733 return
734
735 # get the dragon info for this color
736 dragon = random.choice(DRAGONS)
737 dragon["color"] = color
738
739 debug(f"You picked the {dragon['mood']} {dragon['color']} dragon.")
Part 14.6: Treasure#
In this section we’re going to write the code so that some of the dragons will give the player gems.
In order to do this we’ll add a "treasure"
key to some of the dragon
dictionaries DRAGONS
. The value will be a tuple containing the minimum and
maximum possible gems that a particular dragon might give.
Demo
Then in the do_pet()
function we’ll retrieve the range of possible treasure
from the dragon dictionary, randomly pick a number in that range, then add that
amount the player gems and print a message to tell the player what happened.
A. In test_game.py
modify test_do_pet_cheerful_dragon()
#
In this section we’ll modify the test_do_pet_cheerful_dragon()
test to add
the "treasure"
key to the single dictionary in DRAGONS
, and a tuple
containing two numbers (the minimum and maximum possible treasure) as the value.
Then we’ll need to check that the number if gems in the player’s inventory was increased.
Need help?
1. Modify AND: There is one dragon
…
[ ]
Add a"treasure"
key to the dragon dictionary inDRAGONS
for[ ]
The key should be"treasure"
[ ]
The value should a tuple with two numbers representing the minimum and maximum possible treasure. ie(10, 20)
.
Tip
If we want to know the exact treasure amount you can use the same
number for the minimum and maximum, for example: (10, 10)
.
2. After WHEN add AND: The player has some gems
…
[ ]
Set the"gems"
inPLAYER
inventory to a number
3. After THEN add AND: The player should get treasure
…
[ ]
assert that the gems inPLAYER
inventory is more than it was before
4. After THEN add AND: The player should see a message about what happened
…
[ ]
assert a message like"gave you GEMS gems"
is inoutput
5. Run your tests. They should fail.
test_do_pet_cheerful_dragon()
517def test_do_pet_cheerful_dragon(capsys):
518 # GIVEN: The player is in a place where they can pet a dragon
519 adventure.PLAYER["place"] = "cave"
520 adventure.PLACES["cave"] = {
521 "name": "A cave",
522 "can": ["pet"]
523 }
524
525 # AND: There is one color of dragon heads
526 adventure.COLORS = ["red"]
527
528 # AND: There is one dragon who gives you treasure
529 adventure.DRAGONS = [{
530 "mood": "cheerful",
531 "treasure": (10, 10),
532 }]
533
534 # AND: The player has some gems
535 adventure.PLAYER["inventory"] = {"gems": 10}
536
537 # WHEN: The player pets that head
538 do_pet(["red", "dragon"])
539 output = capsys.readouterr().out
540
541 # THEN: A debug message should print
542 assert "You picked the cheerful red dragon." in output
543
544 # AND: The player should get treasure
545 assert adventure.PLAYER["inventory"]["gems"] == 20
546
547 # AND: The player should see a message about what happened
548 assert "10 gems" in output
B. In adventure.py
modify DRAGONS
#
Modify the dragon dictionaries in the DRAGONS
list to add "treasure"
tuples
for the "cheerful"
and "lonely"
dragons.
Need help?
[ ]
Add a"treasure"
key to the single dictionary inDRAGONS
list for the"cheerful"
and"lonely"
dragons[ ]
The key should be"treasure"
[ ]
The value should a tuple with two numbers representing the minimum and maximum amount of treasure. ie(10, 20)
.
DRAGONS
50DRAGONS = [
51 {
52 "mood": "cheerful",
53 "treasure": (3, 15),
54 },
55 {
56 "mood": "grumpy",
57 },
58 {
59 "mood": "lonely",
60 "treasure": (8, 25),
61 },
62]
B. In adventure.py
modify do_pet()
#
In this section we’ll retrieve the range of possible treasure from the dragon
dictionary (if the "treasure"
key exists) then use the two numbers in the
random.randint()
function to get the number of gems to give the player.
Then we’ll add that number of gems to the player’s inventory and print a message about it.
Need help?
[ ]
Use the.get()
method on thedragon
dictionary to get the value for the"treasure"
key and assign the result topossible_treasure
. Since not all dragons will have the"treasure"
key, use the second argument in.get()
to set the default value to(0, 0)
.[ ]
Pass the minimum and maximum numbers frompossible_treasure
as arguments to therandom.randint()
function and assign the results todragon["gems"]
.[ ]
Use theinventory_change()
function to adddragon["gems"]
to the players inventory.[ ]
Print a message like:"The dragon's MOOD head gave you GEMS gems."
do_pet()
716def do_pet(args):
717 """Pet dragons"""
718
719 debug(f"Trying to pet: {args}")
720
721 # make sure they are somewhere they can pet dragons
722 if not place_can("pet"):
723 error("You can't do that here.")
724 return
725
726 # remove the expected words from args
727 for word in ["dragon", "head"]:
728 if word in args:
729 args.remove(word)
730
731 # make sure the player said what they want to pet
732 if not args:
733 error("What do you want to pet?")
734 return
735
736 color = args[0].lower()
737
738 # make sure they typed in a real color
739 if color not in COLORS:
740 error("I don't see a dragon that looks like that.")
741 return
742
743 # get the dragon info for this color
744 dragon = random.choice(DRAGONS)
745 dragon["color"] = color
746
747 debug(f"You picked the dragon's {dragon['mood']} {dragon['color']} head.")
748
749 # calculate the treasure
750 possible_treasure = dragon.get("treasure", (0, 0))
751 dragon["gems"] = random.randint(*possible_treasure)
752
753 # add the treasure to the players inventory
754 inventory_change("gems", dragon["gems"])
755
756 # print a message about gems
757 if dragon["gems"]:
758 write(f"The dragon's {dragon['mood']} head gave you {dragon['gems']} gems.")
Part 14.7: Damage#
In this section we’re going to write the code so that some of the dragons will cause the player damage.
In order to do this we’ll add a "damage"
key to some of the dragon
dictionaries DRAGONS
. The value will be a tuple containing the minimum and
maximum possible damage that a particular dragon might cause.
Demo
Then in the do_pet()
function we’ll retrieve the range of possible damage
from the dragon dictionary, randomly pick a number in that range, then subtract
that amount the player health and print a message to tell the player what
happened.
A. In test_game.py
modify test_do_pet_cheerful_dragon()
#
In this section we’ll modify the test_do_pet_cheerful_dragon()
test to make
sure that the player’s health does not change.
Need help?
1. After WHEN add AND: The player has a certain amount of health
…
[ ]
Set “health” value in thePLAYER
dictionary to a number like90
2. After THEN add AND: The player’s health should be the same
…
[ ]
Assert thatPLAYER["health"]
is the same as it was before
3. Run your tests. They should pass.
test_do_pet_cheerful_dragon()
517def test_do_pet_cheerful_dragon(capsys):
518 # GIVEN: The player is in a place where they can pet a dragon
519 adventure.PLAYER["place"] = "cave"
520 adventure.PLACES["cave"] = {
521 "name": "A cave",
522 "can": ["pet"]
523 }
524
525 # AND: There is one color of dragon heads
526 adventure.COLORS = ["red"]
527
528 # AND: There is one dragon who gives you treasure
529 adventure.DRAGONS = [{
530 "mood": "cheerful",
531 "treasure": (10, 10),
532 }]
533
534 # AND: The player has some gems
535 adventure.PLAYER["inventory"] = {"gems": 10}
536
537 # AND: The player has a certain amount of health
538 adventure.PLAYER["health"] = 90
539
540 # WHEN: The player pets that head
541 do_pet(["red", "dragon"])
542 output = capsys.readouterr().out
543
544 # THEN: A debug message should print
545 assert "You picked the cheerful red dragon." in output
546
547 # AND: The player should get treasure
548 assert adventure.PLAYER["inventory"]["gems"] == 20
549
550 # AND: The player's health should be the same
551 assert adventure.PLAYER["health"] == 90
552
553 # AND: The player should see a message about what happened
554 assert "10 gems" in output
B. In test_game.py
define test_do_pet_cranky_dragon()
#
In this section we’ll define the test_do_pet_cranky_dragon()
test. It will be
very similar to test_do_pet_cheerful_dragon()
, except in the DRAGONS
dictionary we’ll add a dragon dictionary that has a "damage"
key with a tuple
of two negative numbers and we’ll assert that PLAYER["health"]
has decreased.
Need help?
1. GIVEN: The player is in a place where they can pet a dragon
…
[ ]
ChangePLAYER
to put the player in a fake place[ ]
Add a matching fake place dictionary toPLACES
. The"can"
key should be a list containing the string"pet"
2. AND: There is one color of dragon heads
…
[ ]
Assignadventure.COLORS
to a list containing one color
3. AND: There is one dragon who causes damage
…
[ ]
Assignadventure.DRAGONS
to a list containing one dictionary that contains:[ ]
the key"mood"
and the string"cranky"
for the value.[ ]
the key"damage"
and a tuple with two negative numbers for the value
4. AND: The player has a certain amount of health
…
[ ]
Set"health"
in thePLAYER
dictionary to a number greater than0
and less than100
5. AND: The player has some gems
…
[ ]
Set"gems"
in thePLAYER["inventory"]
dictionary to a positive number
6. WHEN: The player pets that head
…
[ ]
Calldo_pet()
with a list that contains the same color that is inCOLORS
[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
7. THEN: A debug message should print
…
[ ]
assert that an debug message like"You picked the dragon's cranky red head."
is inoutput
8. AND: The player’s health should be reduced
…
[ ]
assert that thePLAYER["health"]
is less than it was before
9. AND: The player’s gems should not be changed
…
[ ]
assert that thePLAYER["inventory"]["gems"]
is the same as it was before
10. AND: The player should see a message about what happened
…
[ ]
assert that an debug message like"caused you DAMAGE" damage
is inoutput
11. Run your tests. They should fail.
test_do_pet_cranky_dragon()
557def test_do_pet_cranky_dragon(capsys):
558 # GIVEN: The player is in a place where they can pet a dragon
559 adventure.PLAYER["place"] = "cave"
560 adventure.PLACES["cave"] = {
561 "name": "A cave",
562 "can": ["pet"]
563 }
564
565 # AND: There is one color of dragon heads
566 adventure.COLORS = ["red"]
567
568 # AND: There is one dragon who causes damage
569 adventure.DRAGONS = [{
570 "mood": "cranky",
571 "damage": (-10, -10),
572 }]
573
574 # AND: The player has a certain amount of health
575 adventure.PLAYER["health"] = 100
576
577 # AND: The player has some gems
578 adventure.PLAYER["inventory"] = {"gems": 10}
579
580 # WHEN: The player pets that head
581 do_pet(["red", "head"])
582 output = capsys.readouterr().out
583
584 # THEN: A debug message should print
585 assert "You picked the cranky red dragon." in output
586
587 # AND: The player's health should be reduced
588 assert adventure.PLAYER["health"] == 90
589
590 # AND: The player's gems should not be changed
591 assert adventure.PLAYER["inventory"]["gems"] == 10
592
593 # AND: The player should see a message about what happened
594 assert "-10 damage" in output
C. In adventure.py
modify DRAGONS
#
Modify the dragon dictionaries in the DRAGONS
list to add "damage"
tuples
for the "cranky"
and "lonely"
dragons.
Need help?
[ ]
Add a"damage"
key to the single dictionary inDRAGONS
list for the"cranky"
and"lonely"
dragons[ ]
The key should be"damage"
[ ]
The value should a tuple with two numbers representing the minimum and maximum amount of damage. ie(-20, -10)
.
DRAGONS
50DRAGONS = [
51 {
52 "mood": "cheerful",
53 "treasure": (3, 15),
54 },
55 {
56 "mood": "grumpy",
57 "damage": (-15, -3),
58 },
59 {
60 "mood": "lonely",
61 "treasure": (8, 25),
62 "damage": (-25, -8),
63 },
64]
D. In adventure.py
modify do_pet()
#
In this section we’ll retrieve the range of possible damage from the dragon
dictionary (if the "damage"
key exists) then use the two numbers in the
random.randint()
function to get the number of gems to give the player.
Then we’ll add that number of gems to the player’s inventory and print a message about it.
Need help?
[ ]
Use the.get()
method on thedragon
dictionary to get the value for the"damage"
key and assign the result topossible_damage
. Since not all dragons will have the"damage"
key, use the second argument in.get()
to set the default value to(0, 0)
.[ ]
Pass the minimum and maximum numbers frompossible_damage
as arguments to therandom.randint()
function and assign the results todragon["health"]
.[ ]
Use thehealth_change()
function to subtractdragon["health"]
from the players health.[ ]
Print a message like:"The dragon's MOOD head caused you HEALTH damage."
do_pet()
718def do_pet(args):
719 """Pet dragons"""
720
721 debug(f"Trying to pet: {args}")
722
723 # make sure they are somewhere they can pet dragons
724 if not place_can("pet"):
725 error("You can't do that here.")
726 return
727
728 # remove the expected words from args
729 for word in ["dragon", "head"]:
730 if word in args:
731 args.remove(word)
732
733 # make sure the player said what they want to pet
734 if not args:
735 error("What do you want to pet?")
736 return
737
738 color = args[0].lower()
739
740 # make sure they typed in a real color
741 if color not in COLORS:
742 error("I don't see a dragon that looks like that.")
743 return
744
745 # get the dragon info for this color
746 dragon = random.choice(DRAGONS)
747 dragon["color"] = color
748
749 debug(f"You picked the dragon's {dragon['mood']} {dragon['color']} head.")
750
751 # calculate the treasure
752 possible_treasure = dragon.get("treasure", (0, 0))
753 dragon["gems"] = random.randint(*possible_treasure)
754
755 # calculate the damage
756 possible_damage = dragon.get("damage", (0, 0))
757 dragon["health"] = random.randint(*possible_damage)
758
759 # add the treasure to the players inventory
760 inventory_change("gems", dragon["gems"])
761
762 # reduce health
763 health_change(dragon["health"])
764
765 # print a message about gems
766 if dragon["gems"]:
767 write(f"The dragon's {dragon['mood']} head gave you {dragon['gems']} gems.")
768
769 # print a message about damage
770 if dragon["health"]:
771 write(f"The dragon's {dragon['mood']} head causes you {dragon['health']} damage.")
E. In test_game.py
define test_do_pet_lonely_dragon()
#
In this section we’ll define the test_do_pet_lonely_dragon()
test. It will be
just like combining the test for a "cheerful"
dragon and a "cranky"
dragon.
That is, the dragon dictionary in DRAGONS
should have both "treasure"
and
"damage"
.
Need help?
1. GIVEN: The player is in a place where they can pet a dragon
…
[ ]
ChangePLAYER
to put the player in a fake place[ ]
Add a matching fake place dictionary toPLACES
. The"can"
key should be a list containing the string"pet"
2. AND: There is one color of dragon heads
…
[ ]
Assignadventure.COLORS
to a list containing one color
3. AND: There is one dragon who causes damage and gives treasure
…
[ ]
Assignadventure.DRAGONS
to a list containing one dictionary that contains:[ ]
the key"mood"
and the string"cranky"
for the value.[ ]
the key"treasure"
and a tuple with two positive numbers for the value[ ]
the key"damage"
and a tuple with two negative numbers for the value
4. AND: The player has a certain amount of health
…
[ ]
Set"health"
in thePLAYER
dictionary to a number greater than0
and less than100
5. AND: The player has some gems
…
[ ]
Set"gems"
in thePLAYER["inventory"]
dictionary to a positive number
6. WHEN: The player pets that head
…
[ ]
Calldo_pet()
with a list that contains the same color that is inCOLORS
[ ]
Assign the results ofcapsys.readouterr().out
to the variableoutput
7. THEN: A debug message should print
…
[ ]
assert that an debug message like"You picked the dragon's lonely red head."
is inoutput
8. AND: The player’s health should be reduced
…
[ ]
assert that thePLAYER["health"]
is less than it was before
9. AND: The player should get treasure
…
[ ]
assert that thePLAYER["inventory"]["gems"]
is more than it was before
10. AND: The player should see a message about what happened
…
[ ]
assert that an debug message like samp"caused you {HEALTH}" damage
is inoutput
11. Run your tests. They should pass.
test_do_pet_lonely_dragon()
597def test_do_pet_lonely_dragon(capsys):
598 # GIVEN: The player is in a place where they can pet a dragon
599 adventure.PLAYER["place"] = "cave"
600 adventure.PLACES["cave"] = {
601 "name": "A cave",
602 "can": ["pet"]
603 }
604
605 # AND: There is one color of dragon heads
606 adventure.COLORS = ["blue"]
607
608 # AND: There is one dragon who gives treasure and causes damage
609 adventure.DRAGONS = [{
610 "mood": "lonely",
611 "damage": (-10, -10),
612 "treasure": (20, 20),
613 }]
614
615 # AND: The player has a certain amount of health
616 adventure.PLAYER["health"] = 100
617
618 # AND: The player has a certain number of gems
619 adventure.PLAYER["gems"] = 10
620
621 # WHEN: The player pets that head
622 do_pet(["blue", "head"])
623 output = capsys.readouterr().out
624
625 # THEN: A debug message should print
626 assert "You picked the lonely blue dragon." in output
627
628 # AND: The player's health should be reduced
629 assert adventure.PLAYER["health"] == 90
630
631 # AND: The player should get treasure
632 assert adventure.PLAYER["inventory"]["gems"] > 10
633
634 # AND: The player should see a message about what happened
635 assert "20 gems" in output
636 assert "-10 damage" in output
Part 14.8: Pet the dragon#
In this section we’ll add a description of what happens when you pet a dragon’s head, pausing between each line to give it a sense of drama.
Demo
A. At the top of adventure.py
: import sleep
from time
#
In order to add a pause for effect in between lines in the action description,
we’ll need to import the sleep
function from the time
module.
Need help?
[ ]
import thesleep
function from thetime
module
Code
26from time import sleep
27
28from console import fg, fx
29from console.progress import ProgressBar
B. At the top of adventure.py
: add DELAY
#
At the top of your script where your other global variables are, add a new
global variable DELAY
and set it to a number that will be the number of
seconds to pause for effect.
Need help?
[ ]
SetDELAY
to a number like1
DELAY
33WIDTH = 45
34
35MARGIN = 2
36
37DEBUG = True
38
39DELAY = 1.25
40
41MAX_HEALTH = 100
42
C. Modify do_pet()
: add action description with delay#
In this section we’re going to add a description of what happens when the player pets the dragon’s head. To make it a little more exciting, we’ll split the description onto multiple strings in a tuple. Something like:
"You slowly creep forward..."
"...gingerly reach out your hand..."
"...and gently pat the dragon's COLOR head."
"..."
"He blinks sleepy eyes and peers at you..."
Before printing the messages about gems and damage, we’ll iterate over the
sentences
tuple. In each iteration we’ll print a blank line,
print the string, and call sleep()
with the argument DELAY
.
Finally we’ll print one blank line at the end.
Need help?
[ ]
Create a tuple (or list) assigned tosentences
that contains the three strings from above.[ ]
Above the messages about gems and damage are printed, use a for loop to iterate oversentences
with the variabletext
. In each iteration:[ ]
Print a blank line.[ ]
Use thewrite()
function to printtext
.[ ]
Callsleep()
with the argumentDELAY
.
[ ]
Print a blank line.[ ]
Play your game and see how it looks!
do_pet()
748 # get the dragon info for this color
749 dragon = random.choice(DRAGONS)
750 dragon["color"] = color
751
752 debug(f"You picked the dragon's {dragon['mood']} {dragon['color']} head.")
753
754 # calculate the treasure
755 possible_treasure = dragon.get("treasure", (0, 0))
756 dragon["gems"] = random.randint(*possible_treasure)
757
758 # calculate the damage
759 possible_damage = dragon.get("damage", (0, 0))
760 dragon["health"] = random.randint(*possible_damage)
761
762 # add the treasure to the players inventory
763 inventory_change("gems", dragon["gems"])
764
765 # reduce health
766 health_change(dragon["health"])
767
768 sentences = (
769 "You slowly creep forward...",
770 "...gingerly reach out your hand...",
771 f"...and gently pat the dragon's {color} head.",
772 "...",
773 "He blinks sleepy eyes and peers at you...",
774 )
775
776 for text in sentences:
777 print()
778 write(text)
779 sleep(DELAY)
780
781 print()
782
783 # print a message about gems
784 if dragon["gems"]:
785 write(f"The dragon's {dragon['mood']} head gave you {dragon['gems']} gems.")
786
787 # print a message about damage
788 if dragon["health"]:
789 write(f"The dragon's {dragon['mood']} head causes you {dragon['health']} damage.")
790
791
D. At the top of test_game.py
: set adventure.DELAY
#
If you were to run your tests now, you would find that all of the
test_do_pet_*
tests run an awful lot slower. That’s because do_pet()
calls
sleep()
in the tests just like it does in the game.
To avoid this problem, simply set adventure.DELAY
to 0
near the top of your
test file. Unlike the changes that we make in a GIVEN portion of a test, we
only need to set adventure.DELAY
once. Put it just under where you assign all
of the *_STATE
global variables.
Need help?
[ ]
Find where you assignPLAYER_STATE
,DEBUG_STATE
, etc. Just under that setadventure.DELAY
to0
[ ]
Run your tests. They should pass and be as fast as usual.
Code
25def setup_module(module):
26 """Initialize global state variables. Should be run once."""
27 global PLAYER_STATE, PLACES_STATE, ITEMS_STATE, DEBUG_STATE
28
29 PLAYER_STATE = deepcopy(adventure.PLAYER)
30 PLACES_STATE = deepcopy(adventure.PLACES)
31 ITEMS_STATE = deepcopy(adventure.ITEMS)
32 DEBUG_STATE = True
33 adventure.DELAY = 0
Part 14.9: Better messages#
In this section we’ll make nicer and more detailed messages for when each dragon causes damage or gives treasure.
To do this we’ll add a "message"
key to each dragon dictionary in DRAGONS
,
which will have the end of the message for a value.
Demo
For example, say we want the message to be:
"The happy green dragon thinks you're great and gives you 100 gems!"
Then the "message"
value would be:
"thinks you're great and gives you {gems} gems!"
It’s important to note that value should contain the f-string style variables
{gems}
and/or {damage}
, but it should not actually be an f-string.
That way we can attach it to the beginning of the message in do_pet()
, then
call the .format()
method on the resulting string to fill in all the
variable values.
A. Modify test_do_pet_*_dragon()
: add "message"
to DRAGONS
#
In this section we’ll modify the three test_do_pet_*_dragon()
tests to add
the "message"
key to the single dictionary in DRAGONS
with the value bing a
string that contains the f-string style variables {gems}
and/or {health}
.
Need help?
1. Modify AND: There is one dragon who gives you treasure
…
[ ]
Add the key"message"
to the single dictionary inDRAGONS
with the value:[ ]
A string that is not an f-string but contains the f-string style variables{gems}
and/or{health}
.It should be the part of the message that comes after
"The dragon's MOOD COLOR head"
and describes what the dragon does after the player pets it.
2. Run your tests. They should pass.
test_do_pet_cheerful_dragon()
518def test_do_pet_cheerful_dragon(capsys):
519 # GIVEN: The player is in a place where they can pet a dragon
520 adventure.PLAYER["place"] = "cave"
521 adventure.PLACES["cave"] = {
522 "name": "A cave",
523 "can": ["pet"]
524 }
525
526 # AND: There is one color of dragon heads
527 adventure.COLORS = ["red"]
528
529 # AND: There is one dragon who gives you treasure
530 adventure.DRAGONS = [{
531 "mood": "cheerful",
532 "treasure": (10, 10),
533 "message": "likes you and gives you {gems} gems.",
534 }]
test_do_pet_cranky_dragon()
559def test_do_pet_cranky_dragon(capsys):
560 # GIVEN: The player is in a place where they can pet a dragon
561 adventure.PLAYER["place"] = "cave"
562 adventure.PLACES["cave"] = {
563 "name": "A cave",
564 "can": ["pet"]
565 }
566
567 # AND: There is one color of dragon heads
568 adventure.COLORS = ["red"]
569
570 # AND: There is one dragon who causes damage
571 adventure.DRAGONS = [{
572 "mood": "cranky",
573 "damage": (-10, -10),
574 "message": "pushes you away causing you {health} damage",
575 }]
test_do_pet_lonely_dragon()
600def test_do_pet_lonely_dragon(capsys):
601 # GIVEN: The player is in a place where they can pet a dragon
602 adventure.PLAYER["place"] = "cave"
603 adventure.PLACES["cave"] = {
604 "name": "A cave",
605 "can": ["pet"]
606 }
607
608 # AND: There is one color of dragon heads
609 adventure.COLORS = ["blue"]
610
611 # AND: There is one dragon who gives treasure and causes damage
612 adventure.DRAGONS = [{
613 "mood": "lonely",
614 "damage": (-10, -10),
615 "treasure": (20, 20),
616 "message": "throws {gems} gems at you causing you {health} damage",
617 }]
B. In adventure.py
modify DRAGONS
: add "message"
#
Modify the dragon dictionaries in the DRAGONS
list to add "message"
string
for all three dragons.
Need help?
[ ]
Add the key"message"
to the all three dragon dictionaries inDRAGONS
with the value:[ ]
A string that is not an f-string but contains the f-string style variables{gems}
and/or{health}
.It should be the part of the message that comes after
"The dragon's MOOD COLOR head "
and describes what the dragon does after the player pets it.
DRAGONS
53DRAGONS = [
54 {
55 "mood": "cheerful",
56 "treasure": (3, 15),
57 "message": "thinks you're adorable! He gives you {gems} gems!"
58 },
59 {
60 "mood": "grumpy",
61 "damage": (-15, -3),
62 "message": (
63 "wants to be left alone. The heat from his mighty sigh "
64 "singes your hair, costing you {health} in health."
65 ),
66 },
67 {
68 "mood": "lonely",
69 "treasure": (8, 25),
70 "damage": (-25, -8),
71 "message": (
72 "is just SO happy to see you! He gives you a whopping "
73 "{gems} gems! Then he hugs you, squeezes you, and calls "
74 "you George... costing you {health} in health."
75 )
76 },
77]
C. Modify do_pet()
: print the message#
Now we’ll combine the beginning of the message
"The dragon's {mood} {color} head "
with the string from dragon["message"]
.
Since the dragon
dictionary already has the information about mood
,
color
, and gems
and/or health
, we can call .format()
on the resulting
string and pass the whole dictionary as keyword arguments. That will fill in
all those variables with their corresponding values from the dictionary.
Then we can replace the lines where we print the two old messages with a line printing the new message, wrapped.
Need help?
[ ]
Remove the lines where you print the messages about gems and damage.[ ]
Concatonate the string"The dragon's {mood} {color} head "
withdragon["message"]
and assign the result to the variabletpl
.[ ]
Call the.format()
method ontpl
and pass all key-value pairs from thedragon
dictionary as keyword arguments. Assign the result totext
.[ ]
Use thewrap()
function to printtext
.[ ]
Run your tests. They should pass.
do_pet()
758 # get the dragon info for this color
759 dragon = random.choice(DRAGONS)
760 dragon["color"] = color
761
762 debug(f"You picked the dragon's {dragon['mood']} {dragon['color']} head.")
763
764 # calculate the treasure
765 possible_treasure = dragon.get("treasure", (0, 0))
766 dragon["gems"] = random.randint(*possible_treasure)
767
768 # calculate the damage
769 possible_damage = dragon.get("damage", (0, 0))
770 dragon["health"] = random.randint(*possible_damage)
771
772 # add the treasure to the players inventory
773 inventory_change("gems", dragon["gems"])
774
775 # reduce health
776 health_change(dragon["health"])
777
778 sentences = (
779 "You slowly creep forward...",
780 "...gingerly reach out your hand...",
781 f"...and gently pat the dragon's {color} head.",
782 "...",
783 "He blinks sleepy eyes and peers at you...",
784 )
785
786 for text in sentences:
787 print()
788 write(text)
789 sleep(DELAY)
790
791 print()
792
793 # generate the message
794 tpl = "The dragon's {mood} {color} head " + dragon["message"]
795 text = tpl.format(**dragon)
796 wrap(text)
Part 14.10: Add dragon#
Finally, we’ll add the dragon itself as an item in your cave so the player can examine it to find out the colors of each dragon head.
Demo
A. In adventure.py
modify ITEMS
#
In your ITEMS
dictionary, add a "dragon"
key. Use the global variable
COLORS
in the description to list the colors of the dragon heads.
ITEMS
214 "dragon": {
215 "key": "dragon",
216 "name": "a three-headed dragon",
217 "description": (
218 f"A colossal dragon with heads of {', '.join(COLORS[0:-1])}, "
219 f"and {COLORS[2]}."
220 ),
221 },
B. In adventure.py
modify PLACES
#
COLORS
147 "cave": {
148 "key": "cave",
149 "name": "A cave",
150 "north": "hill",
151 "description": (
152 "Your footsteps echo as you step into the vast cavern.",
153 "Shafts of sunlight slice through the gloom, playing against the "
154 "landscape of glittering treasure.",
155 "Resting atop a mound of gold, a colossal dragon rests curled up snugly. "
156 "Its three enormous heads snore softly, each in turn.",
157 ),
158 "items": ["dragon"],
159 "can": ["pet"],
160 },