Part 15: Eat & Drink#

In this section we will add the eat and drink commands, which may effect the player’s health.

Part 15.1: Add command#

In this section we’ll add the eat and drink commands.

Demo

A. In test_game.py define test_do_consume_no_args()#

We’re going to use the do_consume() function for both the eat and drink commands. This means that later when we define the do_consume() function, it will take two arguments: the action (either "eat", or "drink"), and args (the usual list of words typed by the user).

First we’ll write a parametrized test which we expect to fail. It will have one parameter action with the two cases "eat" and "drink".

It should test that when you call do_consume(action, []) a debug and error message is printed.

Need help?
  1. [ ] Import the do_consume function.

  2. [ ] Add test_do_consume() function with two parameters: capsys and action

  3. [ ] Immediately above the def line call @pytest.mark.parametrize() with the following arguments:

    • A list containing the string "action"

    • A list containing the strings: "eat" and "drink"

  4. [ ] Call do_consume() with two arguments: action and an empty list.

  5. [ ] Assign the results of capsys.readouterr().out to the variable output

  6. [ ] Write an assert statement that checks that the debug message "Trying to ACTION: []" is in output

  7. [ ] Write an assert statement that checks that an error message like "What do you want to ACTION?" is in output

  8. [ ] Run your tests. They should fail.

Code
Listing 825 test_game.py#
 6from adventure import (
 7    MAX_HEALTH,
 8    debug,
 9    do_consume,
10    do_drop,
11    do_pet,
12    do_read,
13    error,
14    header,
15    health_change,
16    inventory_change,
17    is_for_sale,
18    place_add,
19    place_has,
20    player_has,
21    wrap,
22    write,
23)
Listing 826 test_game.py#
649@pytest.mark.parametrize("action", ["eat", "drink"])
650def test_do_consume_no_args(capsys, action):
651    # WHEN: The player types drink without an item
652    do_consume(action, [])
653
654    output = capsys.readouterr().out
655
656    # THEN: The debug message should be in output
657    assert f"Trying to {action}: []" in output, \
658        "Debug message should be in output"
659
660    # AND: An error message should be printed
661    assert f"What do you want to {action}?" in output, \
662        "User error should be in output"

B. In adventure.py define do_consume()#

Now we’ll add the do_consume() function to our game. Unlike our other do_ functions it will take two arguments: the action (either "eat", or "drink"), and args (the usual list of words typed by the user).

It should print a debug message, then it should check if args is empty and if so print an error message then return.

Need help?
  1. [ ] Add a do_consume() function with two parameters: action and args.

  2. [ ] In it, use the debug() function to print something like "Trying to ACTION: args.".

  3. [ ] Check if args is falsy. If so, print an error message and return.

  4. [ ] Run your tests again. They should now pass.

do_consume()
Listing 827 adventure.py#
739def do_consume(action, args):
740    """Eat or drink something"""
741    debug(f"Trying to {action}: {args}")
742
743    if not args:
744        error(f"What do you want to {action}?")
745        return

C. In adventure.py modify main(): add eat and drink#

Finally, add the code in main() so that when the player types "eat" or "drink", the do_consume() function is called.

Need help?
  1. [ ] Add an elif that checks if command is "eat" or "drink".

    • [ ] if so, call do_consume() and pass the command and args.

main()
Listing 828 adventure.py in main()#
831        if command in ("q", "quit", "exit"):
832            do_quit()
833
834        elif command in ("shop"):
835            do_shop()
836
837        elif command in ("g", "go"):
838            do_go(args)
839
840        elif command in ("x", "exam", "examine"):
841            do_examine(args)
842
843        elif command in ("l", "look"):
844            do_look()
845
846        elif command in ("t", "take", "grab"):
847            do_take(args)
848
849        elif command in ("i", "inventory"):
850            do_inventory()
851
852        elif command in ("r", "read"):
853            do_read(args)
854
855        elif command == "drop":
856            do_drop(args)
857
858        elif command == "buy":
859            do_buy(args)
860
861        elif command == "pet":
862            do_pet(args)
863
864        elif command in ("eat", "drink"):
865            do_consume(command, args)
866
867        else:
868            error("No such command.")
869            continue

Part 15.2: Is the item in inventory?#

In this section we’ll check to make sure the item is in the player’s inventory.

Demo

A. In test_game.py define test_do_consume_not_in_inventory()#

In this section we’ll write a parametrized test which we expect to fail. It will have one parameter action with the two cases "eat" and "drink".

The function should test that if the player tries to eat or drink something that is not in inventory an error message will be printed.

