I am new to Python and I have not yet done any OOP. I wrote this text adventure as a first project and I would like to know if there is a better and more efficient way to write my code within its current paradigm. Any suggestions or critique are more than welcome.
The code is quite long. I don't expect anyone to read all of it.
#!/usr/bin/env python
import cmd
import random
import time
import sys
import os
#### Effects ####
def print_slow(str):
for letter in str:
sys.stdout.write(letter)
sys.stdout.flush()
time.sleep(0.1)
print("\n")
def load_effect(str):
for letter in str:
sys.stdout.write(letter)
sys.stdout.flush()
time.sleep(0.5)
#### Title Screen ####
def menu():
print("\n")
print("SHADOWS OVER LAURENDALE")
print("Version 1.0")
action = input("> ")
if action.lower() == "p":
intro()
elif action.lower() == "q":
sys.exit()
elif action.lower() == "i":
info_menu()
elif action.lower() == "h":
help_menu()
while action.lower() not in ['p', 'q', 'i', 'h']:
print("Invalid option! Enter 'p' to launch the game,")
print("'q' to quit, 'i' for info, or 'h' for help.")
action = input("> ")
if action.lower() == "p":
intro()
elif action.lower() == "q":
sys.exit()
elif action.lower() == "i":
info_menu()
elif action.lower() == "h":
help_menu()
def info_menu():
print("www.shadows-over-laurendale.github.io")
action = input("> ")
if action.lower() == "p":
intro()
elif action.lower() == "q":
sys.exit()
elif action.lower() == "i":
info_menu()
elif action.lower() == "h":
help_menu()
while action.lower() not in ['p', 'q', 'i', 'h']:
print("Invalid option! Enter 'p' to launch the game,")
print("'q' to quit, 'i' for info, or 'h' for help.")
action = input("> ")
if action.lower() == "p":
intro()
elif action.lower() == "q":
sys.exit()
elif action.lower() == "i":
info_menu()
elif action.lower() == "h":
help_menu()
def help_menu():
print("Enter commands to interact with the world.")
print("For more info and a list of commands, consult the manual.")
action = input("> ")
if action.lower() == "p":
intro()
elif action.lower() == "q":
sys.exit()
elif action.lower() == "i":
info_menu()
elif action.lower() == "h":
help_menu()
while action.lower() not in ['p', 'q', 'i', 'h']:
print("Invalid option! Enter 'p' to launch the game,")
print("'q' to quit, 'i' for info, or 'h' for help.")
action = input("> ")
if action.lower() == "p":
intro()
elif action.lower() == "q":
sys.exit()
elif action.lower() == "i":
info_menu()
elif action.lower() == "h":
help_menu()
#### Player ####
player_health = 100
player_inventory = ["Map"]
player_gold = 50
#### Main Scenes ####
def intro():
print("\n")
load_effect("...loading...")
print("\n")
print_slow("'Wake up, Arris...'")
print_slow("'You've been sleeping all day!'")
print_slow("'Why don't you go outside and do something?'")
print_slow("'It's so lovely today. Be back for dinner!'")
print("\n")
print_slow("'Okay...'")
print("\n")
village()
def village():
global player_gold
global player_health
print("\n")
print("You are in the village.")
while True:
action = input("> ")
if action == "1":
river()
if action == "2":
farmlands()
elif action == "3":
forest()
elif action == "4":
shop()
elif action == "5":
home()
elif action == "h":
print("Health:",player_health)
elif action == "i":
print("Inventory:")
for items in player_inventory:
print(items)
elif action == "g":
print("Gold:",player_gold)
elif action == "m":
print("Map: the river(1), the farmlands(2), the forest(3), the shop(4), home(5).")
elif action == "e":
print("Sounds of laughter and loud conversation can be heard in this lively village.")
print("What a lovely place! I was born here and my heart will always be here...")
elif action == "t":
print("A merchant on the street waves you over to where he is.")
print("'Care for some fresh bread, young man? It's only 5 gold.' He asks.")
print("You have",player_gold,"gold.")
print("Yes(y) or no(n)?")
action = input("> ")
if action == "y":
if "Bread" in player_inventory:
print("Looks like you already have some bread, kid!")
print("Eat it up and come back... Hehe.")
elif player_gold >= 5:
player_inventory.append("Bread")
player_gold -= 5
print("'Here you go!'")
print("You take the bread from the man.")
elif player_gold < 5:
print("Sorry you don't have enough! Ask your mother for a little gold! Hehe.")
elif action == "n":
print("Okay. Maybe next time!")
else:
print("Invalid command! Try again.")
action = input("> ")
if action == "y":
if player_gold >= 5:
player_inventory.append("Bread")
player_gold -= 5
print("'Here you go!'")
print("You take the bread from the man.")
elif player_gold < 5:
print("Sorry you don't have enough! Ask your mother for a little gold! Hehe.")
elif action == "n":
print("Okay. Maybe next time!")
else:
print("Invalid command! Try again.")
def river():
global player_gold
global player_health
print("\n")
print("You are by the river.")
while True:
action = input("> ")
if action == "1":
village()
elif action == "2":
farmlands()
elif action == "3":
forest()
elif action == "h":
print("Health:",player_health)
elif action == "i":
print("Inventory:")
for items in player_inventory:
print(items)
elif action == "g":
print("Gold:",player_gold)
elif action == "m":
print("Map: the village(1), the farmlands(2), the forest(3).")
elif action == "e":
print("I can hear the water flowing gently and the birds are singing...")
print("A small boy is out here with his fishing net.")
elif action == "t":
print("The little boy with his net look over at you.")
print("'Have you ever tried crossing this river?'")
print("'It looks so beautiful on the other side.'")
print("'I better get back to catching the fishes.'")
print("'My papa will be proud!'")
if "Raft" in player_inventory:
print("/n")
print("Will you cross the river? Yes(y) or no(n)?")
action = input("> ")
if action == "n":
print("That river looks dangerous anyways...")
print("It's infested with snakes.")
elif action == "y":
outskirts()
else:
print("Invalid command! Try again.")
action = input("> ")
if action == "n":
print("That river looks dangerous anyways...")
print("It's infested with snakes.")
elif action == "y":
outskirts()
else:
print("Invalid command! Try again.")
def farmlands():
global player_gold
global player_health
global ruby_saved
print("\n")
print("You are in the farmlands.")
while True:
action = input("> ")
if action == "1":
village()
elif action == "2":
river()
elif action == "3":
forest()
elif action == "h":
print("Health:",player_health)
elif action == "i":
print("Inventory:")
for items in player_inventory:
print(items)
elif action == "g":
print("Gold:",player_gold)
elif action == "m":
print("Map: the village(1), the river(2), the forest(3).")
elif action == "e":
print("Rosy red tomatoes and evergreen lettuce are growing from the ground...")
print("A farmer waves to you and smiles.")
elif action == "t":
print("'Have you seen my lovely Ruby?' The farmer asks.")
print("'She's run away again.'")
print("He looks very concerned.")
else:
print("Invalid command! Try again.")
def forest():
global player_gold
global player_health
print("\n")
print("You are in the forest.")
while True:
action = input("> ")
if action == "1":
village()
elif action == "2":
river()
elif action == "3":
farmlands()
elif action == "h":
print("Health:",player_health)
elif action == "i":
print("Inventory:")
for items in player_inventory:
print(items)
elif action == "g":
print("Gold:",player_gold)
elif action == "m":
print("Map: the village(1), the river(2), the farmlands(3).")
elif action == "e":
print("This is a far way from home...")
print("The sound of a girl screaming can be faintly heard.")
print("I'm afraid to go on.")
if "Sword" in player_inventory:
print("\n")
print("But I do have a weapon to protect myself.")
print("Shall I go on? Yes(y) or no(n)?")
action = input("> ")
if action == "n":
print("I won't go any further...")
elif action == "y":
forest_two()
else:
print("Invalid command! Try again.")
action = input("> ")
if action == "n":
print("I won't go any further...")
elif action == "y":
forest_two()
elif action == "t":
print("There's no one to talk to here...")
else:
print("Invalid command! Try again.")
#### Sub Scenes ####
def home():
global player_gold
global player_health
print("\n")
print("You are at home.")
while True:
action = input("> ")
if action == "1":
village()
elif action == "2":
river()
elif action == "3":
farmlands()
elif action == "4":
forest()
elif action == "h":
print("Health:",player_health)
elif action == "i":
print("Inventory:")
for items in player_inventory:
print(items)
elif action == "g":
print("Gold:",player_gold)
elif action == "m":
print("Map: the village(1), the river(2), the farmlands(3), the forest(4).")
elif action == "e":
print("A warm fire is crackling in the hearth...")
print("Something smells delicious!")
elif action == "t":
print("You call for your mother.")
print("She comes inside from the back door and smiles.")
print("'There you are! Now what have you been up to?'")
if player_health < 100:
print("\n")
print("'You're hurt! Let me tend to your wounds.'")
player_health = 100
print("You feel better. You now have",player_health,"health.")
else:
print("Invalid command! Try again.")
def outskirts():
global player_gold
global player_health
print("\n")
print("You are in the outskirts.")
while True:
action = input("> ")
if action == "1":
village()
elif action == "2":
river()
elif action == "3":
farmlands()
elif action == "4":
forest()
elif action == "h":
print("Health:",player_health)
elif action == "i":
print("Inventory:")
for items in player_inventory:
print(items)
elif action == "g":
print("Gold:",player_gold)
elif action == "m":
print("Map: the village(1), the river(2), the farmlands(3), the forest(4).")
elif action == "e":
print("You can see the boy fishing from across the river.")
if "Sword" not in player_inventory:
print("You see a shiny item lying in the grass.")
print("Will you take it? Yes(y) or no(n)?")
action = input("> ")
if action == "n":
print("Some things are better left where you found them.")
elif action == "y":
print("You reach for it. It's a beautiful and sharp sword!")
print("But as soon as you grab it, a snake bites your hand!")
snake_attack = random.randint(1,100)
player_health -= snake_attack
player_inventory.append("Sword")
else:
print("Invalid command! Try again.")
action = input("> ")
if action == "n":
print("Some things are better left where you found them.")
elif action == "y":
print("You reach for it. It's a beautiful and sharp sword!")
print("But as soon as you grab it, a snake bites your hand!")
snake_attack = random.randint(1,100)
player_health -= snake_attack
player_inventory.append("Sword")
elif action == "t":
print("You yell to the boy across the river.")
print("'Hey! How did you get over there?'")
else:
print("Invalid command! Try again.")
def shop():
global player_gold
global player_health
print("\n")
print("You are in the shop.")
while True:
action = input("> ")
if action == "1":
village()
elif action == "2":
river()
elif action == "3":
farmlands()
elif action == "4":
forest()
elif action == "h":
print("Health:",player_health)
elif action == "i":
print("Inventory:")
for items in player_inventory:
print(items)
elif action == "g":
print("Gold:",player_gold)
elif action == "m":
print("Map: the village(1), the river(2), the farmlands(3), the forest(4).")
elif action == "e":
print("A friendly woman is standing at the counter.")
print("'Hello! Can I help you, young man?'")
elif action == "t":
print("'Please, browse our wares.'")
print("'We have what you need.'")
print("'Do you want to see what we have?'")
print("Yes(y) or no(n)?")
action = input("> ")
if action == "y":
print("You can buy:")
print("a fishing pole(f) for 10 gold, a leather tunic(t) for 20 gold,")
print("a guitar(g) for 30 gold, and a raft(r) for 15 gold.")
action = input("> ")
if action == "f":
if "Fishing poll" in player_inventory:
print("I aleady have one!")
elif player_gold >= 10:
player_inventory.append("Fishing poll")
player_gold -= 10
print("'Here you go!'")
print("You bought a lovely handmade fishing poll.")
elif player_gold < 5:
print("Sorry you don't have enough for this, kid.")
elif action == "t":
if "Tunic" in player_inventory:
print("I aleady have one!")
elif player_gold >= 20:
player_inventory.append("Leather tunic")
player_gold -= 20
print("'Here you go!'")
print("You bought a bulky leather tunic.")
elif player_gold < 20:
print("Sorry you don't have enough for this, kid.")
elif action == "g":
if "Guitar" in player_inventory:
print("I aleady have one!")
elif player_gold >= 30:
player_inventory.append("Guitar")
player_gold -= 30
print("'Here you go!'")
print("You bought a gorgeous guitar.")
elif player_gold < 30:
print("Sorry you don't have enough for this, kid.")
elif action == "r":
if "Raft" in player_inventory:
print("I aleady have one!")
elif player_gold >= 15:
player_inventory.append("Raft")
player_gold -= 15
print("'Here you go!'")
print("You bought a small wooden raft.")
elif player_gold < 15:
print("Sorry you don't have enough for this, kid.")
else:
print("Invalid command! Try again.")
elif action == "n":
print("'Okay, no worries! I just love your company.'")
print("'Tell your friends about this place, okay?'")
else:
print("Invalid command! Try again.")
print("'Please, browse our wares.'")
print("'We have what you need.'")
print("'Do you want to see what we have?'")
print("Yes(y) or no(n)?")
action = input("> ")
else:
print("Invalid command! Try again.")
def forest_two():
global player_gold
global player_health
print("\n")
print("You are deep into the forest.")
while True:
action = input("> ")
if action == "1":
print("I don't know how to get there. I'm lost.")
elif action == "2":
print("I don't know how to get there. I'm lost.")
elif action == "3":
print("I don't know how to get there. I'm lost.")
elif action == "4":
print("I don't know how to get there. I'm lost.")
elif action == "h":
print("Health:",player_health)
elif action == "i":
print("Inventory:")
for items in player_inventory:
print(items)
elif action == "g":
print("Gold:",player_gold)
elif action == "m":
print("Map: the village(1), the river(2), the farmlands(3), the forest entrance(4)")
elif action == "e":
print("You are in a part of the forest you've never seen before.")
print("You are lost...")
elif action == "t":
print("You call for help.")
print("You suddenly hear the girl scream again.")
print("This time louder...")
print("Will you investigate?")
print("Yes(y) or no(n)?")
action = input("> ")
if action == "n":
print("You decide not to go.")
print("A wolf howls in the distance.")
print("The moon glistening above you.")
elif action == "y":
final()
else:
print("Invalid command! Try again.")
else:
print("Invalid command! Try again.")
def final():
global player_gold
global player_health
print("\n")
print("You are even further into the woods.")
print("You see an old, abandoned cabin just ahead.")
while True:
action = input("> ")
if action == "1":
print("I can't go there. I'm lost!")
elif action == "2":
print("I can't go there. I'm lost!")
elif action == "3":
print("I can't go there. I'm lost!")
elif action == "4":
print("I can't go there. I'm lost!")
elif action == "h":
print("Health:",player_health)
elif action == "i":
print("Inventory:")
for items in player_inventory:
print(items)
elif action == "g":
print("Gold:",player_gold)
elif action == "m":
print("Map: the village(1), the river(2), the farmlands(3), the forest entrance(4).")
elif action == "e":
print("The screaming is now even closer...")
print("You are frozen with terror.")
elif action == "t":
print("You call for the girl.")
print("She can hear you now!")
print("'Help me! Please, help me!'")
print("Her voice sounds like it's coming from the cabin.")
print("Will you go?")
print("Yes(y) or no(n)?")
action = input("> ")
if action == "n":
print("You hide behind some trees.")
print("You look behind you into the forest.")
print("There's no turning back.")
elif action == "y":
cabin()
else:
print("Invalid command! Try again.")
else:
print("Invalid command! Try again.")
def cabin():
global player_gold
global player_health
print("\n")
print("You are right outside of the cabin.")
print("You draw your sword...")
while True:
action = input("> ")
if action == "1":
print("No turning back.")
elif action == "2":
print("No turning back.")
elif action == "3":
print("No turning back.")
elif action == "4":
print("No turning back.")
elif action == "h":
print("Health:",player_health)
elif action == "i":
print("Inventory:")
for items in player_inventory:
print(items)
elif action == "g":
print("Gold:",player_gold)
elif action == "m":
print("Map: the village(1), the river(2), the farmlands(3), the forest entrance(4).")
elif action == "e":
print("You walk up to the door and raise your sword.")
print("Will you enter? Yes(y) or no(n)?")
action = input("> ")
if action == "n":
print("You wait to regain your composure.")
print("You muster up a little courage.")
print("Something terrible is waiting behind the door.")
elif action == "y":
print("\n")
print("You open the door. It creaks loudly as peak inside.")
print("You see a small girl with red hair lying in the corner.")
print("'Help me! Someone brought me here and he's going to kill me!'")
print("She sobs hopelessly.")
print("You ask her where he is.")
print("'I don't know.' She says. 'He left a few minutes ago to get an axe!'")
print("'Please help me! My name is Ruby. My dad is looking for me!'")
time.sleep(10)
print("\n")
print("A man in a black robe bursts into the cabin with a bloody axe!")
time.sleep(5)
print("\n")
print("Ruby screams.")
print("'No! Please. I didn't do anything...'")
print("'Help me, boy!'")
time.sleep(5)
print("\n")
print("The man raises his axe to slice you apart.")
print("But you swiftly parry his attack with your sword.")
time.sleep(5)
print("\n")
print("You raise your sword to strike back.")
print("Ruby puts her hands over her face.")
time.sleep(5)
print("\n")
print("You slice into the man's shoulder.")
time.sleep(5)
print("\n")
print("He strikes again...")
enemy_attack = random.randint(1,100)
player_health -= enemy_attack
print("He swings his axe furiously toward you...")
time.sleep(5)
print("\n")
print("He cuts into your arm as you moan in agony!")
print("Ruby screams.")
time.sleep(5)
print("\n")
print("You fall on the floor and drop your sword...")
if player_health > 0:
time.sleep(5)
print("\n")
print("In a last burst of strength,")
print("You lift up the sword with both hands")
print("and charge toward the shadowy figure.")
time.sleep(5)
print("\n")
print("You cut into the man's chest and pierce his heart.")
time.sleep(5)
print("\n")
print("He falls to the floor.")
print("He lies in a puddle of his own blood.")
time.sleep(5)
print("\n")
print("Ruby runs toward you, crying.")
print("'You saved my life!' She exclaims enthusiastically.")
print("You drop your sword, exausted")
time.sleep(5)
print("\n")
print("She embraces you, tightly.")
print("'Please take me home to my daddy's farm...' She begs.")
time.sleep(5)
print("\n")
print("You both return to the farm.")
print("The farmer sees you and his daughter walking toward the farm.")
print("'Ruby! You're back!'")
time.sleep(5)
print("\n")
print("'You were the boy I saw yesterday!'")
print("You shrug in agreement.")
time.sleep(5)
print("\n")
print("'You found my daughter, boy!' He says excitedly.")
print("'I can't thank you enough.'")
print("Ruby runs home to her father.")
print("She looks back at you and smiles.")
time.sleep(5)
print("\n")
print("You return home.")
print("Your mother must be worried sick!")
elif player_health <= 0:
print("You fall unconscious as the hooded figure drags you away...")
sys.exit
else:
print("Invalid command! Try again.")
elif action == "t":
print("You anxiously hyperventilate. You cannot make a sound.")
else:
print("Invalid command! Try again.")
menu()
-
\$\begingroup\$ Welcome to Code Review! I changed your title so that it hopefully better follows our guide on How do I ask a good question? . Feel free to have a look at the resource I linked and edit it again to make it even more expressive. \$\endgroup\$AlexV– AlexV2019年06月19日 22:49:04 +00:00Commented Jun 19, 2019 at 22:49
-
\$\begingroup\$ Thanks for the clarification, AlexV. \$\endgroup\$Elijah– Elijah2019年06月19日 23:08:10 +00:00Commented Jun 19, 2019 at 23:08
2 Answers 2
In print_slow
:
I would pass in a time delay value, or at the very least have the delay value as a "constant" above. Using magic numbers isn't a good habit to get in to. I'd lean to either one of these:
def print_slow(str, delay = 0.1): for letter in str: sys.stdout.write(letter) sys.stdout.flush() time.sleep(delay) print("\n")
or
SLOW_PRINT_DELAY = 0.1 def print_slow(str): for letter in str: sys.stdout.write(letter) sys.stdout.flush() time.sleep(SLOW_PRINT_DELAY) print("\n")
The first has the benefit that you can alter how slowly it prints for different circumstances. This does away with the need for a separate nearly identical function
load_effect
. I'm having it to default to 0.1 just in case they don't want to specify the delay. You could even combine both of these options and have the constant, and use it as the default value thatprint_slow
defaults to.I'm not entirely sure why you're using
sys.stdout
directly. It doesn't seem to be for portability reasons since you use plainprint
right below that. If it's to avoid the newline being added at the end when usingprint
, I'll just point out that you can use theend
parameter ofprint
to avoid that:print("Some text", end = "") # Will not print a newline after
At the top of menu
you have a dispatch where you're using lower
then checking the input against several characters. My issue with how you have it here is you're calling lower
repeatedly (and repeating yourself is never a good thing), and then you repeat that whole thing below that in a verification loop! Wrap the repetitious code up in a function and call it as needed:
def dispatch_action(action_key):
std_action = action_key.lower() # Standardize it once
if std_action == "p":
intro()
elif std_action == "q":
sys.exit()
elif std_action == "i":
info_menu()
elif std_action == "h":
help_menu()
Then, you can return False
if they entered a bad action, and True
if it was a good action, and loop in menu
until dispatch_action
returns True
.
Or, you could get a little fancy and neaten it up a bit by having a map of functions:
MENU_ACTIONS = {"p": intro,
"q": sys.exit,
"i": info_menu,
"h": help_menu}
def dispatch_action(action_key):
std_action = action_key.lower() # Standardize it once
f = MENU_ACTIONS.get(std_action, None) # Default to None if the command is bad
if f:
f() # Call the menu function returned by the lookup
else:
# Handle a bad command
I like dictionaries when you're just doing simple matching against something like a String or number. It'll be potentially faster than an if
tree (although that doesn't matter here), and I just personally like how they read. It makes it so you don't have to write (and read) std_action ==
over and over again.
There's a lot more to get into here, but I'm quite tired. Hopefully someone else can comment on the rest.
-
\$\begingroup\$ Thank you for your especially useful and thought-provoking feedback. And thank you for taking the time out of your day to reply despite being tired. \$\endgroup\$Elijah– Elijah2019年06月24日 02:06:21 +00:00Commented Jun 24, 2019 at 2:06
The right tool for the job
Adventure games are data-driven programs. In the game, you will have objects which have descriptions. You will have places, which have descriptions, contents, and connections to other locations. You will have a player, who will have a description, an inventory (contents), and move from location to location.
Inform 7 is an interactive fiction programming language, which allow you to describe the world to the game engine in a natural language, and it will help you build the interactions by providing a standard library of actions, input parsing, etc., and allow someone to play the story.
But if your goal is to learn Python, and this is just a project to work on while you are learning...
Infinite Recursion
You game never ends.
menu()
can call help_menu()
which can call help_menu()
which can call info_menu()
which can call help_menu()
which can call intro()
which will call village()
, which can call home()
which can call village()
which can call ...
Your stack can become infinitely deep. Well, actually, it can't; the interpreter will eventually give up and raise an exception if it gets too deep. You probably will never encounter this is normal game play, but if you ever had a robot tester for the program, it could move back and forth between locations and eventually cause a stack overflow.
You want your functions to return
to their caller, eventually.
Don't Repeat Yourself (DRY)
The opposite of DRY code is WET code ... Write Everything Twice. In your shop()
function, you've written almost exactly the same code 4 times!
if action == "f":
if "Fishing poll" in player_inventory:
print("I aleady have one!")
elif player_gold >= 10:
player_inventory.append("Fishing poll")
player_gold -= 10
print("'Here you go!'")
print("You bought a lovely handmade fishing poll.")
elif player_gold < 5:
print("Sorry you don't have enough for this, kid.")
elif action == "t":
# Almost identical code
elif action == "g":
# Almost identical code
elif action == "r":
# Almost identical code
else:
Let's dry this up:
if action == "f":
buy("Fishing poll", 10, "lovely handmade fishing pole")
elif action == "t":
buy("Tunic", 20, "bulky leather tunic")
elif action == "g":
buy("Guitar", 30, "gorgeous guitar")
elif action == "r":
buy("Raft", 30, "small wooden raft")
else:
def buy(item, cost, description):
global player_gold, player_inventory
if item in player_inventory:
print("I already have one!")
elif player_gold >= cost:
player_inventory.append(item)
player_gold -= cost
print("'Here you go!'")
print(f"You bought a {description}.")
else:
print("Sorry you don't have enough for this, kid.")
This will fix two bugs:
- Buying a
Tunic
will add aLeather Tunic
to your inventory, not aTunic
, so you can keep buying aTunic
until you run out of money. - A fishing pole cost 10 gold, but you are only told you can't buy one if you have less than 5 gold.
We still repeat ourself for the items and their costs when the shopkeeper describes what you can buy. So let's keep going:
store_inventory = {
'f': ("Fishing poll", 10, "lovely handmade fishing pole"),
't': ("Tunic", 20, "bulky leather tunic"),
'g': ("Guitar", 30, "gorgeous guitar"),
'r': ("Raft", 30, "small wooden raft")
}
print("You can buy:")
for key, item_data in store_inventory.items():
item, cost, description = item_data
print(f" a {item.lower()}({key}) for {cost} gold")
action = input("> ")
if action in store_inventory:
item, cost, description = store_inventory[action]
buy(item, cost, description)
This can work for your other bread merchant as well.
Certain player actions are available in every location/scene.
h
- Player healthi
- Player inventoryg
- Player goldm
- Map
With the exception of the map command, these are all identical. The map command displays different text depending on the location.
def player_commands(action):
if action == 'h':
print(f"Heath: {player_health}")
elif action == 'i':
print("Inventory:")
for item in player_inventory:
print(item)
elif action == 'g':
print(f"Gold: {player_gold}")
elif action == 'm':
show_map()
else:
return False # Not a Player Command, so not handled
return True # Player Command handled
Now, in the village, or forest, or outskirts, or shop, or river, or cabin, or home, you can use the following, instead of repeating the same 10 lines over and over again:
if player_command(action):
pass
elif action == 'e':
# etc.
Object Orientation
From your code, we can tell you know how to use strings, lists, functions, loops, and if/elif/else statements. I introduced a dictionary and a tuple in the store’s inventory, above. Those, along with classes, are going to make your game easier to write.
Right now you have player_gold
, player_inventory
and player_health
. 3 separate global variables which are all related to the player. We can encapsulate these into an object.
player = object()
player.health = 100
player.inventory = ["Map"]
player.gold = 50
This is an ad-hoc object, with 3 members. Having it has the advantage that you don’t need to pull 3 global variables into various functions, just the one player
global. And since you will never modify the player
global (just its contents), you don’t even need global player
in any function; it you haven’t defined a local player
variable, the function will use the global player
automatically.
We can be a bit more structured. The player is a person, but so it the merchant, the little boy, the farmer, your mother, the friendly woman, Ruby and the man in a black robe. You fight the man in the black robe, so he should probably have health that can be eroded. Both you and Ruby have names. Maybe you should declare a Person
class.
class Person:
def __init__(self, name):
self.name = name
self.health = 100
self.inventory = []
self.gold = 0
self.location = None
player = Person("Arris")
player.gold += 50
player.inventory.append("Map")
mother = Person("your mother")
ruby = Person("Ruby")
The world is full of things. Things have names. Things have descriptions. Things should have a class
:
class Thing:
def __name__(self, name, description):
self.name = name
self.description = description
sword = Thing("Sword", "a beautiful and sharp sword")
bread = Thing("Bread", "fresh bread")
In a similar vein, the world is full of locations. The locations have names. The locations have contents. The locations have connections to other locations. A location should be a class
, and each location should be a member of that class.
class Location:
def __init__(self, name):
self.name = name
self.contents = []
self.connections = {}
def remove_item(self, item):
self.contents.remove(item)
home = Location("at home")
village = Location("in the village")
river = Location("by the river")
outskirts = Location("in the outskirts")
# ... etc ...
home.contents.append(mother)
home.connection['1'] = village
home.connection['2'] = river
# ... etc ...
outskirts.contents.append(sword)
It may not be immediately obvious, but a Person
is a Thing
, and a Location
is a Thing
, so you could could make the Person
and Location
classes derive from Thing
. The Person
’s inventory would be their contents
. If you extend your game, a Thing
could be a Container
(such as a box), which also has contents ... so you might consider deriving Person
and Location
from Container
which is derived from Thing
. Some class hierarchies will add value; some class hierarchies may just create confusion. Is a Person
really a Container
? Does that add any useful simplifications from a programming point of view?
I mentioned Player actions earlier, which can be invoked anywhere the player is. These actions could be attached to a Player object:
class Player(Person):
def __init__(self, name):
super().__init__(name)
def perform(self, action):
if action == 'h':
print("Health: {self.health}")
# ... etc
When the player is in a different locations, the e
and t
actions behave differently.
elif action == 'e':
self.location.explore()
elif action == 't':
self.location.talk()
... and we can attach actions to the Location
class:
class Location:
def explore(self):
if self.contents:
print("You see:")
for item in self.contents:
print(item)
else:
print("You poke around, but don’t find anything interesting")
def talk(self):
print("You mutter to yourself.")
Different locations may have different behaviours. For example, taking the sword
in the outskirts
might invoke the wrath of a snake
. (Talking to you mom will get her to heal you.) So you might want to further specialize the Location
class.
class Outskirts(Location):
def remove_item(self, item):
if item == sword:
print("A snake bites you")
player.health -= 40
super().remove_item(item)
Framework
Once you have completed your framework for a game, with many objects, many rooms, many actions, and so on, you can create a completely different adventure game by changing the items, descriptions, and triggers. You might consider loading the world from a structured file, with the same adventure game framework supporting multiple games, by operating on different data. Of course, this means you have effectively reinvented the Inform7 game engine, mentioned at the start.
-
\$\begingroup\$ Great in-depth review. I'm not convinced about 'Thing' though. Perhaps copy-pasting 'Name' and 'Description' to the classes that require these attributes is better. I'm not a fan of object hierarchies that contain very lightweight bases classes. \$\endgroup\$dfhwze– dfhwze2019年06月23日 05:30:34 +00:00Commented Jun 23, 2019 at 5:30
-
1\$\begingroup\$ @dfhwze Python’s mix-in classes might be more appropriate, to make something that is a
Container
(you can put things in), aSupporter
(you can put things on),Animate
(does stuff by itself),Enterable
(a location a player can move to).class SelfDrivingCar(Thing, Container, Supporter, Animate, Enterable): ...
. Alternately, everything could be aThing
and the those attributes could be stored in anEnumFlag
in theThing
object. Many many ways to do this. I look forward to reviewing iterations 2, 7, and 12 of the resulting framework. \$\endgroup\$AJNeufeld– AJNeufeld2019年06月23日 14:16:45 +00:00Commented Jun 23, 2019 at 14:16 -
\$\begingroup\$ @AJNeufeld Thank you so much for the in-depth micro lesson on OOP and writing clean code. I very much appreciate the feedback. I know the code was repetitious but I also expected I would get some real solutions to refactor it and I got more than I was expecting. Do you know any books on OOP that you would suggest? \$\endgroup\$Elijah– Elijah2019年06月24日 02:03:56 +00:00Commented Jun 24, 2019 at 2:03
Explore related questions
See similar questions with these tags.