Part 13: Health
Contents
Part 13: Health#
In this section we’ll add player health between 1
- 100
with a health
progress bar on the inventory command.
Part 13.1: Add health_change()
#
In this section we’ll start the health_change()
function which will work very
much like inventory_change()
. It should:
Take one
int
argument:amount
.Add the
amount
toPLAYER["health"]
.
A. In test_game.py
define test_health_change()
#
Write the test_health_change()
function to test health_change()
.
Need help?
Import
health_change
Add the function
test_health_change()
GIVEN: the player has health
Set
PLAYER["health"]
to a positive number between1
-100
WHEN: you call
health_change()
with a positive number*THEN: a positive number should be added to player health
assert that
PLAYER["health"]
is now correctRun your test. It should fail.
Code
6from adventure import (
7 debug,
8 do_drop,
9 do_read,
10 error,
11 header,
12 health_change,
13 inventory_change,
14 is_for_sale,
15 place_has,
16 player_has,
17 place_add,
18 wrap,
19 write,
20)
52def test_health_change():
53 # GIVEN: The player has some health
54 adventure.PLAYER["health"] = 50
55
56 # WHEN: You call health_change() with a positive value
57 health_change(10)
58
59 # THEN: The amount should be added to player health
60 assert adventure.PLAYER["health"] == 60, \
61 "a positive number should be added to player health"
62
63
B. In adventure.py
define health_change()
#
Write the health_change()
function.
Need help?
[ ]
Add the functionhealth_change()
with one argumentamount
[ ]
Addamount
toPLAYER["health"]
[ ]
Run your tests. They should now pass.
Code
222def health_change(amount: int):
223 """Add the following (positive or negative) amount to health, but limit to 0-100"""
224 PLAYER["health"] += amount
225
226
Part 13.2: Parameterize the test#
In this section we’ll modify the test_health_change()
function to use
parametrization. This allows us to use the same test for several different
test cases which are stored and run as a list of arguments to a single
test function.
If you’re not already familiar with parameterization, see Pytest Tests > Parametrization.
A. In test_game.py
modify test_health_change()
: parameterize#
In this section we will extract the values that we expect to be different in
new test cases and turn them into four parameters and variables: start
,
amount
, result
and message
.
At the end of this section we will have a parameratized test with exactly one test case representing the original test. Functionally, nothing will change but we’ll be set up to more easily add new test cases.
Change the following values to variables. (Either keep the original line commented out or otherwise make note the original values.)
1def test_health_change(): 2 # GIVEN: The player has some health 3 adventure.PLAYER["health"] = 50 4 5 # WHEN: You call health_change() 6 health_change(10) 7 8 # THEN: The player health should be adjusted 9 assert adventure.PLAYER["health"] == 60, \ 10 "the player health should be adjusted"
Under GIVEN: Change the value assigned to
PLAYER["health"]
to the variablestart
.Under WHEN: Change the argument passed to
health_change()
the variableamount
Under THEN: In the assert statement change the value that should equal
PLAYER["health"]
to the variableresult
Under THEN: Change the assert message to the variable
message
If you don’t already have an assert message add one that is similar to the THEN description. ie:
"a positive number should be added to player health"
Add the four variables (
start
,amount
,result
,message
) as parameters to thetest_health_change()
functionImmediately above the
def
line call@pytest.mark.parametrize()
with the following arguments:A list containing the name of all four variables in the same order as above
A list of tuples with each tuple on its own line
The first tuple should contain the values extracted from step #1 in the same order as their cooresponding parameters:
start
,amount
,result
,message
.
Change your GIVEN/WHEN/THEN comments to be more generic so that they can apply to all test cases. For example, change:
# THEN: The amount should be added to player health
To:
# THEN: The player health should be adjusted
Run your test. It should pass.
Code
52@pytest.mark.parametrize(
53 ["start", "amount", "result", "message"], [
54 (50, 10, 60, "a positive number should be added"),
55 ]
56)
57def test_health_change(start, amount, result, message):
58 # GIVEN: The player has some health
59 adventure.PLAYER["health"] = start
60
61 # WHEN: You call health_change()
62 health_change(amount)
63
64 # THEN: The player health should be adjusted
65 assert adventure.PLAYER["health"] == result, message
B. In test_game.py
above test_health_change()
: Add another test case#
In this section we are going to add a test case for passing a negative value to
health_change()
to effectively subtract that amount from player health.
If we were to write this as a non-parameratized test it would look something like:
64def test_health_change_subtract():
65 # GIVEN: The player has some health
66 adventure.PLAYER["health"] = 50
67
68 # WHEN: You call health_change() with a negative value
69 health_change(-10)
70
71 # THEN: The amount should be subtracted from player health
72 assert adventure.PLAYER["health"] == -40, \
73 "a negative number should be subtracted from player health"
Instead of writing this test though, we will add a test case tuple that
contains the start
, amount
, result
and message
values that we would
otherwise put in a test_health_change_subtract()
test.
[ ]
Add a new tuple to the list of test case tuples that contains values for each of the four parameters:Parameter
Value
start
a positive
int
amount
a negative
int
result
start
-amount
message
a
str
describing this test case[ ]
Run your test. It should pass.
Code
52@pytest.mark.parametrize(
53 ["start", "amount", "result", "message"], [
54 (50, 10, 60, "a positive number should be added"),
55 (50, -10, 40, "a negative number should be subtracted"),
56 ]
57)
58def test_health_change(start, amount, result, message):
59 # GIVEN: The player has some health
60 adventure.PLAYER["health"] = start
61
62 # WHEN: You call health_change()
63 health_change(amount)
64
65 # THEN: The player health should be adjusted
66 assert adventure.PLAYER["health"] == result, message
Part 13.3: Add health limits#
In this section we’re going to modify the health_change()
function so that
PLAYER["health"]
is always between 0
and 100
.
A. In test_game.py
above test_health_change()
: ensure health > 0#
In this section we will add a test case to ensure that even if start
- amount
is
less than zero, player health is set to zero instead of a negative number
If we were to write this as a non-parameratized test, it would look something like:
test_health_change_minimum_health()
64def test_health_change_minimum_health():
65 # GIVEN: The player has some health
66 adventure.PLAYER["health"] = 20
67
68 # WHEN: You call health_change() with a negative number
69 # which would make player health less than zero
70 health_change(-30)
71
72 # THEN: The player health should be zero
73 assert adventure.PLAYER["health"] == 0, \
74 "the minimum health should be 0"
[ ]
Add a new tuple to the list of test case tuples that contains values for each of the four parameters:Parameter
Value
start
a positive
int
amount
a negative
int
that would make player health less than0
result
0
message
a
str
describing this test case[ ]
Run your test. It should fail.
Code
53@pytest.mark.parametrize(
54 ["start", "amount", "result", "message"], [
55 (50, 10, 60, "a positive number should be added"),
56 (50, -10, 40, "a negative number should be subtracted"),
57 (20, -30, 0, "the minimum health should be 0"),
B. In adventure.py
modify health_change()
#
In this section we will modify health_change()
to make the above test case
pass.
[ ]
At the end of the function, afterPLAYER["health"]
is changed, check ifPLAYER["health"]
is less than zero[ ]
if so, setPLAYER["health"]
to zero
[ ]
Run your tests. They should pass
Code
224def health_change(amount: int):
225 """Add the following (positive or negative) amount to health, but limit to 0-100"""
226 PLAYER["health"] += amount
227
228 # don't let health go below zero
229 if PLAYER["health"] < 0:
230 PLAYER["health"] = 0
231
C. At the top of adventure.py
#
In this section we’ll add a global variable MAX_HEALTH
to keep track of the
maximum value for PLAYER["health"]
.
[ ]
Add global variableMAX_HEALTH
and set it to100
Code
24import textwrap
25
26from console import fg, fx
27
28WIDTH = 45
29
30MARGIN = 2
31
32DEBUG = True
33
34MAX_HEALTH = 100
35
D. In test_game.py
above test_health_change()
: ensure MAX_HEALTH
#
In this section we’ll add a test case to ensure that even if start
+ result
is greater than MAX_HEALTH
, player health will be set to MAX_HEALTH
.
If we were to write this as a non-parameratized test it would look something like:
test_health_change_maximum_health()
64def test_health_change_maximum_health():
65 # GIVEN: The player has some health
66 adventure.PLAYER["health"] = 90
67
68 # WHEN: You call health_change() with a positive number
69 # which would make player health more than the maximum
70 health_change(20)
71
72 # THEN: The player health should be MAX_HEALTH
73 assert adventure.PLAYER["health"] == MAX_HEALTH, \
74 f"the maximum health should be {MAX_HEALTH}"
[ ]
ImportMAX_HEALTH
[ ]
Add a new tuple to the list of test case tuples that contains values for each of the four parameters:Parameter
Value
start
a positive
int
amount
a positive
int
that would make player health more than100
result
MAX_HEALTH
message
a
str
describing this test case[ ]
Run your test. It should fail.
Code
6from adventure import (
7 debug,
8 do_drop,
9 do_read,
10 error,
11 header,
12 health_change,
13 inventory_change,
14 is_for_sale,
15 place_has,
16 player_has,
17 place_add,
18 wrap,
19 write,
20 MAX_HEALTH,
21)
53@pytest.mark.parametrize(
54 ["start", "amount", "result", "message"], [
55 (50, 10, 60, "a positive number should be added"),
56 (50, -10, 40, "a negative number should be subtracted"),
57 (20, -30, 0, "the minimum health should be 0"),
58 (90, 20, MAX_HEALTH, f"the max health should be {MAX_HEALTH}"),
59 ]
60)
61def test_health_change(start, amount, result, message):
E. In adventure.py
modify health_change()
#
In this section we will modify health_change()
to make the above test case
pass.
[ ]
At the end of the function, check ifPLAYER["health"]
is greater thanMAX_HEALTH
[ ]
if so, setPLAYER["health"]
toMAX_HEALTH
[ ]
Run your tests. They should pass
Code
224def health_change(amount: int):
225 """Add the following (positive or negative) amount to health, but limit to 0-100"""
226 PLAYER["health"] += amount
227
228 # don't let health go below zero
229 if PLAYER["health"] < 0:
230 PLAYER["health"] = 0
231
232 # cap health
233 if PLAYER["health"] > MAX_HEALTH:
234 PLAYER["health"] = MAX_HEALTH
235
236
Part 13.4: UX Changes#
In this section we’ll add a few changes to integrate health with the game itself.
A. In adventure.py
modify PLAYER
#
[ ]
Add a"health"
key to thePLAYER
dictionary with a value of100
Code
45PLAYER = {
46 "place": "home",
47 "inventory": {"gems": 50},
48 "health": MAX_HEALTH,
49}
50
B. At the top of adventure.py
: Add ProgressBar#
We’re going to use the progress bar feature of the console library to add a health bar to the inventory command.
In this section we’ll add a global variable BAR
which will be set to a
ProgressBar()
object. We’ll use this later to print the progress bar.
[ ]
ImportProgressBar
fromconsole.progress
[ ]
Create a new global variableBAR
and set it to a newProgressBar()
object with the following keyword arguments:Keyword
Value
Why
total
MAX_HEALTH + 0.1
prevent dimming of bar at
100%
clear_left
False
prevent removal of
"Health "
text to left of barwidth
WIDTH - len("Health") - len("100%")
make bar width
WIDTH
minus the length of the other text on the same line
Note
From the command line you can run python -m console.progress
to see a demo of
the various progress bar styles.
Feel free to play around and pick your own style.
Code
24import textwrap
25
26from console import fg, fx
27from console.progress import ProgressBar
28
29WIDTH = 45
30
31MARGIN = 2
32
33DEBUG = True
34
35MAX_HEALTH = 100
36
37BAR = ProgressBar(
38 total=(MAX_HEALTH + 0.1),
39 width=(WIDTH - len("Health") - len("100%")),
40 clear_left=False,
41)
42
C. In adventure.py
define health_bar()
#
In this section we’ll add a function health_bar()
which will print the health bar.
[ ]
Write a functionhealth_bar()
[ ]
In use thewrite()
command to print:[ ]
"Health"
[ ]
the value returned when you callBAR()
and pass the argumentPLAYER["health"]
Code
198def health_bar():
199 """Print a progress bar showing player health"""
200 print()
201 write(f"Health {BAR(PLAYER['health'])}")
202
203
D. In adventure.py
modify do_inventory()
#
In this section we’ll call health_bar()
in the do_inventory()
function to
print the health bar.
[ ]
At the beginning ofdo_inventory()
callhealth_bar()
Demo
Code
505def do_inventory():
506 """Show the players inventory"""
507
508 debug("Trying to show inventory.")
509
510 health_bar()
511
512 header("Inventory")
513
514 if not PLAYER["inventory"]:
515 write("Empty.")
516 return
517
518 for name, qty in PLAYER["inventory"].items():
519 item = get_item(name)
520 write(f"(x{qty:>2}) {item['name']}")
521
522 print()
523
524
E. In adventure.py
modify main()
#
In this section we’ll quit the game if player health is out of health.
[ ]
At the very end of themain()
function, check to make sure that the player still has health[ ]
If not print something like"Game over"
and callquit()
Code
642def main():
643 header("Welcome!")
644
645 while True:
646 debug(f"You are at: {PLAYER['place']}")
647
648 reply = input(fg.cyan("> ")).strip()
649 args = reply.split()
650
651 if not args:
652 continue
653
654 command = args.pop(0)
655 debug(f"Command: {command!r}, args: {args!r}")
656
657 if command in ("q", "quit", "exit"):
658 do_quit()
659
660 elif command in ("shop"):
661 do_shop()
662
663 elif command in ("g", "go"):
664 do_go(args)
665
666 elif command in ("x", "exam", "examine"):
667 do_examine(args)
668
669 elif command in ("l", "look"):
670 do_look()
671
672 elif command in ("t", "take", "grab"):
673 do_take(args)
674
675 elif command in ("i", "inventory"):
676 do_inventory()
677
678 elif command in ("r", "read"):
679 do_read(args)
680
681 elif command == "drop":
682 do_drop(args)
683
684 elif command == "buy":
685 do_buy(args)
686
687 else:
688 error("No such command.")
689 continue
690
691 # print a blank line no matter what
692 print()
693
694 # exit the game if player has no health
695 if not PLAYER["health"]:
696 write("Game over.\n")
697 quit()
698
699