Need help?

1. Define a parametrized test_do_consume_not_in_inventory() function

  1. [ ] Add test_do_consume_not_in_inventory() function with two parameters: capsys and action.

  2. [ ] Immediately above the def line call @pytest.mark.parametrize() with the following arguments:

    • The string "action"

    • A list that contains the strings "eat" and "drink"

2. GIVEN: The player does not have an item in inventory

  • [ ] Set adventure.PLAYER["inventory"] to an empty dictionary

3. WHEN: You call do_consume() with that item

  • [ ] Call do_consume() two arguments: action, and a list containing a string like "something tasty"

  • [ ] Assign the results of capsys.readouterr().out to the variable output

4. THEN: An error message should be printed

  • [ ] assert that an error message like "Sorry, you don't have any 'something tasty' to ACTION." is in output

4. Run your tests. They should fail.

test_do_consume_not_in_inventory()
Listing 829 test_game.py#
665@pytest.mark.parametrize("action", ["eat", "drink"])
666def test_do_consume_not_in_inventory(capsys, action):
667    # GIVEN: The player does not have an item in inventory
668    adventure.PLAYER["inventory"] = {}
669
670    # WHEN: You call do_consume() with that item
671    do_consume(action, ["something tasty"])
672
673    output = capsys.readouterr().out
674
675    # THEN: An error message should be printed
676    assert f"Sorry, you don't have any 'something tasty' to {action}." in output, \
677        "User error should be in output"

B. In adventure.py modify do_consume(): check inventory#

Now we’ll modify do_consume() function to check that the player has the current item in inventory. If not an error message will be printed and the function will return.

Need help?
  1. [ ] Join all items in args into a single string seperated by spaces, and make it lowercase. Assign it to the variable name.

  2. [ ] Write an if statement that checks if the player has the item. If not:

    • [ ] Use the error() function to print a message like:
      "Sorry, you don't have any name to action."

    • [ ] return

do_consume()
Listing 830 adventure.py#
739def do_consume(action, args):
740    """Eat or drink something"""
741    debug(f"Trying to {action}: {args}")
742
743    if not args:
744        error(f"What do you want to {action}?")
745        return
746
747    # get the item entered by the user and make it lowercase
748    name = " ".join(args).lower()
749
750    # make sure the item is in this place or in the players inventory
751    if not player_has(name):
752        error(f"Sorry, you don't have any {name!r} to {action}.")
753        return

Part 15.3: Is the item consumable?#

In this section we’ll check to make sure the item can be eaten or drunk, based on if it has a "eat-message" or "drink-message" key.

Demo

A. In test_game.py define test_do_consume_cant_consume()#

In this section we’ll write a parametrized test which we expect to fail. It will have one parameter action with the two cases "eat" and "drink".

The function should test that if the player tries to eat or drink an item that does not have a "action-message" key an error message will be printed.

Need help?

1. Define a parametrized test_do_consume_cant_consume() function

  1. [ ] Add test_do_consume_cant_consume() function with two parameters: capsys and action.

  2. [ ] Immediately above the def line call @pytest.mark.parametrize() with the following arguments:

    • The string "action"

    • A list that contains the strings "eat" and "drink"

2. GIVEN: An item exists with no eat-message or drink-message key

  • [ ] Create a fake item in adventure.ITEMS with a key like "something tasty". The value should be a dictionary containing:

    • [ ] the key "name" and a value like "something tasty"

3. AND: That item is in the player’s inventory

  • [ ] Add the item to the player’s inventory

4. WHEN: You call do_consume() with that item

  • [ ] Call do_consume() with two arguments: action, and a list containing the key to your fake item, ie "something tasty"

  • [ ] Assign the results of capsys.readouterr().out to the variable output

4. THEN: An error message should be printed

  • [ ] assert that an error message like "Sorry, you can't action 'something tasty'." is in output

4. Run your tests. They should fail.

test_do_consume_cant_consume()
Listing 831 test_game.py#
680@pytest.mark.parametrize("action", ["eat", "drink"])
681def test_do_consume_cant_consume(capsys, action):
682    # GIVEN: An item exists with no eat-message or drink-message key
683    adventure.ITEMS["something tasty"] = {
684        "name": "something tasty"
685    }
686
687    # AND: That item is in the player's inventory
688    inventory_change("something tasty")
689
690    # WHEN: You call do_consume() with that item
691    do_consume(action, ["something tasty"])
692
693    output = capsys.readouterr().out
694
695    # THEN: An error message should be printed
696    assert f"Sorry, you can't {action} 'something tasty'." in output, \
697        "User error should be in output"

