I've been using Python for around 3 months and decided to make a simple text adventure game for fun and to exercise a lot of ideas I've been learning about. I've just recently learned about classes and OOP, as well as modules so those are the two features I'm the least sure of. It's mostly a base and has two rooms set up, but I'm pretty proud of it so far as I feel like its fairly readable and has good style (though I admittedly have nothing to compare it with.) So far I have four modules that I've distinguished from each other, mostly for readability. My use of classes is shallow, let me know how I could improve on it! I have two rooms set up so far.
Main module:
""" This is the main file, it contains the functions for the rooms as well as the startup sequences."""
import actions
import room_info as rooms
import character_and_items as char
"""TODO LIST:
-Implement save features(Save module?)
-Add some spunk into the descriptions, make it fun yet concise!!
"""
# output
def print_frog_title():
print(" ____ ___ __ __ ")
print(" / __/______ ___ _ / _ |___/ / _____ ___ / /___ _________ ")
print(" / _// __/ _ \/ _ `/ / __ / _ / |/ / -_) _ \/ __/ // / __/ -_)")
print("/_/ /_/ \___/\_, / /_/ |_\_,_/|___/\__/_//_/\__/\_,_/_/ \__/ ")
print(" /___/ ")
# input
def check_start():
print_frog_title()
print(" A game by Mopi Productions ")
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print("For a list of commands type 'h', 'help', or '?'")
def load_room(room):
char.current_room = room
room_dict = {
"burrow": burrow,
"burrow_basement": burrow_basement,
"front_of_house": front_of_house
}
func = room_dict.get(room, lambda: "ERROR") # To be quite frank I don't really understand lambda: ERROR yet
func()
def burrow():
print("You are in your burrow. The front door is to the north.")
while True:
action = actions.get_action(rooms.burrow)
if action[0] == "mopi":
load_room(action[1])
if action == "down":
if rooms.burrow.variables["trapdoor_opened"] and "lantern" in char.character_inventory:
load_room("burrow_basement")
break
elif not rooms.burrow.variables["rug_moved"]:
print("You cant go that way!")
elif rooms.burrow.variables["rug_moved"] and not rooms.burrow.variables["trapdoor_opened"]:
print("The trapdoor is shut.")
elif "lantern" not in char.character_inventory:
print("It's far too dark down there, you should stay up here until you find a light source.")
elif action == "north":
if rooms.burrow.variables["door_unlocked"]:
load_room("front_of_house")
else:
print("The front door is locked!")
elif action in rooms.directions:
print("You cant go that way!")
elif action[0] == "examine":
if action[1] in ("rug", "carpet", "lump"):
if not rooms.burrow.variables["rug_moved"]:
print("Upon further examination, the 'lump' appears to be handle shaped. You should try moving "
"the rug.")
else:
print("Your beloved family heirloom has been rudely shoved against a wall. What would Aunt Frogatha think?")
elif action[1] == "door":
print("Your door is locked. You'd unlock it, but you've lost the key.")
elif action[1] == "cabinet":
if rooms.burrow.variables["cabinet_opened"]:
if rooms.burrow.variables["jar_taken"]:
print("It's an open cabinet with barren shelves.")
else:
print("It's an open cabinet with barren shelves excepting a single empty jar.")
else:
print("It's a closed cabinet where you keep your dishware.")
elif action[0] == "move":
if action[1] in ("rug", "carpet"):
if not rooms.burrow.variables["rug_moved"]:
print("You moved the rug, and underneath you found a trapdoor! This isn't a new discovery.")
rooms.burrow.variables["rug_moved"] = True
else:
print("You've already kicked the rug into the corner! Better to not do any more damage.")
elif action[0] == "use":
if action[1] == "trapdoor" and rooms.burrow.variables["rug_moved"]:
print("You opened the trapdoor.")
rooms.burrow.variables["trapdoor_opened"] = True
elif action[1] == "door":
if not rooms.burrow.variables["door_unlocked"] and "key" not in char.character_inventory:
print("The door is locked, and you seem to have misplaced your key.")
elif not rooms.burrow.variables["door_unlocked"] and "key" in char.character_inventory:
print("The door is locked, you should using your key.")
else:
load_room("burrow_basement")
break
elif action[0] == "open":
if action[1] == "trapdoor" and rooms.burrow.variables["rug_moved"]:
print("You opened the trapdoor.")
rooms.burrow.variables["trapdoor_opened"] = True
elif action[1] == "door":
if not rooms.burrow.variables["door_unlocked"] and "key" in char.character_inventory:
print("You unlocked the front door! Welcome to the outside ")
elif "key" not in char.character_inventory:
print("The door is locked, and you seem to have misplaced your key.")
else:
print("The door is already open!")
elif action[1] == "cabinet":
if not rooms.burrow.variables["jar_taken"]:
print("You opened the cabinet, there is an empty jar inside.")
rooms.burrow.room_contents.append("jar")
else:
print("You opened the cabinet, it is empty")
elif action[0] == "close":
if action[1] == "trapdoor" and rooms.burrow.variables["rug_moved"]:
print("You closed the trapdoor.")
rooms.burrow.variables["trapdoor_opened"] = False
elif action[1] == "door":
print("The door is already closed, you like it that way.")
else:
continue
def burrow_basement():
print("You are in your basement. There is a trapdoor above you.")
while True:
action = actions.get_action(rooms.burrow_basement)
if action == "up":
if rooms.burrow.variables["trapdoor_opened"]:
load_room("burrow")
break
else:
print("The trapdoor is shut.")
elif action in rooms.directions:
print("You can't go that way!")
elif action[0] == "examine":
if action[1] == "key_hook":
if "key" in rooms.burrow_basement.room_contents:
print("It is a handmade key hook your Uncle Frogert made. Its single hook remains unoccupied.")
else:
print("It is handmade key hook your Uncle Frogert made. It holds an old key.")
elif action[1] == "icebox":
print("It is an icebox where you store your cold foods, but it hasn't been filled in months.")
elif action[0] == "move":
if action[1] == "icebox" and not rooms.burrow_basement.variables["icebox_moved"]:
pass
elif action[1] == "icebox":
pass
elif action[0] == "use":
if action[1] == "icebox" and not rooms.burrow_basement.variables["icebox_opened"]:
rooms.burrow_basement.variables["icebox_opened"] = True
if not rooms.burrow_basement.variables["fish_sticks_taken"]:
print("You opened the icebox, there's a lonely box of fish sticks inside with colorful writing on it.")
rooms.burrow_basement.room_contents.append("fish_sticks")
else:
print("You opened the icebox, it's empty devoid of a few lonely ice cubes. ")
elif action[1] == "icebox":
print("You closed the icebox like a good roommate.")
rooms.burrow_basement.variables["icebox_opened"] = False
if "fish_sticks" in rooms.burrow_basement.room_contents and not rooms.burrow.variables["fish_sticks_taken"]:
rooms.burrow_basement.room_contents.remove("fish_sticks")
elif action[0] == "open":
if action[1] == "trapdoor" and not rooms.burrow.variables["trapdoor_opened"]:
print("You opened the trapdoor.")
rooms.burrow.variables["trapdoor_opened"] = True
elif action[1] == "trapdoor":
print("The trapdoor is already opened.")
elif action[1] == "icebox" and not rooms.burrow_basement.variables["icebox_opened"]:
rooms.burrow_basement.variables["icebox_opened"] = True
if not rooms.burrow_basement.variables["fish_sticks_taken"]:
print("You opened the icebox, there's a lonely box of fish sticks inside with colorful writing on it.")
rooms.burrow_basement.room_contents.append("fish_sticks")
else:
print("You opened the icebox, it's empty devoid of a few lonely ice cubes. ")
elif action[0] == "close":
if action[1] == "trapdoor" and rooms.burrow.variables["rug_moved"]:
print("You closed the trapdoor.")
rooms.burrow.variables["trapdoor_opened"] = False
elif action[1] == "icebox" and rooms.burrow_basement.variables["icebox_opened"]:
print("You closed the icebox like a good roommate.")
rooms.burrow_basement.variables["icebox_opened"] = False
if "fish_sticks" in rooms.burrow_basement.room_contents and not rooms.burrow.variables["fish_sticks_taken"]:
rooms.burrow_basement.room_contents.remove("fish_sticks")
else:
continue
def front_of_house():
print("You are in front of your burrow")
while True:
break
"""
def room_template():
print("ROOM. EXITS")
while True:
action = actions.get_action(rooms.ROOM)
if action[0] == "mopi":
load_room(action[1])
if action == DIRECTION:
elif action in rooms.directions:
print("You cant go that way!")
elif action[0] == "examine":
elif action[0] == "move":
elif action[0] == "use":
elif action[0] == "open":
elif action[0] == "close":
else:
continue
"""
def main():
check_start()
print(
"You are in a burrow, but not just any burrow. The burrow you reside in is in fact"
"\nthe estate of Von Frogerick III, who just so happens to be your great great grandfather."
"\nThe immense and fascinating history of your lineage matters not though, for you are hungry."
"\nYou should find a fly to eat.")
load_room("burrow")
main()
Actions module:
"""This is the actions module, it contains functions required to process user input and
processes minor actions itself"""
import random
import room_info as rooms
import character_and_items as char
snarky_remark_list = ["And how exactly are you going to do that?", "Fat chance", "In your dreams", "Inconceivable!",
"Aunt Frogatha would be ashamed.."]
# Commands to add, - eat - anything else?
# output
def print_help():
print("--HELP--\n-'l' or 'look' - Provides description of room\n-'north' or 'n' - goes in specified direction"
"\n-'examine' + object to examine it more closely\n-'move' + object to move an object"
"\n-'use' + object to use an object\n-'down' or 'd' - move up or down"
"\n-'inventory' or 'i' - displays inventory\n-'open' or 'close' - opens or closes an object"
"\n-'read' - reads an object\n-'use __ on __' - uses one object on another")
# this checks if the complex action is able to be performed on the object given
def actions_match(complex_u_in, room):
if (complex_u_in[0] == "take" and complex_u_in[1] in rooms.take_ob(room)) \
or (complex_u_in[0] == "use" and complex_u_in[1] in rooms.use_ob(room)) \
or (complex_u_in[0] == "examine" and complex_u_in[1] in rooms.exam_ob(room)) \
or (complex_u_in[0] == "move" and complex_u_in[1] in rooms.mov_ob(room)) \
or (complex_u_in[0] in ("open", "close") and complex_u_in[1] in rooms.open_ob(room)):
return True
else:
return False
# Consider moving this into the items module?
def use_on(obj1, obj2):
if obj1 == "key" and char.current_room == "burrow" and "key" in char.character_inventory and obj2 == "door":
rooms.burrow.variables["door_unlocked"] = True
print("You unlocked the door!")
# Sort objects under the world class into an array.
def get_room_contents(room):
return rooms.exam_ob(room) + rooms.use_ob(room) + rooms.mov_ob(room) + rooms.take_ob(room) + rooms.open_ob(room)
# Every action that doesn't return an input should be ended with return False
# a complex action is an action containing more than one word.
def check_complex_actions(room, complex_u_in):
room_contents_list = get_room_contents(room)
# relabeled for readability
action = complex_u_in[0]
item = complex_u_in[1]
try:
# Dev command to load rooms for testing
if action == "mopi" and item in rooms.all:
return ["mopi", item]
# Dropping items is a character based action, and not a room based. Therefore it is separated.
if len(complex_u_in) == 4 and (action == "use" and complex_u_in[2] == "on") \
and (char.use_on_items[item] == complex_u_in[3]):
use_on(item, complex_u_in[3])
return False
elif len(complex_u_in) == 3 and (item + "_" + complex_u_in[2]) in char.double_word_list:
item = item + "_" + complex_u_in[2]
complex_u_in.pop()
if action == "drop" and item in char.character_inventory:
rooms.append_to_room(room, item)
char.character_inventory.remove(item)
print("You dropped the " + item)
return False
elif action == "take" and item in rooms.take_ob(room):
if item in rooms.contents(room):
char.character_inventory.append(item)
rooms.remove_from_room(room, item)
# This removes the underscore in two-word objects
if "_" in item:
item = item.replace("_", " ")
print("You took the " + item)
return False
else:
print("You see no such thing!")
return False
elif action == "read" and (item in room_contents_list or item in char.character_inventory):
item_description = char.get_description(item, "read")
if item_description != "":
if "_" in item:
item = item.replace("_", " ")
print("The " + item + " reads: " + item_description)
else:
print("You can't read that!")
return False
elif action == "examine" and (item in char.character_inventory or
(item in rooms.contents(room) and item not in rooms.exam_ob(room))):
item_description = char.get_description(item, "examine")
if "_" in item:
item = item.replace("_", " ")
print(item_description)
return False
elif item in room_contents_list:
if actions_match(complex_u_in, room):
return complex_u_in
elif action[0] == "examine":
print("There's nothing to see of importance.")
else:
print(random.choice(snarky_remark_list))
return False
else:
print("You see no such thing!")
return False
except IndexError:
# In case user inputs half of a command
print(action + " what?")
return False
def get_action(room):
complex_actions = ["move", "use", "examine", "take", "drop", "open", "close", "read", "mopi"]
while True:
# u_in stands for user input
u_in = input("-> ").lower().strip()
print()
complex_u_in = u_in.split(" ")
if complex_u_in[0] in complex_actions:
u_in = check_complex_actions(room, complex_u_in)
if u_in != False: # Consider editing this bit? little sloppy
return u_in
else:
# This loops if an action is not returned.
continue
if u_in in ("h", "?", "help"):
print_help()
elif u_in in ("look", "l"):
rooms.get_description(room)
elif u_in in ("inventory", "i"):
char.print_inventory()
elif u_in in ("north", "n"):
return "north"
elif u_in in ("south", "s"):
return "south"
elif u_in in ("west", "w"):
return "west"
elif u_in in ("east", "e"):
return "east"
elif u_in in ("northwest", "nw"):
return "northwest"
elif u_in in ("northeast", "ne"):
return "northeast"
elif u_in in ("southwest", "sw"):
return "southwest"
elif u_in in ("southeast", "se"):
return "southeast"
elif u_in in ("down", "d"):
return "down"
elif u_in in ("up", "u"):
return "up"
elif u_in in ("ribbit", "r"):
print("you let out a cute ribbit")
elif u_in in ("fuck", "shit", "damn"):
print("That was awfully rude of you :(")
elif u_in == "heck":
print("No swearing!!!! >:(")
elif u_in in ("mopi", "mopi productions"):
print("Mopi says: 'eat hot pant,, lie.'")
else:
print("That command was not recognized")
Character and items module:
"""This is the character module, it stores information on the character and items."""
double_word_list = ["fish_sticks", "key_hook", "front_door", "bedroom_door"]
character_inventory = []
current_room = None
def print_inventory():
if len(character_inventory) < 1:
print("Your pockets are empty! Wait, how do frogs have pockets?")
else:
print("--INVENTORY--")
for item in character_inventory:
print("-" + item)
# This could get tedious for every item in the game, look for an alternative solution?
def get_description(item, form):
item_dict = {
"pamphlet": pamphlet,
"fish_sticks": fish_sticks,
"key": key,
"lantern": lantern,
"photo": photo,
"jar": jar
}
ob = item_dict.get(item, lambda: "ERROR")
try:
if form == "read":
return ob.read_description
elif form == "examine":
return ob.exam_description
except AttributeError:
return "There's nothing to see of importance."
use_on_items ={
"key": "door"
}
class Items:
def __init__(self, read_desc, exam_desc):
self.read_description = read_desc
self.exam_description = exam_desc
pamphlet = Items("The flies future is YOUR Future. Donate today!",
"This is an annoying pamphlet, it came in the mail")
fish_sticks = Items("MOPI Brand Fish Sticks! Unethically sourced, deep fried fun!",
"These fish sticks are LONG expired, and unethically sourced at that. Better leave them be.")
key = Items("34TH BURROW STREET",
"It's an old cast iron key, it's been in your family for generations.")
lantern = Items("",
"It's a trusty oil lamp. It provides plenty of light, and was just recently topped off.")
photo = Items("Forever yours and always :) Jan. 10, 1993",
"It's a wedding photo of your Aunt Frogatha and Uncle Frogert. They're posing in front of the pond, "
"many flies can be seen buzzing in the background. There's something written on the back.")
jar = Items("",
"It is an empty glass jar.")
Room info module:
"""This module contains information on every room as well as the room class"""
directions = ["north", "n", "south", "s", "east", "e", "west", "w", "up", "u", "down", "d"]
all = ["burrow", "burrow_basement"]
# This is a workaround for the from room_info import * method.
# Ideally this would all be one function, with a room parameter and an info parameter
def exam_ob(room):
return room.examinable_objects
def use_ob(room):
return room.usable_objects
def mov_ob(room):
return room.movable_objects
def take_ob(room):
return room.takeable_objects
def open_ob(room):
return room.open_ob
def contents(room):
return room.room_contents
def append_to_room(room, item):
room.room_contents.append(item)
def remove_from_room(room, item):
try:
room.room_contents.remove(item)
except ValueError:
print("You see no such thing!")
def get_description(room):
print(room.description)
if room == burrow:
if not burrow.variables["rug_moved"]:
print("There is a handwoven rug lying in the center of your room, with a bothersome lump in the middle.")
elif burrow.variables["rug_moved"] and not burrow.variables["trapdoor_opened"]:
print("There is a handwoven rug rudely shoved in to the corner. In the center of your room, there is a "
"shut trapdoor.")
else:
print("There is a handwoven rug rudely shoved in to the corner. In the center of your room is an open "
"trapdoor. Talk about a safety hazard!")
if "lantern" in burrow.room_contents:
print("A lit lantern hangs loosely on the wall.")
if "pamphlet" in burrow.room_contents:
print("An annoying informational pamphlet lies on the floor.")
if "photo" in burrow.room_contents:
print("A framed photo rests cheerily on the mantle.")
if burrow.variables["cabinet_opened"]:
print("There is an open cabinet on the far wall where you store dishware.")
else:
print("There is a shut cabinet on the far wall.")
for item in burrow.room_contents:
if item not in ("lantern", "pamphlet", "cabinet", "photo", "jar"):
print("You've left a " + item + " here.")
if room == burrow_basement:
if not burrow_basement.variables["icebox_moved"]:
if not burrow_basement.variables["icebox_opened"]:
print("A securely shut ice box sits near the ladder.")
else:
print("An open ice box sits near the ladder")
elif burrow_basement.variables["icebox_moved"]:
if not burrow_basement.variables["icebox_opened"]:
print("A knocked over ice box lies miserably near the ladder, luckily its still latched shut.")
else:
print("A knocked over ice box lies miserably near the ladder, spilling its contents everywhere."
"\nIf only someone hadn't knocked it over...")
if "fish sticks" in burrow_basement.room_contents:
if burrow_basement.variables["icebox_moved"] and burrow_basement.variables["icebox_opened"]:
print("A box of fish sticks is rapidly defrosting on the floor.")
class World:
def __init__(self, exam_ob, mov_ob, use_ob, take_ob, open_ob):
self.examinable_objects = exam_ob
self.movable_objects = mov_ob
self.usable_objects = use_ob
self.takeable_objects = take_ob
self.open_ob = open_ob
burrow = World(["rug", "door", "lump"], ["rug"], ["door", "trapdoor", "cabinet"], ["lantern", "pamphlet", "photo", "jar"],
["front_door", "trapdoor", "bedroom_door", "cabinet"])
burrow.room_contents = ["lantern", "pamphlet", "photo", "cabinet"]
burrow.description = "You're in a cozy burrow, and a well furnished one at that. The room is lit by an assortment of " \
"\nwarm candles and lanterns. Your front door is to the north, and the door to your Aunt and " \
"Uncle's bedroom lies to the South West"
burrow.variables = {
"rug_moved": False,
"trapdoor_opened": False,
"door_unlocked": False,
"cabinet_opened": False,
"jar_taken": False
}
burrow_basement = World(["key_hook", "key", "icebox", "fish_sticks"], ["icebox"], ["icebox"], ["fish_sticks", "key"], ["trapdoor", "icebox"])
burrow_basement.room_contents = ["key", "icebox"]
burrow_basement.description = "You're in a muddy basement that serves as more of a storage room than any sort of " \
"living quarters.\nThere is a key hook on the far wall." \
" A ladder leads out of the basement."
burrow_basement.variables = {
"icebox_moved": False,
"icebox_opened": False,
"fish_sticks_taken": False
}
2 Answers 2
An interesting beginning to your game. I'd be interested to seeing how it turns out.
Text adventure games are usually data-driven. This means you would have a game engine (program) that would read a game description file (data), and execute the same instructions for every action in every location in the game, producing different outcomes based on the game data.
What you have written is not data driven. You have a function for handling actions in the burrow()
, another for the burrow_basement()
, and another for front_of_house()
. The code is these functions look very similar, and very repetitive, which is why we can write it once, and change the outcome based on the data.
A problem with the way you've written the game is recursion. If you start in the burrow, and you can go down into the basement or north to the front of the house. Each of those actions calls load_room(location)
which never returns, and calls its own function for handling that new room location. For instance, if you go down to the basement, load_room("burrow_basement")
is called, and it calls burrow_basement()
, which in response to an "up" action would call load_room("burrow")
, which would call burrow()
. We keep getting deeper and deeper into the call stack. If the player keeps exploring, eventually they will run into Python's stack limit. You could fix this by increasing the stack size, but that is just a kludge; the correct solution is to get rid of the recursion.
Object model
Places
You should start by defining some things. Like rooms. A room is location the player can be in. It should have a short name ("Kitchen", "Burrow's Basement"), and a longer description. The player may or may not have been in the room before. It will have connections (exits) to other locations. There may be items in the room.
class Room:
def __init__(self, name, description):
self.name = name
self.description = description
self.visited = False
self.exits = { }
self.contents = []
Things
Beyond rooms, the world is full of objects, or things. Things will have a name, and perhaps a description. Some things can be carried (photos, pamphlets, jars, ...) where as other things must remain where they are (stoves, sinks). Some things can be worn, like a coat or a hat. It might be hidden:
class Thing:
def __init__(self, name, description):
self.name = name
self.description = description
self.fixed = False
self.moved = False
self.wearable = False
self.concealed = False
Containers
Some things can be placed inside other things. Fish sticks may be found in an ice box. The ice box is usually closed, but occasionally open. Some containers may be locked, like a cabinet. Some containers may be see through, like a china cabinet. You can climb into some containers, like a wardrobe or a car, but most you cannot.
class Container(Thing):
def __init__(self, name, description):
super().__init__(name, description)
self.contents = []
self.open = False
self.locked = False
self.key = None
self.transparent = False
self.enterable = False
Is a room a container? Rooms have names, descriptions, and contents. And they are enterable.
Supporters
Some things can be placed on other things. You can put a jar on a table. You can put a key on a hook. So perhaps some containers should be considered a supporter, instead. You can even enter some supporters, such as climbing into a bed.
Animate
Some things can move around, and have behaviours. You can talk to some of them, such as a shopkeeper, and they will talk back. Other actors, might not talk back, such as a cat.
A shopkeeper (an animate thing) might be wearing pants with pockets (a container) which may contain a pocket watch (openable).
Is the player an animate container??? They can hold things (inventory), move around (animate), take things (container), hold things, wear things.
Doors
A door sits between two rooms. A door could be open or closed. If it is closed, it may or may not be locked.
class Door(Thing):
def __init__(self, name, description, key=None)
self.name = name
self.description = description
self.open = False
self.lockable = key is not None
self.locked = True if key else False
self.key = key
self.connects = []
living_room = Room("Living Room", "A place for the family to hang out")
dining_room = Room("Dining Room", "Many meals were had here")
basement = Room("Basement", "A dark and dingy hole in the ground")
trap_door = Door("Trap door", "A piece of wood, with a basic hinge and a ring handle")
trap_door.concealed = True
trap_door.connects = [living_room, basement]
dining_room.exits["south"] = living_room
living_room.exits["north"] = dining_room
living_room.exits["down"] = trap_door
basement.exits["up"] = trap_door
Here, we have two exits from the living room, north directly to the dining room (no door), and down to a trap_door
. The trap_door
is concealed, so if the player is in the living room, and the try to go "down", initially they should get a "you can't go that way". Moving the rug should reveal the trap door (marking it not concealed), allowing travel through the door to the other location it connects to. Maybe:
rug = Thing("A rug", "A thick heavy rug, passed down through the ages")
rug.fixed = True
def rug_move():
print("You pull back the corner of the rug, revealing a trap door")
rug.description = "One corner of the rug has been lifted to reveal a trap door"
trap_door.concealed = False
rug.on_move = run_move
Now if your game logic allows you to type "lift rug" or "move rug", you could parse the word "rug", find the object with that name in the location.contents
, and call that object's .on_move()
function, which tells you what happened, changes the rug's description, and removes the concealed attribute from the trap door.
Example
Something to get you started. The lantern is just a Thing
. The rug
is actually a Rug
(a special Thing
), which has an action defined in the frog.py
game file.
Notice the adventure game framework can be reused for many different games.
This is a "better" way that you're previous approach, but I wouldn't say it is a good way, yet. The framework has a lot of detail to flush out, and reworked to include better visibility and touchability. Items should optionally have a .has_light
attribute. A room .has_light
if it itself has that attribute set to True, or if an item within it has that attribute, unless the item is in a closed container (unless that container is transparent).
If you continue down this road, eventually you'll re-invent the Inform 7 interactive fiction framework. Good luck.
adventure.py
COMMANDS = { 'go', 'move', 'use', 'examine', 'open', 'close', 'inventory' }
DIRECTIONS = set()
REVERSE_DIRECTION = {}
for fwd, rev in (('n', 's'), ('e', 'w'), ('u', 'd')):
DIRECTIONS.add(fwd)
DIRECTIONS.add(rev)
REVERSE_DIRECTION[fwd] = rev
REVERSE_DIRECTION[rev] = fwd
class CantSee(Exception):
pass
class Thing:
def __init__(self, short_description, **kwargs):
self.short_description = short_description
self.long_description = None
self.concealed = False
self.scenery = False
self.fixed = False
self.openable = False
for key, value in kwargs.items():
if not key in self.__dict__:
raise ValueError("Unrecognized argument: "+key)
self.__dict__[key] = value
def description(self):
return self.short_description
def examine(self):
if self.long_description:
print(self.long_description)
else:
print("There is nothing special about it")
def move(self):
if self.fixed:
print("You can't move it")
else:
print("You move it a bit.")
class Container(Thing):
def __init__(self, short_description, **kwargs):
self.contents = {}
self.openable = True
self.open = False
self.transparent = False
super().__init__(short_description, **kwargs)
def containing():
if self.contents:
return ", ".join(item.description() for item in self.contents())
return "nothing"
def description(self):
text = self.short_description
if self.openable:
if self.open:
text += " (which is closed)"
else:
text += " (which is open)"
if self.open or self.transparent:
if self.contents:
text += "(containing " + self.containing() + ")"
return description
class Door(Thing):
def __init__(self, short_description, **kwargs):
self.lockable = False
self.locked = False
self.key = None
self.connects = {}
super().__init__(short_description, **kwargs)
self.fixed = True
self.closed = True
class Room(Thing):
def __init__(self, name, **kwargs):
self.exits = {}
self.visited = False
self.contents = set()
super().__init__(name, **kwargs)
def exit_to(self, direction, destination, door=None):
reverse = REVERSE_DIRECTION[direction]
if door:
door.connects[direction] = destination
door.connects[reverse] = self
self.exits[direction] = door
destination.exits[reverse] = door
else:
self.exits[direction] = destination
destination.exits[reverse] = self
def enter(self):
print("Location:", self.short_description)
if not self.visited:
self.describe()
self.visited = True
def visible_things(self):
return [item for item in self.contents if not item.concealed]
def describe(self):
if self.long_description:
print(self.long_description)
print()
items = [item for item in self.visible_things() if not item.scenery]
for item in items:
if item.concealed or item.scenery:
continue
if items:
print("You see:")
for item in items:
print(" ", item.description())
class Player(Container):
def __init__(self):
super().__init__("yourself")
self.long_description = "As good looking as ever."
self.openable = False
self.location = None
self.alive = True
def inventory(self):
if self.contents:
print("You are carring:")
for item in self.contents:
print(" ", item.description)
else:
print("You have nothing.")
def go(self, direction):
destination = self.location.exits.get(direction, None)
if isinstance(destination, Door):
door = destination
destination = door.connects[direction]
if door.concealed:
destination = None
elif door.closed:
if door.locked:
print("You'd need to unlock the door first")
return
print("First opening the", door.short_description)
if destination:
self.location = destination
destination.enter()
else:
print("You can't go that way")
class Game:
def __init__(self, protagonist):
self.player = protagonist
self.game_over = False
self.turns = 0
def welcome(self):
print("A text adventure game.")
def help(self):
print("Examine everything.")
def get_action(self):
while True:
command = input("\n> ").lower().split()
if command:
if len(command) == 1:
if command[0] in DIRECTIONS:
command.insert(0, 'go')
if command[0] == 'i':
command[0] = 'inventory'
if command == ['inventory']:
self.player.inventory()
elif command == ['help']:
self.help()
elif command[0] == 'go':
if len(command) == 2 and command[1] in DIRECTIONS:
return command
else:
print("I'm sorry; go where?")
elif command[0] in COMMANDS:
return command
else:
print("I don't understand")
def go(self, direction):
self.player.go(direction)
def item(self, thing):
items = self.player.location.visible_things()
for item in items:
if thing in item.short_description:
return item
raise CantSee(thing)
def move(self, thing):
item = self.item(thing)
item.move()
def perform_action(self, command):
if command[0] == 'go' and len(command) == 2:
self.go(command[1])
elif command[0] == 'move' and len(command) == 2:
self.move(command[1])
else:
print("Command not implemented")
def play(self):
self.welcome()
self.player.location.enter()
while not self.game_over:
command = self.get_action()
try:
self.perform_action(command)
self.turns += 1
except CantSee as thing:
print("You don't see a", thing)
if not self.player.alive:
print("You have died.")
else:
print("Game over.")
frog.py
from adventure import Thing, Room, Door, Player, Game
burrow = Room("Your Burrow")
basement = Room("Your Basement")
front_yard = Room("Your Front Yard")
front_door = Door("Front Door")
trap_door = Door("Trap Door", concealed=True)
burrow.exit_to('n', front_yard, front_door)
burrow.exit_to('d', basement, trap_door)
class Rug(Thing):
def move(self):
if trap_door.concealed:
print("Moving the rug reveals a trap door to the basement.")
trap_door.concealed = False
else:
super().move()
rug = Rug("a rug", fixed=True)
burrow.contents.add(rug)
lantern = Thing("a lantern")
burrow.contents.add(lantern)
player = Player()
player.location = burrow
class FrogGame(Game):
def welcome(self):
print("""\
You are in a burrow, but not just any burrow. The burrow you reside in is in
fact the estate of Von Frogerick III, who just so happens to be your great
great grandfather. The immense and fascinating history of your lineage matters
not though, for you are hungry. You should find a fly to eat.
""")
game = FrogGame(player)
if __name__ == '__main__':
game.play()
-
\$\begingroup\$ Hey! First of all, thanks for the response. It's immensely insightful and really shows how useful classes are in python. The concept is still pretty new to me, so I hope you don't mind if I try to clarify. From my current understanding, switching to an object model would basically remove the need for room functions, and have one major function that processes actions by referring to classes and class methods? Would it be subdivided into rooms still? Or could I just process the action "move rug" by having it refer to the rug which is known to be in the burrow? What if I have duplicate items? \$\endgroup\$digital_drako– digital_drako2020年04月10日 06:51:21 +00:00Commented Apr 10, 2020 at 6:51
-
3\$\begingroup\$ I would also add that each object (including actions) should have a list of synonyms that can be used, so if the player opts to "lift carpet", it will understand that this is the same as "move rug". \$\endgroup\$Darrel Hoffman– Darrel Hoffman2020年04月10日 15:23:58 +00:00Commented Apr 10, 2020 at 15:23
import room_info as rooms
import character_and_items as char
If you're going to import them this way all the time, why didn't you just name the modules rooms
and char
respectively? What's the point of all the extra typing? Btw, it's conventional in text adventures to refer to the player
; I'd go with import player
over import char
, just to de-confuse the syntax highlighter.
Your function print_frog_title
is used in only one place. You should just inline it there.
snarky_remark_list = ["And how exactly are you going to do that?", "Fat chance", "In your dreams", "Inconceivable!",
"Aunt Frogatha would be ashamed.."]
When you start version-controlling your source code, you'll realize that it's better to indent sequences so that they have one element per line. This allows you to add and remove elements without introducing extra diffs into your history. Also add a trailing comma on each element; don't sigil the last element specially from all the rest.
snarky_remark_list = [
"And how exactly are you going to do that?",
"Fat chance",
"In your dreams",
"Inconceivable!",
"Aunt Frogatha would be ashamed..",
]
English grammar nit: I notice that some of these remarks end with punctuation and some don't. Another: You wrote mantle
when you meant mantel
.
Your room_dict
has nice git-friendly indentation, but is also missing that trailing comma on the last element.
character_inventory = []
current_room = None
These two variables feel like they should be data members of a class Player
. But then there's other stuff in this module that is clearly unrelated to the player, e.g.
# This could get tedious for every item in the game...
def get_description(item, form):
So number one, I'd consider splitting that out into an items
or objects
module; and number two, you already know the non-tedious solution! Just imagine that instead of writing
pamphlet = Items("The flies future is YOUR Future. Donate today!",
"This is an annoying pamphlet, it came in the mail")
you wrote
pamphlet = Item(
name="pamphlet",
read="The flies future is YOUR Future. Donate today!",
examine="This is an annoying pamphlet, it came in the mail",
)
And instead of
key = Items("34TH BURROW STREET",
"It's an old cast iron key, it's been in your family for generations.")
imagine that you wrote
key = Item(
name="key",
read="34TH BURROW STREET",
examine="It's an old cast iron key, it's been in your family for generations.",
use_on=["door"],
)
Can you see how to implement the constructor of class Item
now?
EDITED TO ADD: It'd be something like this.
class Item:
def __init__(self, name, read, examine, use_on=None):
self.name = name
self.read = read
self.examine = examine
self.use_on = use_on or []
# self.location = burrow
# self.inroom_description = "There is a %s here." % name
No member functions are necessary yet.
And how to implement get_description
? ...Well, almost. Suppose we wrote our list of "all the items in the game" like this!
all_items = [
Item(
name="pamphlet",
read="The flies future is YOUR Future. Donate today!",
examine="This is an annoying pamphlet, it came in the mail",
),
Item(
name="key",
read="34TH BURROW STREET",
examine="It's an old cast iron key, it's been in your family for generations.",
use_on=["door"],
),
]
Now get_description
starts out something like
def get_description(verb, noun):
for item in all_items:
if item.name == noun:
if verb == "examine":
return item.examine
elif verb == "read":
return item.read
return "I don't see what you're referring to."
You could preprocess all_items
into a dict mapping from names to Item
objects, to save two levels of indentation in that loop.
def get_description(verb, noun):
item = all_items_by_name.get(noun, None)
[...]
Notice that I quietly turned Items
plural into Item
singular, and also turned form
into verb
.
# This is a workaround for the from room_info import * method.
# Ideally this would all be one function, with a room parameter and an info parameter
def exam_ob(room):
return room.examinable_objects
def use_ob(room):
return room.usable_objects
Okay, this seems insane. Why not just take the one place you call rooms.exam_ob(room)
and write room.examinable_objects
instead?
Your handling of takeable objects seems tedious and naïve... but also probably unavoidable, if you want to have that level of control over the messages. (The photo is cheerily on the mantel; the fish sticks are defrosting on the floor; etc.) If you want to add the ability to drop arbitrary objects in arbitrary places, you'll have to find a new way of doing these messages.
Have you seen Donald Knuth's literate version of Adventure? See particularly section 63, on page 43.
-
\$\begingroup\$ Thank you a ton for the smaller style pointers, it really helps. The better utilization of the item class was super smart, and I'm not quite sure why I didn't think of it! That being said, how would you go about defining the class? Would it be a simple class Item: pass, or does it use parameters still? Classes are definitely still confusing to me, but I learn best by asking questions. Thanks for your help! \$\endgroup\$digital_drako– digital_drako2020年04月10日 07:00:25 +00:00Commented Apr 10, 2020 at 7:00
-
1\$\begingroup\$ Added a definition for
class Item
. \$\endgroup\$Quuxplusone– Quuxplusone2020年04月10日 15:37:32 +00:00Commented Apr 10, 2020 at 15:37
Explore related questions
See similar questions with these tags.
owner
andlocation
, but almost everything, including people, rooms, containers, doors, even a text editor, are implemented with end-user-written methods and properties added to those minimal objects. \$\endgroup\$