Learning Goals
3 minBy the end of this lesson you can:
- Initialise an empty
inventorylist outside any scene, and pass it into every scene function as a parameter. - Use
.append(),.remove()andin(from PY-L1-15..17) to take, drop and check items. - Gate a scene's outcome on what the player is carrying — "you can't enter the cave without a torch".
Warm-Up
5 minAn inventory is just a list. Open Python and try:
bag = [] bag.append("torch") bag.append("coin") print(bag) # ['torch', 'coin'] print("torch" in bag) # True print("sword" in bag) # False bag.remove("torch") print(bag) # ['coin']
Four list operations from PY-L1-15..17 — append, remove, the in operator, and printing the list. The whole inventory system is built from those four moves.
When you pass a list into a function, the function receives the same list — not a copy. If the function appends to it, the caller's list grows. That's exactly the behaviour we want today: the bag follows the player around even though each scene is a separate function call.
New Concept · The Bag Goes Everywhere
14 minThe bag is one list, owned by the main program
# main program inventory = [] # <-- created ONCE, lives forever location = "village" while True: if location == "village": location = scene_village(inventory) # <-- pass it in elif location == "forest": location = scene_forest(inventory) ...
The main program creates the inventory once, then passes the same list into every scene call. Because lists are passed by reference, any change a scene makes shows up everywhere else.
Scenes take the bag as a parameter
Each scene function now takes inventory alongside its other logic:
def scene_forest(inventory): print("== FOREST ==") print("A torch lies on the ground.") if "torch" not in inventory: print("You can TAKE it, walk to the CAVE, or go BACK.") else: print("You can walk to the CAVE or go BACK.") c = input("> ").strip().lower() if c == "take" and "torch" not in inventory: inventory.append("torch") # picked up print("You pick up the torch.") return "forest" elif c == "cave": return "cave" elif c == "back": return "village" elif c == "bag": print("Bag:", inventory) return "forest" else: return "forest"
The function reads the bag (to decide what options to show), modifies it (when the player takes), and still returns the next location. Everything else from Part 1 stays the same.
Gating a scene on what's in the bag
def scene_cave(inventory): print("== CAVE MOUTH ==") if "torch" not in inventory: print("It's pitch black. You can't see a thing. You turn back.") return "forest" print("Your torch flickers, lighting an old chest. Inside — a gold coin!") inventory.append("coin") return "end"
Now the cave checks the bag before letting the player progress. No torch → forced retreat. Torch → reward. The map didn't change; the gameplay did.
The bag command anywhere
Add a uniform "check what I'm holding" option to every scene by checking c == "bag" and printing the inventory. It returns the same location, so the player's position doesn't change — they just glanced inside their pack.
Watch out for .remove() on missing items
bag = ["coin"] bag.remove("torch") # ValueError: list.remove(x): x not in list
If you implement a drop command, always check if item in bag: before calling remove. Otherwise Python crashes with ValueError. We'll show this in the worked example.
State that needs to persist across function calls lives in the main program and gets passed into the functions as parameters. The list-is-shared-not-copied behaviour does the rest.
Worked Example · Adventure v2 with Inventory
14 minThe full file
Update adventure.py from Part 1:
Code
# adventure.py — Part 2: inventory list def scene_village(inventory): print() print("== VILLAGE SQUARE ==") print("A path leads east into the forest.") print("Options: FOREST, BAG, QUIT.") c = input("> ").strip().lower() if c == "forest": return "forest" elif c == "bag": print("Bag:", inventory if inventory else "(empty)") return "village" elif c == "quit": return "quit" else: return "village" def scene_forest(inventory): print() print("== FOREST PATH ==") if "torch" not in inventory: print("A TORCH lies on a moss-covered stone.") print("Options: TAKE, CAVE, BACK, BAG, QUIT.") else: print("Options: CAVE, BACK, DROP torch, BAG, QUIT.") c = input("> ").strip().lower() if c == "take" and "torch" not in inventory: inventory.append("torch") print("You take the torch.") return "forest" elif c == "drop" and "torch" in inventory: inventory.remove("torch") print("You set the torch back on the stone.") return "forest" elif c == "cave": return "cave" elif c == "back": return "village" elif c == "bag": print("Bag:", inventory if inventory else "(empty)") return "forest" elif c == "quit": return "quit" else: return "forest" def scene_cave(inventory): print() print("== CAVE MOUTH ==") if "torch" not in inventory: print("It is pitch black. You stumble in the dark and back out.") return "forest" print("Your torch lights an old chest. Inside, a gold coin gleams.") inventory.append("coin") print("(Coin added to bag.)") return "end" # main program print("=" * 40) print(" A Short Adventure — v2") print("=" * 40) inventory = [] location = "village" while True: if location == "village": location = scene_village(inventory) elif location == "forest": location = scene_forest(inventory) elif location == "cave": location = scene_cave(inventory) elif location == "end": print() print("=" * 40) print("THE END.") print("Final inventory:", inventory) print("=" * 40) break elif location == "quit": print("\nYou set down your pack and go home.") print("Final inventory:", inventory) break
Sample run · the "forgot the torch" route
== VILLAGE SQUARE == > forest == FOREST PATH == A TORCH lies on a moss-covered stone. Options: TAKE, CAVE, BACK, BAG, QUIT. > cave == CAVE MOUTH == It is pitch black. You stumble in the dark and back out. == FOREST PATH == A TORCH lies on a moss-covered stone. Options: TAKE, CAVE, BACK, BAG, QUIT. > take You take the torch. == FOREST PATH == Options: CAVE, BACK, DROP torch, BAG, QUIT. > bag Bag: ['torch'] == FOREST PATH == Options: CAVE, BACK, DROP torch, BAG, QUIT. > cave == CAVE MOUTH == Your torch lights an old chest. Inside, a gold coin gleams. (Coin added to bag.) ======================================== THE END. Final inventory: ['torch', 'coin'] ========================================
The neat inventory if inventory else "(empty)" trick
An empty list is "falsy" in Python — so inventory alone is False when empty, True when it has items. The conditional expression X if condition else Y picks one of two values. Together they let us print the actual list when there's something to show, and a friendlier "(empty)" when there isn't.
Adding the inventory took maybe a dozen lines of new code. But the game now has memory — choices earlier in the playthrough affect what's possible later. That's the whole soul of an adventure game. Part 3 builds on this further by making the player's carried items matter in combat.
Try It Yourself
13 minThree tasks. Each adds one new behaviour to the bag.
Modify your Part 1 file. Add inventory = [] in the main program. Add inventory as a parameter to scene_village. Add a bag command in the village that prints inventory. Don't implement take yet.
Hint
def scene_village(inventory): print("Village. FOREST, BAG, QUIT.") c = input("> ").strip().lower() if c == "forest": return "forest" if c == "bag": print("Bag:", inventory) return "village" if c == "quit": return "quit" return "village" # in the dispatcher: inventory = [] location = "village" while True: if location == "village": location = scene_village(inventory) ...
Run it. Type bag. You should see Bag: []. The plumbing is in place; we just have nothing in the bag yet.
In scene_forest, place a torch. If the player types take and the torch isn't already in the bag, append it and print "You take the torch.". Type bag after taking to confirm it's there.
Hint
def scene_forest(inventory): print("Forest. A torch on the ground.") print("TAKE, CAVE, BACK, BAG, QUIT.") c = input("> ").strip().lower() if c == "take" and "torch" not in inventory: inventory.append("torch") print("Taken.") return "forest" ...
The and in the if matters — without it, the player could type take a hundred times and end up with a bag full of identical torches. PY-L1-10's combined-conditions trick still pays off.
In scene_cave, check if "torch" not in inventory:. If true, print "too dark" and return "forest". If the torch is there, print the cave's treasure message, append "coin" to the bag, and return "end".
Hint
def scene_cave(inventory): print("Cave mouth.") if "torch" not in inventory: print("Too dark.") return "forest" print("Treasure! A coin.") inventory.append("coin") return "end"
Test it both ways — once going to cave without taking the torch (you should bounce back), once after taking the torch (you should win).
Mini-Challenge · Daniyal's Disappearing Inventory
8 minDaniyal's game lets you take a torch, but the bag is empty by the time you check. Find the two bugs.
# daniyal_adventure.py — buggy bag
def scene_forest():
inventory = [] # <-- created INSIDE the scene
print("Take the TORCH?")
c = input("> ").strip().lower()
if c == "take":
inventory.append("torch")
print("Bag:", inventory)
return "village"
# main program
location = "village"
while True:
if location == "forest":
scene_forest() # <-- return value ignored
location = "forest"
...- Bug 1.
inventory = []is inside the function. Every call creates a brand-new empty list and throws away whatever the player had. The bag never carries between scenes. - Bug 2. The dispatcher ignores
scene_forest()'s return value withscene_forest()on its own. Then it hard-codeslocation = "forest", so the player is stuck in the forest forever.
Show the fix
# daniyal_adventure.py — fixed def scene_forest(inventory): # <-- bag passed in print("Take the TORCH?") c = input("> ").strip().lower() if c == "take": inventory.append("torch") print("Bag:", inventory) return "forest" if c == "back": return "village" return "forest" # main program inventory = [] # <-- owned by main location = "village" while True: if location == "forest": location = scene_forest(inventory) # <-- pass it, capture return ...
Both bugs are the same idea: a function's state shouldn't be created inside the function if it needs to outlive the call. Move it to the main program and pass it in. The capture-the-return-value fix is the standard "forgot the equals sign" mistake — your function had nice logic, you just never let the dispatcher hear about it.
Recap
3 minAn inventory is just a list owned by the main program and passed into every scene as a parameter. Because Python passes lists by reference, any .append() or .remove() a scene does is visible from then on. The in operator is the heart of every check — "does the player have a torch?" becomes "torch" in inventory. Scenes can gate on inventory state (no torch → bounce back from the cave) which is what turns a tour into a game. Next lesson — Part 3 — we add a boss fight with HP and a battle loop.
Vocabulary Card
- inventory
- A list of strings representing what the player is carrying.
- pass by reference
- When a function gets a list as a parameter, it gets the same list — modifying it modifies the caller's list too. (Numbers and strings behave differently — they're effectively copied.)
inoperatorx in seqreturnsTrueifxappears in the sequence. Works on lists, strings, and most other containers.- gating
- Letting (or refusing) an action based on a precondition — "you need the torch to enter the cave".
- state in main
- The design pattern of keeping persistent data (location, inventory, score) in the main program, with functions reading and modifying it as parameters.
Homework
4 minGrow your adventure.py into something with two ways to win.
- Add at least two different pickable items in two different scenes (e.g.
torchin the forest,keyin the market). - Add at least two different gated endings — one ending needs the torch, the other needs the key. Same dispatcher, same
"end"sentinel — the scenes themselves do the gating. - Implement
dropin at least one scene, with theif item in bagsafety check from the lesson. - Every scene must have a
bagcommand that prints the inventory. Repetition is okay — it's a good user-experience habit.
Bring adventure.py next class — PY-L1-41 adds a boss fight with HP and random damage.
Sample · adventure.py (two pickups, two endings)
# adventure.py — Part 2 with two endings def show_bag(inv): print("Bag:", inv if inv else "(empty)") def scene_village(inv): print("\n== VILLAGE ==\nFOREST, MARKET, BAG, QUIT.") c = input("> ").strip().lower() if c == "forest": return "forest" if c == "market": return "market" if c == "bag": show_bag(inv); return "village" if c == "quit": return "quit" return "village" def scene_forest(inv): print("\n== FOREST ==\nA TORCH on the ground.") print("TAKE/DROP torch, CAVE, BACK, BAG, QUIT.") c = input("> ").strip().lower() if c == "take" and "torch" not in inv: inv.append("torch"); print("Taken."); return "forest" if c == "drop" and "torch" in inv: inv.remove("torch"); print("Dropped."); return "forest" if c == "cave": return "cave" if c == "back": return "village" if c == "bag": show_bag(inv); return "forest" if c == "quit": return "quit" return "forest" def scene_market(inv): print("\n== MARKET ==\nA shiny KEY on a stall.") print("TAKE key, GATE, BACK, BAG, QUIT.") c = input("> ").strip().lower() if c == "take" and "key" not in inv: inv.append("key"); print("Taken."); return "market" if c == "gate": return "gate" if c == "back": return "village" if c == "bag": show_bag(inv); return "market" if c == "quit": return "quit" return "market" def scene_cave(inv): print("\n== CAVE ==") if "torch" not in inv: print("Too dark — you stumble back.") return "forest" print("Your torch lights a coin chamber. You win the cave ending!") return "end" def scene_gate(inv): print("\n== LOCKED GATE ==") if "key" not in inv: print("Locked. You walk back.") return "market" print("The key turns. You step into the secret garden — gate ending!") return "end" # main inv = [] loc = "village" while True: if loc == "village": loc = scene_village(inv) elif loc == "forest": loc = scene_forest(inv) elif loc == "market": loc = scene_market(inv) elif loc == "cave": loc = scene_cave(inv) elif loc == "gate": loc = scene_gate(inv) elif loc == "end": print("\nTHE END. Final bag:", inv); break elif loc == "quit": print("\nGoodbye. Final bag:", inv); break
A reusable show_bag(inv) helper means every scene calls one line instead of two. The map now has a non-trivial topology — you can win the cave ending without ever visiting the market, or the gate ending without ever entering the forest. That's replay value, built out of the same simple parts.