B. In adventure.py modify do_consume(): check item#

Now we’ll modify do_consume() function to check to make sure the item can be eaten or drunk, based on if it has a "eat-message" or "drink-message" key. If not an error message will be printed and the function will return.

Need help?
  1. [ ] Get the item using by calling get_item() with the argument name and assign the result to the variable item.

  2. [ ] Write an if statement that checks if the key "action-message" is in the item dictionary. If not:

    • [ ] Use the error() function to print a message like:
      "Sorry, you can't action 'name'.'"

    • [ ] return

  3. [ ] Run your tests. They should pass.

do_consume()
Listing 832 adventure.py#
752def do_consume(action, args):
753    """Eat or drink something"""
754    debug(f"Trying to {action}: {args}")
755
756    if not args:
757        error(f"What do you want to {action}?")
758        return
759
760    # get the item entered by the user and make it lowercase
761    name = " ".join(args).lower()
762
763    # make sure the item is in this place or in the players inventory
764    if not player_has(name):
765        error(f"Sorry, you don't have any {name!r} to {action}.")
766        return
767
768    # get the item dictionary
769    item = get_item(name)
770
771    # make sure it is an item you can consume
772    if f"{action}-message" not in item:
773        error(f"Sorry, you can't {action} {name!r}.")
774        return

Part 15.4: Modify health_change()#

In this section we’re going to modify the health_change() function so that it returns the amount that health was actually changed, which may be different than the amount argument.

This is so that if, for example, the player’s health is at 90 and they drink a potion that is +20 health, we can tell the player that they gained 10 health points.

A. In test_game.py modify test_health_change()#

In this section, we’ll add a parameter diff to the parametrization of the test_health_change() function. Then we’ll need to save the result of calling health_change() and assert that it is equal to diff.

(You may also want to rename result to health, so we can use the variable name result for the returned value.)

Need help?
  1. [ ] Change the name result to health in:

  • The first argument to @pytest.mark.parametrize

  • The parameters to the test_health_change() function

  • The assert statement

  1. [ ] Add a parameter diff to:

  • The first argument to @pytest.mark.parametrize

  • The parameters to the test_health_change() function

  1. [ ] Add a number for diff to each of the parametrization tuples, which should be the difference between their start health and end health.

  2. [ ] Assign the value returned from of health_change() to the variable result

  3. [ ] In the THEN section, add: AND: The value returned should be the difference in points \

  • assert that result is equal to diff

  1. Run your tests. They should fail.

test_health_change()
Listing 833 test_game.py#
57@pytest.mark.parametrize(
58    "start, amount, health, diff, message", [
59        (50, 10, 60, 10, "a positive number should be added"),
60        (50, -10, 40, -10, "a negative number should be subtracted"),
61        (20, -30, 0, -20, "the min health should be 0"),
62        (90, 20, MAX_HEALTH, 10, f"the max health should be {MAX_HEALTH}"),
63    ]
64)
65def test_health_change(start, amount, health, diff, message):
66    # GIVEN: The player has some health
67    adventure.PLAYER["health"] = start
68
69    # WHEN: You call health_change()
70    result = health_change(amount)
71
72    # THEN: The player health should be adjusted
73    assert adventure.PLAYER["health"] == health, message
74
75    # AND: The value returned should be the difference in points
76    assert result == diff, \
77        "The value returned should be the difference in health"

B. In adventure.py modify health_change(): return difference#

Now we’ll modify the health_change() function to return the difference between the player’s health before and after it is changed.

Need help?
  1. [ ] At the beginning of the function, save the value of PLAYER["health"] to the variable before.

  2. [ ] At the end of the function return the result of subtracting before from PLAYER["health"]

health_change()
Listing 834 adventure.py#
348def health_change(amount: int) -> int:
349    """Add the (positive or negative) amount to health, but limit to 0-100.
350    Return the actual amount added to player health."""
351
352    # save the current player health
353    before = PLAYER["health"]
354
355    # change the player health
356    PLAYER["health"] += amount
357
358    # don't let health go below zero
359    if PLAYER["health"] < 0:
360        PLAYER["health"] = 0
361
362    # cap health
363    if PLAYER["health"] > MAX_HEALTH:
364        PLAYER["health"] = MAX_HEALTH
365
366    # return the difference
367    return PLAYER["health"] - before

Part 15.5: Print message with delay#

In this section we’re going to print the eat or drink message.

Demo

A. In test_game.py define test_do_consume()#

In this section we’ll write a test_do_consume() function.

The function should test that when a player tries to eat or drink a consumable item, the "eat-message" or "drink-message" is printed.

We’ll parameratize the test with the parameters action ("eat"” or "drink), and item (a dictionary to add to adventure.ITEMS).

Tip

You can set adventure.WIDTH to extra wide to avoid wrapping. That way when you assert "some string" in ouput you can be sure that all words are on the same line.

Need help?

1. Define a parametrized test_do_consume() function.

  1. [ ] Add test_do_consume() function with four parameters: capsys, action, and item.

  2. [ ] Immediately above the def line call @pytest.mark.parametrize() with the following arguments:

    • The string "action, item"

    • A list of tuples with each tuple on its own line, with values for:

      • action: "eat" or "drink"

      • item: a dictionary for the fake item to add to adventure.ITEMS. it should include keys for: "name", "health", and "eat-message" or "drink-message"

  3. [ ] Make two tuples, one for "eat" and one for "drink".

2. GIVEN: An item exists

  • [ ] Assign the variable name to the value item["name"]

  • [ ] Create a fake item in adventure.ITEMS with the key name. The value should be the item dictionary (from params).

3. AND: The player has the item in their inventory

  • [ ] Call inventory_change() with the argument name

4. AND: The width is set wide to avoid wrapping

  • [ ] set adventure.WIDTH to a large number like 200

5. WHEN: You call do_consume() with that item

  • [ ] call do_consume with the arguments action, and a list containing the item name.

  • [ ] assign the varible output to the results of calling capsys.readouterr().out

6. THEN: The message contents should be in output

  • [ ] assign the varible line to the first item in the item[f"{action}-message"] tuple

  • [ ] assert that line is in output

7. Run your tests. They should fail.

test_do_consume()
Listing 835 test_game.py#
704@pytest.mark.parametrize(
705    "action, item",
706    [
707        (
708            "drink",
709            {
710                "name": "atmosphere",
711                "drink-message": (
712                    "You drink in the warm and cheerful atmosphere.",
713                ),
714                "health": 5,
715            },
716        ),
717        (
718            "eat",
719            {
720                "name": "your feelings",
721                "eat-message": (
722                    "It's the wee hours of the morning,",
723                    "and you're eating your feelings.",
724                ),
725                "health": -5,
726            },
727        ),
728    ]
729)
730def test_do_consume(capsys, action, item):
731    # GIVEN: An item exists
732    name = item["name"]
733    adventure.ITEMS[name] = item
734
735    # AND: The player has the item in their inventory
736    inventory_change(name)
737
738    # AND: The width is set wide to avoid wrapping
739    adventure.WIDTH = 200
740
741    # WHEN: You call do_consume() with that item
742    do_consume(action, [name])
743
744    output = capsys.readouterr().out
745
746    # THEN: The message contents should be in output
747    line = item[f"{action}-message"][0]
748    assert f"  {line}" in output, \
749        f"The {action}-message should be printed."

B. In adventure.py modify do_consume(), at the end#

In this section we’ll modify the do_consume() function to wrap and print each item in the "eat-message" or "drink-message" and then sleep.

Need help?
  1. [ ] print a blank line

  2. [ ] Iterate over item[f"{action}-message"] with the variable sentence. In the for loop:

    • [ ] print sentence by calling the wrap() function

    • [ ] print a blank line

    • [ ] sleep for DELAY seconds

  3. [ ] Run your tests. They should pass.

do_consume()
Listing 836 adventure.py#
784def do_consume(action, args):
785    """Eat or drink something"""
786    debug(f"Trying to {action}: {args}")
787
788    if not args:
789        error(f"What do you want to {action}?")
790        return
791
792    # get the item entered by the user and make it lowercase
793    name = " ".join(args).lower()
794
795    # make sure the item is in this place or in the players inventory
796    if not player_has(name):
797        error(f"Sorry, you don't have any {name!r} to {action}.")
798        return
799
800    # get the item dictionary
801    item = get_item(name)
802
803    # make sure it is an item you can consume
804    if f"{action}-message" not in item:
805        error(f"Sorry, you can't {action} {name!r}.")
806        return
807
808    print()
809    for sentence in item[f"{action}-message"]:
810        wrap(sentence)
811        print()
812        sleep(DELAY)

C. In adventure.py modify ITEMS and PLACES#

In this section we’ll add or modify items in ITEMS to include an "eat-message" or "drink-message" key. Then if needed we’ll add those items to PLACES.

ITEMS
Listing 837 adventure.py#
163ITEMS = {
164    "elixir": {
165        "key": "elixir",
166        "name": "healing elixir",
167        "description": "a magical elixir that will heal what ails ya",
168        "price": -10,
169        "drink-message": (
170            "You uncork the bottle.",
171            "The swirling green liquid starts to bubble.",
172            "You hesitatingly bring the bottle to your lips...",
173            "then quickly down the whole thing!",
174            "Surprisingly, it tastes like blueberries.",
175            "You feel an odd tingling sensation starting at the top of your head... ",
176            "...moving down your body...",
177            "...down to the tips of your toes.",
178        ),
179    },
180    "dagger": {
181        "key": "dagger",
182        "name": "a dagger",
183        "description": "a 14 inch dagger with a double-edged blade",
184        "price": -25,
185    },
186    "water": {
187        "key": "water",
188        "name": "bottle of water",
189        "can_take": True,
190        "description": (
191            "A bottle of water."
192        ),
193        "drink-message": (
194            "You pull the cork from the waxed leather bottle.",
195            "You take a deep drink of the cool liquid.",
196            "You feel refreshed.",
197        ),
198    },
199    "mushroom": {
200        "key": "mushroom",
201        "name": "a mushroom",
202        "can_take": True,
203        "description": (
204            "A mushroom."
205        ),
206        "eat-message": (
207            "You shove the whole mushroom in your mouth...",
208            "Things start to look swirllllly...",
209            "Your tummy doesn't feel so good.",
210        ),
211    },
212    "desk": {
213        "key": "desk",
214        "name": "a desk",
215        "description": (
216            "A wooden desk with a large leather-bound book open on "
217            "its surface."
218        ),
219    },
220    "book": {
221        "key": "book",
222        "can_take": True,
223        "name": "a book",
224        "description": (
225            "A hefty leather-bound tome open to an interesting passage."
226        ),
227        "title": (
228            "The book is open to a page that reads:"
229        ),
230        "message": (
231            "At the edge of the woods is a cave that is home to a three "
232            "headed dragon, each with a different temperament. ",
233
234            "Legend says that if you happen upon the dragon sleeping, the "
235            "brave may pet one of its three heads. ",
236
237            "Choose the right head and you will be rewarded with great "
238            "fortunes. ",
239
240            "But beware, choose poorly and it will surely mean your doom!",
241        ),
242    },
243    "gems": {
244        "key": "gems",
245        "name": "gems",
246        "description": (
247            "A pile of sparkling gems."
248        ),
249    },
250    "dragon": {
251        "key": "dragon",
252        "name": "a three-headed dragon",
253        "description": (
254            f"A colossal dragon with heads of {', '.join(COLORS[0:-1])}, "
255            f"and {COLORS[2]}."
256        ),
257    },
258}
259
PLACES
Listing 838 adventure.py#
 85PLACES = {
 86    "home": {
 87        "key": "home",
 88        "name": "Your Cottage",
 89        "east": "town-square",
 90        "description": "A cozy stone cottage with a desk and a neatly made bed.",
 91        "items": ["desk", "book", "water"],
 92    },
 93    "town-square": {
 94        "key": "town-square",
 95        "name": "The Town Square",
 96        "west": "home",
 97        "east": "woods",
 98        "north": "market",
 99        "description": (
100            "A large open space surrounded by buildings with a burbling "
101            "fountain in the center."
102        ),
103    },
104    "market": {
105        "key": "market",
106        "name": "The Market",
107        "south": "town-square",
108        "items": ["elixir", "dagger"],
109        "can": ["shop", "buy"],
110        "description": (
111            "A tidy store with shelves full of goods to buy. A wooden hand "
112            "painted menu hangs on the wall."
113        ),
114    },
115    "woods": {
116        "key": "woods",
117        "name": "The Woods",
118        "east": "hill",
119        "west": "town-square",
120        "description": (
121            "A dirt road meanders under a canopy of autumn leaves in brilliant "
122            "hues of gold and crimson.",
123
124            "You hear a stream burbling somewhere out of sight. Leaves crunch "
125            "under your feet on the sun dappled forest floor.",
126
127            "You see an ancient moss-covered hollow tree, its gnarled and twisted "
128            "branches looming over you. On the opposite side, a fallen log juts "
129            "partway into the road.",
130        ),
131        "items": ["mushroom"],
132    },
133    "hill": {
134        "key": "hill",
135        "name": "A grassy hill",
136        "west": "woods",
137        "south": "cave",
138        "description": (
139            "A winding path leads up the slope of a grassy hill. The air is "
140            "warm here.",
141            "At the top of the hill, you see that the path continues to the "
142            "down to the south. In that direction you can make out a cave by "
143            "the shore of a lake."
144        ),
145        "items": ["dragon"],
146    },
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    },
161}
162

Part 15.6: Consume#

In this section we’ll make sure that when you eat or drink something it is removed from inventory and your health is changed accordingly.

Demo

A. In test_game.py modify test_do_consume()#

In this section we’ll modify the test_do_consume() function to test that the player’s health and inventory has changed and that a message is printed to tell them so.

In order to do so we’ll add two parameters to the test: health (for the player’s health after consuming) and message for the text that will be printed about the change.

Need help?

1. Modify test_do_consume() to add parameters for health and message.

  1. [ ] In the first argument of @pytest.mark.parametrize() add the parameters "health, message" to the end of the string.

  2. [ ] Add values for each tuple:

    • A number like 55, for the amount of health the player will have after consuming the item

    • A string like "lost 5" for part of the text that the player will see

  3. [ ] In the definition of test_do_consume() add the parameters health and message.

2. [ ] Under GIVEN add : AND: The player has a certain amount of health

[ ] Set adventure.PLAYER["health"] to a specific number like 50

3. [ ] Under THEN add : The player’s health should be changed

[ ] Assert that adventure.PLAYER["health"] equals health

4. [ ] Under THEN add : The item should be removed from inventory

[ ] Assert that the player does not have name

5. [ ] Under THEN add : A message about the action take should be printed

[ ] Assert that f"You {message} point" is not in output

6. [ ] Run your tests. They should fail.

test_do_consume()
Listing 839 test_game.py#
704@pytest.mark.parametrize(
705    "action, item, health, message",
706    [
707        (
708            "drink",
709            {
710                "name": "atmosphere",
711                "drink-message": (
712                    "You drink in the warm and cheerful atmosphere.",
713                ),
714                "health": 5,
715            },
716            55,
717            "gained 5",
718        ),
719        (
720            "eat",
721            {
722                "name": "your feelings",
723                "eat-message": (
724                    "It's the wee hours of the morning,",
725                    "and you're eating your feelings.",
726                ),
727                "health": -5,
728            },
729            45,
730            "lost 5",
731        ),
732    ]
733)
734def test_do_consume(capsys, action, item, health, message):
735    # GIVEN: An item exists
736    name = item["name"]
737    adventure.ITEMS[name] = item
738
739    # AND: The player has the item in their inventory
740    inventory_change(name)
741
742    # AND: The player has a certain amount of health
743    adventure.PLAYER["health"] = 50
744
745    # AND: The width is set wide to avoid wrapping
746    adventure.WIDTH = 200
747
748    # WHEN: You call do_consume() with that item
749    do_consume(action, [name])
750
751    output = capsys.readouterr().out
752
753    # THEN: The message contents should be in output
754    line = item[f"{action}-message"][0]
755    assert f"  {line}" in output, \
756        f"The {action}-message should be printed."
757
758    # AND: The player's health should be changed
759    assert adventure.PLAYER["health"] == health, \
760        "The player's health should be changed."
761
762    # AND: The item should be removed from inventory
763    assert not player_has(name), \
764        "The item should be removed from inventory."
765
766    # AND: A message about the action take should be printed
767    assert f"You {message} point" in output, \
768        "A message about the action take should be printed"

B. In adventure.py modify do_consume()#

In this section we’ll modify the do_consume() function so that it removes the item from inventory, modifies the player’s health, and prints a message about what happened.

Need help?
  1. [ ] Call inventory_change() with the arguments name and -1.

  2. [ ] Call item.get() with the arguments "health" and 0 and assign the returned value to the variable health.

  3. [ ] Call health_change() with the argument health and assign the returned result to the variable difference.

  4. [ ] If health is greater than zero:

    • [ ] Print a message like: \

    • "You lost POINTS point(s)."

  5. [ ] Otherwise, if health is less than zero:

    • "You gained difference point(s)."

  6. [ ] Run your tests. They should pass.

do_consume()
Listing 840 adventure.py#
787def do_consume(action, args):
788    """Eat or drink something"""
789    debug(f"Trying to {action}: {args}")
790
791    if not args:
792        error(f"What do you want to {action}?")
793        return
794
795    # get the item entered by the user and make it lowercase
796    name = " ".join(args).lower()
797
798    # make sure the item is in this place or in the players inventory
799    if not player_has(name):
800        error(f"Sorry, you don't have any {name!r} to {action}.")
801        return
802
803    # get the item dictionary
804    item = get_item(name)
805
806    # make sure it is an item you can consume
807    if f"{action}-message" not in item:
808        error(f"Sorry, you can't {action} {name!r}.")
809        return
810
811    inventory_change(name, -1)
812    health = item.get("health", 0)
813    difference = health_change(health)
814
815    print()
816    for sentence in item[f"{action}-message"]:
817        wrap(sentence)
818        print()
819        sleep(DELAY)
820
821    if health < 0:
822        write(f"You lost {abs(difference)} point(s).")
823    elif health > 0:
824        write(f"You gained {difference} point(s).")

C. In adventure.py modify ITEMS#

In this section we’ll modify any of the item dictionaries in ITEMS that have a "eat-message" or "drink-message" key to add a "health" key. The value should be the number of health points to add or remove.

Play your game and eat or drink the relevant items to test it out!

ITEMS
Listing 841 adventure.py#
163ITEMS = {
164    "elixir": {
165        "key": "elixir",
166        "name": "healing elixir",
167        "description": "a magical elixir that will heal what ails ya",
168        "price": -10,
169        "drink-message": (
170            "You uncork the bottle.",
171            "The swirling green liquid starts to bubble.",
172            "You hesitatingly bring the bottle to your lips...",
173            "then quickly down the whole thing!",
174            "Surprisingly, it tastes like blueberries.",
175            "You feel an odd tingling sensation starting at the top of your head... ",
176            "...moving down your body...",
177            "...down to the tips of your toes.",
178        ),
179        "health": 20,
180    },
181    "dagger": {
182        "key": "dagger",
183        "name": "a dagger",
184        "description": "a 14 inch dagger with a double-edged blade",
185        "price": -25,
186    },
187    "water": {
188        "key": "water",
189        "name": "bottle of water",
190        "can_take": True,
191        "description": (
192            "A bottle of water."
193        ),
194        "drink-message": (
195            "You pull the cork from the waxed leather bottle.",
196            "You take a deep drink of the cool liquid.",
197            "You feel refreshed.",
198        ),
199        "health": 5,
200    },
201    "mushroom": {
202        "key": "mushroom",
203        "name": "a mushroom",
204        "can_take": True,
205        "description": (
206            "A mushroom."
207        ),
208        "eat-message": (
209            "You shove the whole mushroom in your mouth...",
210            "Things start to look swirllllly...",
211            "Your tummy doesn't feel so good.",
212        ),
213        "health": -15,
214    },
215    "desk": {
216        "key": "desk",
217        "name": "a desk",
218        "description": (
219            "A wooden desk with a large leather-bound book open on "
220            "its surface."
221        ),
222    },
223    "book": {
224        "key": "book",
225        "can_take": True,
226        "name": "a book",
227        "description": (
228            "A hefty leather-bound tome open to an interesting passage."
229        ),
230        "title": (
231            "The book is open to a page that reads:"
232        ),
233        "message": (
234            "At the edge of the woods is a cave that is home to a three "
235            "headed dragon, each with a different temperament. ",
236
237            "Legend says that if you happen upon the dragon sleeping, the "
238            "brave may pet one of its three heads. ",
239
240            "Choose the right head and you will be rewarded with great "
241            "fortunes. ",
242
243            "But beware, choose poorly and it will surely mean your doom!",
244        ),
245    },
246    "gems": {
247        "key": "gems",
248        "name": "gems",
249        "description": (
250            "A pile of sparkling gems."
251        ),
252    },
253    "dragon": {
254        "key": "dragon",
255        "name": "a three-headed dragon",
256        "description": (
257            f"A colossal dragon with heads of {', '.join(COLORS[0:-1])}, "
258            f"and {COLORS[2]}."
259        ),
260    },
261}
262

Part 15.7: Add health to examine#

In this section we’ll add health to the examine command.

Demo

A. In test_game.py add test_examine()#

In this section we’ll add a test for the do_examine() function if it does not already exist. Then we’ll modify the test to make sure that the health is displayed.

Need help? (skip if test_do_examine is defined)

1. Import do_examine

2. Define the test_do_examine() function and include the capsys fixture.

[ ] Define test_do_examine() with the parameter capsys

3. [ ] GIVEN: An item exists

  • [ ] Create a dictionary for a fake item and assign it to the variable item. The dictionary should include keys for: "name", "description" and "price".

  • [ ] Add the item to the adventure.ITEMS dictionary

4. [ ] AND: A place has the item

[ ] Add a fake place to the adventure.PLACES dictionary. It should include keys for "name" and "items". Make sure to add the fake item to the place.

5. [ ] AND: The player is that place

[ ] Modify adventure.PLAYER to put the player in your fake place.

6. [ ] WHEN: do_examine() is called

  • [ ] Call do_examine() with a list containing the key to your fake item.

  • [ ] Assign the variable output to the result of calling capsys.readouterr().out

7. [ ] THEN: a debug message should be printed

[ ] Assert that a string like "Trying to examine: ['cookie']" is in output

8. [ ] AND: The item name should be printed

[ ] Assert that item["name"] is in output

9. [ ] AND: The item description should be printed

[ ] Assert that item["description"] is in output

10. [ ] AND: The price should be printed

[ ] Assert that a string like "5 gems" is in output

11. [ ] Run your tests. They should pass.

Need help?

1. [ ] Modify: GIVEN: An item exists to add "health"

[ ] Modify your fake item dictionary to include a key for "health"

2. [ ] Under THEN add : AND: The health points should be printed

[ ] Assert that a string like "+5 health" is in output

3. [ ] Run your tests. They should fail.

test_do_examine()
Listing 842 test_game.py#
365def test_do_examine(capsys):
366    # GIVEN: An item exists
367    item = {
368        "name": "a cookie",
369        "description": "A chocolate chip cookie.",
370        "health": 5,
371        "price": 5,
372    }
373    adventure.ITEMS["cookie"] = item
374
375    # AND: A place has the item
376    adventure.PLACES["bakery"] = {
377        "name": "bakery",
378        "items": ["cookie"],
379    }
380
381    # AND: The player is that place
382    adventure.PLAYER["place"] = "bakery"
383
384    # WHEN: do_examine() is called
385    do_examine(["cookie"])
386
387    output = capsys.readouterr().out
388
389    # THEN: a debug message should be printed
390    assert "Trying to examine: ['cookie']" in output, \
391        "Debug message should be in output"
392
393    # AND: The item name should be printed
394    assert item["name"] in output, \
395        "The item name should be printed"
396
397    # AND: The item description should be printed
398    assert item["description"] in output, \
399        "The item description should be printed"
400
401    # AND: The price should be printed
402    assert "5 gems" in output, \
403        "The price should be printed"
404
405    # AND: The health points should be printed
406    assert "+5 health" in output, \
407        "The health points should be printed"

B. In adventure.py modify do_examine()#

In this section we’ll modify the do_examine() function to print the health gained or lost from an item, if any.

In my examine display I have the item price, inventory quantity, and health all on the same line, right aligned.

To accomplish this I put them all in a list named stats, then joined them with the | character, then called .rjust() on the result to print it. Depending how you want to format your examine output, you may or may not want to do it the same way.

Need help?
  1. [ ] Create an empty list assigned to the variable stats.

  2. [ ] If the place can "shop" and the place has the item and the item is for sale:

    • [ ] Append a string like "Price: PRICE gems" to stats.

  3. [ ] Otherwise, if the item is in the player’s inventory:

    • [ ] Append a string like "(x QUANTITY)" to stats.

  4. [ ] If the item has a "health" key:

    • [ ] Append a string like "+HEALTH health" to stats

  5. [ ] If the stats list is not empty:

    • [ ] Assign the variable text to the stats list joined by a string like:
      " | ".

    • [ ] Call the .rjust() method with the argument WIDTH - MARGIN and print it.

    • [ ] Print a blank line.

  6. [ ] Run your tests. They should pass.

do_examine()
Listing 843 adventure.py#
539def do_examine(args):
540    """Look at an item in the current place."""
541
542    debug(f"Trying to examine: {args}")
543
544    # make sure the player said what they want to examine
545    if not args:
546        error("What do you want to examine?")
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    # variable for the health/price/inventory line
561    stats = []
562
563    # the price if we're in the market
564    if place_can("shop") and place_has(name) and is_for_sale(item):
565        stats.append(f"Price: {abs(item['price'])} gems")
566
567    # the quantity if the item is from inventory
568    elif player_has(name):
569        stats.append(f"(x{PLAYER['inventory'][name]})")
570
571    # add the health if applicable
572    if "health" in item:
573        stats.append(f"{item['health']:+} health")
574
575    # print the item information
576    header(item["name"].title())
577
578    # right-justify the price/quantity
579    if stats:
580        text = " | " + " | ".join(stats) + " | "
581        print(text.rjust(WIDTH - MARGIN))
582        print()
583
584    wrap(item["description"].capitalize().rstrip(".") + ".")