Python: Exercise 36 (text adventure)


#1

Howdy! I spent a week doing exercise 36, the “build your own text adventure” exercise. It’s pretty fun! What I have at the end of the week is, as far as I can tell 100% playable from start to finish. There are a lot of comments in this file, and a few of them are for incomplete functions that I basically ran out of time on, such as the “save/load” feature. But it’s close! I may return to this once I learn a little bit more. One quick warning, it’s a little dark, inspired by the Cthulhu Mythos as well as philosopher Albert Camus. Looking for some feedback, both on the game and the code! Thanks for checking it out!

You can also find this game on pastebin if it’s easier for you to access.

from sys import exit

end_timer = 0
inv = []
location = ""
money = 0
money_taken = []
money_in_location = {
"home": 15,
"arkham": 5,
"library": 50,
"chapel": 4,
"store": 1
}
define = {
"Gun": "A shiny silver revolver with ammo. If you're not careful you could SHOOT yourself.",
"Coffee beans": "A bag of cheap coffee, pre-ground",
"Coffee": "A cup of dark, bitter coffee",
"Book": "An old book from the library, the \"Kitab al-Azif\" written by Abdul Alhazred. The book demonstrates how to make an \"Elder Sign,\" a jet black sigil that's supposed to ward off evil spirits, so powerful even the Old Gods are reluctant to go near it. Perhaps you chould MOLD something to look like this sign.",
"Gum": "The label says \"Flavor lasts for hours!\"",
"Gum wad" : "A sticky lump of gum. You feel like you could probaly mold it into a shape.",
"Gummy coffee wad": "Just disgusting. What the fuck are you doing.",
"Gummy elder sign": "Bright pink, but otherwise it should work. There's no apparent rule about material, just shape and color.",
"Gummy coffee elder sign": "The blackened sign now seems to vibrate in your hands. You get the strange urge to THROW it from a high place."
}
#take_me = {}

# TO-DO
# Check each function for specific todos
#
# Optional Goals:
#   Make a list of adverbs/adjectives that can be used randomly in conjunction with the travel statements to vary them, ie "briskly", "with purpose", etc.
#   Make a save/load game function
# Pass the timer as an argument of the room you are in, ie arkham(end_timer)
#         * Make the time an if-statement so it can change the description of the room
#         * How, then, do you change everything? Are the variables stored in dictionaries?
#            * Potentially you wouldn't have to change everything, just the description and maybe (MAYBE) the search.
#

# Game Start, runs once
def start():
    global location
    print("""
       Camus in Arkham
    By Christian Mitchell
    """)
    location = ""
    #load_game = input("Would you like to load a game? Y/N \n> ").lower()
    #if load_game == "y":
    #    load()
    #else:
    help()
    print("You wake up in the morning and can't help but feel something is off. The hairs on the back of your neck raise. Something's coming. Something large, larger than you can rationally conceive. Impending doom. There isn't much you can do. \n\n\nYou're out of coffee.\n\n")
    home()

# Displays when the game starts and whenever someone inputs "help"
def help():
    print("""Controls:
    "Travel": change locations
    "Search": Examine your surroundings more closely
    "Take [an object]", ie "Take Money": Pick up the object that you find on the street, adding it to your inventory
    "Inventory": Display your inventory
    "Help": Displays the controls again
    "Talk": Talk to someone
    "Examine [an object]", ie "Examine Apple": Take a closer look at an object in your inventory
    "Buy [an object]", ie "Buy Apple": Purchase an object at the store, provided you have the money.

    Look out for any other contextual COMMANDS you might find.


    """)

# Locations

# Home
# Unfinished Goals:
#   Take:   If you have a gun, kill yourself?
#

def home():
    global inv, location, money
    location = "home"
    if "Coffee beans" not in inv:
        print("You are home. A small house in Arkham, Rhode Island.")
        print("Outside of your home, the streets of Arkham stretch out under the rays of the dawning sun. Something unnatural is present in the light. A color that defies description.\n")
    else:
        print("The light outside is almost blinding and inescapable. It compounds with your headache and you feel as though a nail is being driven into your skull.")
        print("You could probably BREW some coffee if you wanted, though you only have enough coffee grounds for one cup.")

    user_interface()

# The streets of Arkham
#
def arkham():
    global inv, location
    location = "arkham"
    print("Arkham, Rhode Island. A beggar sits on the street corner, his face old and filthy. There aren't usually beggars in this town, but to see this man is not altogether unsettling for you. You look around at the air and feel a deathly stillness. None of the usual sounds of birds or bugs or people greet your ears.")
    user_interface()

# Miskatonic Library
# Unfinished Goals:
#   Talk: Beggar on the street corner, who warns about what's going to happen.
#
def library():
    global inv, location, money
    location = "library"
    print("The Miskatonic Library is old. Dust collects on its many books. The air feels cool and the warm light of the lamps inside make you feel less tense. The librarian reads at her desk. She coughs and hums intermittently.")
    user_interface()

# The Chapel
#
def chapel():
    global inv, location, money
    location = "chapel"
    print("The small chapel stands at the very end of the road, away from any other buildings. Devoid of people, rows of pews face an alter. You wonder if this chapel has ever been entirely full. In a town like Arkham, probably not.")
    user_interface()

# The roof of the chapel
#
def chapel_roof():
    global inv, location, money
    location = "chapel_roof"
    print("Cold wind whistles past your face. It's much higher up than you expected. You'd probably die if you tried to JUMP down.")
    user_interface()

# Arkham General Store
#
def store():
    global inv, location, money
    location = "store"
    print("The shop is brightly lit, stickers for cigarettes and cheap alcohol covering most of the window. It would make sense that the shopkeeper didn't see the strange nature of the outside world. The shop contains an odd array of items, including coffee, cigarettes, gum, firearms, and more.")
    user_interface()


# Experimenting with making one function to handle user input
def user_interface():
    time_out(end_timer)
    print("\nWhat would you like to do?\n")
    while (True):
        choice = input("> ").lower()
        if choice == "help":
            help()
        elif choice == "inventory":
            inventory()
        elif choice == "travel":
            if location == "chapel_roof":
                print("You should CLIMB down first.")
            else:
                travel(location)
        elif "take" in choice:
            take(choice)
        elif choice == "search":
            search(location)
        elif "climb" in choice:
            climb()
        elif choice == "jump":
            jump(location)
        elif "chew" in choice:
            chew(location)
        elif "talk" in choice:
            talk(location)
        elif "brew" in choice and location == "home" and "Coffee beans" in inv:
            brew()
        elif "drink" in choice and "Coffee" in inv:
            drink()
        elif "examine" in choice:
            examine(choice)
        elif "buy" in choice:
            buy(choice)
        elif "throw" in choice:
            throw()
        #elif choice == "save":
        #    save()
        #elif other commands
        else:
            print("The universe either doesn't understand, or doesn't care.")

# Travel function to handle location changes
def travel(location):
    global end_timer
    print ("Where would you like to go? (Type 'cancel' if you change your mind.)")
    travel_location = input("> ").lower()
    if travel_location == location:
        print("You're already here.")
        travel()
    elif travel_location == "library":
        if location == "arkham":
            end_timer += 1
            time_out(end_timer)
            print("You duck into the library.\n\n")
            library()
        else:
            end_timer += 2
            time_out(end_timer)
            print("\nYou walk briskly down the street and arrive at the library.\n")
            library()
    elif travel_location == "arkham":
        print("\nYou step outside.\n")
        end_timer += 1
        time_out(end_timer)
        arkham()
    elif travel_location == "chapel":
        if location == "arkham":
            end_timer += 1
            time_out(end_timer)
            print("\nYou duck into the chapel.\n")
            chapel()
        else:
            end_timer += 2
            time_out(end_timer)
            print("\nYou walk briskly down the street and arrive at the chapel.\n")
            chapel()
    elif travel_location == "home":
        if location == "arkham":
            end_timer += 1
            time_out(end_timer)
            print("\nYou duck into your home.\n")
            home()
        else:
            end_timer += 2
            time_out(end_timer)
            print("\nYou walk briskly down the street and arrive home.\n")
            home()
    elif travel_location == "store":
        if location == "arkham":
            end_timer += 1
            time_out(end_timer)
            print("\nYou duck into the store.\n")
            store()
        else:
            end_timer += 2
            time_out(end_timer)
            print("\nYou walk briskly down the street and arrive at the store.\n")
            store()
    elif travel_location == "help":
        help()
        travel(location)
    elif travel_location == "inventory":
        inventory()
        travel(location)
    elif travel_location == "cancel":
        cancel_travel(location)
    else:
        print("\nThat must not be a place in this dimension. You can't go there.\n")
        travel()

# Search function, based on location
def search(loc):
    search_answers = {
    "home": "Your house is small. You have a small pile of money on the counter, maybe $15 all told. Your coffee maker stands on the counter, plugged in and ready for use. An empty coffee bag is in the trash. Your decaffinated brain throbs at the sight. You look out the window and see that the ARKHAM streets, the CHAPEL, the STORE, and the LIBRARY are all within easy walking distance.",
    "arkham": "The beggar on the street corner looks at you briefly before returning his gaze skyward. He doesn't seem to be bothered by the strange light. You scuff your foot and find money on the underside of your shoe. Your HOME, the CHAPEL, the STORE, and the LIBRARY are all within easy walking distance.",
    "library": "The library is silent save for the librarian making soft humming and coughing sounds. You run your hands along the spines of the books in front of you. A corner of paper stuck between two books catches your finger, giving you a small cut. After sucking on the cut, you pull the paper out from between the books to find a crisp $50 bill. A note on the bill says \"Do what you think is right.\" \nYour HOME, the CHAPEL, the STORE, and the streets of ARKHAM are all within easy walking distance.",
    "chapel": "Four $1 bills lay in a collection tray near the front of the chapel, with a sign over the tray saying, \"For the children of Arkham Orphanage\". On the rear wall, you see a ladder you suppose you could CLIMB if you wanted to. Someone has already unbolted the hatch to the roof. \nYour HOME, the LIBRARY, the STORE, and the streets of ARKHAM are all within easy walking distance.",
    "store": "On the ground on the customer side of the counter, you see a one dollar bill that someone apparently dropped. The shopkeeper is mopping the floor of one of the aisles. You look down to see you've tracked mud from the street onto the his freshly polished floor. The shopkeeper smiles as he listens to the radio. One must imagine him happy. A few items catch your eye that you think you might like to BUY: some Coffee beans, a pack of Gum, or a shiny Gun. \nYour HOME, the CHAPEL, the LIBRARY, and the streets of ARKHAM are all within easy walking distance.",
    "chapel_roof": "The round clay shingles are worn, with fresh cracks in a few suggesting that someone was up here recently."
    }
    print(search_answers[loc])

# take function, which splits a string by ' ' and uses the second word (the non-"take" word) to take an object
def take(s):
    global inv, define
    if len(s) <= 6:
        print("You take nothing.")
    else:
        obj = s.split(None, 1)
        obj = obj[1].capitalize()
        if obj in inv:
            print("You already took that.")
        elif obj == "Money":
            money_adder(location)
        #elif obj in define and obj in take_me:         # Not Needed?
        #    print(f"You take the {obj}.")              #
        #    inv.append(obj)                            #
        else:
            print(f"You can't take the {obj}.")

# Inventory function, seems easier to do this than repeat a print statement
def inventory():
    print ("\nYou're carrying have the following:")
    for i in inv:
        print(i)
    print(f"${money}\n")

# Talk function which calls to conversation tree
def talk(location):
    global end_timer, inv
    if location == "arkham":
        end_timer += 3
        print("""
        You approach the beggar, who smiles at you. His grin reveals more gaps than teeth. As you get closer, you see that what you thought was dirt and wrinkles covering his face are actually tattoos. This man is entirely marked, with only hints of plain skin between the elaborate designs that cross over his face. He opens his mouth to speak, and the voice that you hear is strong, confident.

        "Hello sir, how are you enjoying your morning?"

        The question takes you aback. You respond with a greeting and ask about his morning.

        "Fine, sir, just fine. Beautiful weather, is it not?" As he speaks, the glow grows brighter. It's as if the sun was on the other side of the planet, shining so brightly its light penetrated the very ground. Shadows fade and from somewhere you feel a deep, ominous hum. Your vision turns black and suddenly you find yourself standing on a dark, gravely shore. You watch as a single, giant hand breaches the surface of the water. The flesh of the beast is grotesque, scaly, as it reaches out to you...

        Just as suddenly you are again standing in front of the beggar, his toothless smile suggesting he knows where you were and what you saw. "As I said, sir. Beautiful weather. And change enough for us all."

        You walk away in a slight daze. Not until a little later do you realize you've been walking in a circle. You find yourself once again on Arkham's Main Street.
        """)
        arkham()
    elif location == "store":
        print("""
        You wave to the shopkeeper to attract his attention. He looks up, broken out of his music-induced reverie. "Oh! Sorry, I didn't realize anyone had come in." He hastily makes his way to the counter, knocking a rack holding chips over in his hurry. "Oop!" He is not the most intelligent or agile fellow, but you can see he's a kind man. "Can I help you find anything?"

        $70 - Gun
        $10 - Coffee beans
        $1  - Gum
        """)
    elif location == "library":
        print("""
        The librarian looks up over her spectacles. "Hem, hmm. Hello sir. How are you doing?" The librarian coughs and hums through your reply, she can't seem to help it. You mention the strange weather outdoors which sends her into a coughing fit.

        "Ah! Aha, ehem, hmm! Yes, uhm, hmm very interesting, eherm. It reminds me," she continues coughing as she bends down. "Here it is! Ahem, heh hmm." She produces a large tomb, the... "Kitab al-Azif" you think it says. "A lot of the weather today... ahem ah hmm... " she stops to cough and hum, then flips to a bookmarked section. "Here it is, aham." She hands the book to you, pointing out the signs of a coming... God? Cthulhu?.
        """)
        if "Book" in inv:
            print("You feel strange about the book in front of you.")
        else:
            inv.append("Book")
    else:
        print("There's nobody to talk to.")

# Examine for objects in your inventory
def examine(s):
    global inv, money
    if len(s) <= 9:
        print("You examine nothing.")
    else:
        obj = s.split(None, 1)
        obj = obj[1].capitalize()
        if obj in inv:
            print(define[obj])
        elif obj == "Money":
            print(f"${money} in small bills. Nothing remarkable about them.")
        else:
            print("Examine what now?")

# Buy an object, which checks that you're in the shop and you have the money then adds an item to your inv
def buy(s):
    global inv, money
    if len(s) <= 5:
        print("You buy nothing.")
    else:
        obj = s.split(None, 1)
        obj = obj[1].capitalize()
        if obj in inv:
            print("You already bought that.")
        elif obj == "Coffee beans" or obj == "Coffee":
            if money >= 10:
                print("You purchase the pre-ground Coffee beans.")
                money -= 10
                inv.append(obj)
            else:
                print("You can't afford that.")
        elif obj == "Gum":
            if money >= 1:
                print("You purchase the pack of Gum.")
                inv.append(obj)
            else:
                print("You can't afford that.")
        elif obj == "Gun":
            if money >= 70:
                print("You purchase the Gun.")
                inv.append(obj)
            else:
                print("You can't afford that.")
        else:
            print("You don't see a reason to purchase that.")


# Auxillary utility functions

# The easiest way I can find to cancel travelling. Theoretically, a better way would be to somehow convert a string to a function in the same way one converts a string to an integer or vice versa
def cancel_travel(location):
    print("\n")
    if location == "home":
        home()
    elif location == "library":
        library()
    elif location == "chapel":
        chapel()
    elif location == "store":
        store()
    else:
        arkham()

# Money adder is a special function to add money rather than add "Money" to your inventory
def money_adder(location):
    global money_taken, money
    if location in money_taken:
        print("There is no more money to take.")
    else:
        print (f"You pocket the ${money_in_location[location]} you found.")
        money += money_in_location[location]
        money_in_location[location] = 0
        money_taken.append(location)

# Timer function to end the game via timeout
def time_out(time):
    if time > 10:
        end4()

# Context-specific to the chapel, with a catch case to prevent anything from being climbed.
def climb():
    global end_timer
    if location == "chapel":
        print("You climb the ladder.")
        chapel_roof()
    elif location == "chapel_roof":
        print("You carefully climb back down.")
        end_timer += 1
        chapel()
    else:
        print("There is nothing to climb.")

# Jumping off the roof to get end0().
def jump():
    if location == "chapel_roof":
        end0()
    else:
        print("You jump in place. You feel proud about how high you can jump.")

# Chewing gum for preparation for the "good" ending.
def chew():
    global inv
    if "Gum" in inv:
        print("You take your gum out and chew it, then wrap the wad in the wrapper.")
        inv.remove("Gum")
        inv.append("Gum wad")
    elif "Gum wad" in inv:
        print("You are tempted to chew your gum again, but think better of it.")
    else:
        print("You have nothing to chew, so you bite your lip idly while you think.")

# Throwing for getting the "good" ending, end3.
def throw():
    if location == "chapel_roof" and "Gummy coffee elder sign" in inv:
        print("You hurl the sign skyward where it seems to darken its surroundings as it flies higher and higher. You feel like you're not looking directly at it. You squint your eyes to see it more clearly. You think you see something, another place with hoards of pallid beings, seemingly half-fish and half-frog, baying, pushing each other out of the way to reach the edge of the rip in the sky one reaches out, breaking the barrier and then...\n\nYou find yourself standing on the street outside your home.")
        end3()
    elif "Gummy coffee elder sign" not in inv:
        print("You have nothing you particularly want to throw right now.")
    else:
        print("You feel like this is not the right time or place.")

# Molding the gum into an elder sign
def mold():
    global inv
    if "Gum wad" in inv and "Book" in inv:
        print("Using the book as a guide, you fashion a bubblegum pink elder sign.")
        inv.remove("Gum wad")
        inv.append("Gummy elder sign")
    elif "Gummy coffee wad" in inv and "Book" in inv:
        print("Using the book as a guide, you fashion the disgusting mess into an elder sign.")
        inv.remove("Gummy coffee wad")
        inv.append("Gummy coffee elder sign")
    elif "Gum wad" in inv and "Book" not in inv:
        print("You don't know what you'd want to make.")
    else:
        print("You have no material to mold with.")

# Combining the coffee and gum. Also used as a catch case if people try to combine the book and the gum.
def combine(case):
    global inv
    if case == gum:
        mold()
    if case == coffeewad:
        inv.remove("Coffee beans")

# Making coffee
def brew():
    global inv
    inv.remove("Coffee beans")
    inv.append("Coffee")
    print("You brew a nice cup of coffee. Perfect. You should probably DRINK it before it gets too cold.")

# Drink
def drink():
    if location != "arkham":
        print("You step out to the streets of arkham to enjoy your coffee.")
    end1()

# Shoot self
def shoot():
    global end_timer
    if "Gun" in inv:
        end 2()
    elif location == "home":
        print("You shoot yourself a couple of finger guns in the mirror. What a cool guy.")
    elif location == "chapel":
        print("You shoot god a couple of finger guns.")
    elif location == "library":
        print("You shoot the librarian a couple of finger guns. She is flustered, but tentatively shoots one back with a smile.")
    elif location == "store":
        print("You shoot the shopkeeper a finger gun. He laughs.")
    elif location == "arkham":
        print("You shoot the beggar a couple of finger guns. He returns fire. The two of you play a game, ducking behind trash cans or hiding behind lampposts. A fun way to spend a morning.")
        end_timer += 1
    else:
        print("You have nothing to shoot.")

#def save():
#    global end_timer, inv, location, money, money_taken, money_in_location
#    save_title = input("Give a title for your savegame\n> ")
#    save = open(f"{save_title}.txt", "w+")
#    save.write(f"{end_timer},{inv},{location},{money},{money_taken},{money_in_location}")
#    save.close()
#    print("Game Saved.")
#    cancel_travel(location)

#def load():
#    load_title = input("What's your game file?\n> ")
#    load = open(f"{load_title}.txt", "r")
#    load_order = load.read().split(",")
#    inv_feeder = load_order[1]
#    end_timer = int(load_order[0])
#    inv = load_order[1]
#    location = load_order[2]
#    money = int(load_order[3])
#    money_taken = load_order[4]
#    money_in_location = load_order[5]
#
#    load.close()
#    print("\nGame Loaded.\n")
#    cancel_travel(location)

# Endings

def end0():
    # You jump off the roof of the chapel too early.
    print("\nThe ground rushes up to meet you.")
    dead()

def end1():
    # This is the coffee ending. You must buy (at the store), make (at home), and drink (anywhere/Maybe outside?) coffee to get this ending.
    print('\nYou gaze up at the sky, now dark though your watch says it\'s not even noon yet. Your watch says "He is here." You look down in mild surprise to find your watch actually talking. It grows larger and larger on your wrist. "Spare a sip?" Your watch face opens into a horrible mouth, gears resembling teeth lining the hole. You oblige, and pour coffee on your wrist as reality ceases.')
    dead()

def end2():
    # This is the kill yourself ending. There should be some flavor text depending on where you do it, but that can go in the location functions.
    print('\nNietzsche once said, "God is dead." If that is so, then you must be one step closer to God. These are thoughts you\'d be sure to think if you were still alive, but as the world implodes and the sky darkens, not a thought remains for you to piece together. Though in the arms of Cthulhu, no child is allowed to die quite so easily. Your new life is just beginning.')
    dead()

def end3():
    # This ending is the result of stopping Cthulhu. Not sure how you do that yet.
    print("\nThe sky is light. No hint of the eerie color that shadowed the heavens. As you turn to go back inside, a glimmer on the street catches your eye. You pick up a fish scale, larger than you\'ve seen on any fish. Looking around you catch the beggar staring at you. He nods at you and you nod back. You head inside.  Your head pounds from your lack of caffeine.")
    dead()

def end4():
    # This is the ending where you run out of time. There is a global counter that incriments every time you travel (and maybe with dialog as well)
    print("\nA car is pulling a woman into its grill, which has morphed into crooked, metallic teeth. Looking up, you see robed followers on the roof of the chapel, dancing in unholy delight. As you watch, one of the members falls off the roof, his neck snapping as he hits the ground. The others either do not notice or do not care, all their gazes fixed on the sky. You can see why immediately. A large, winged, scaly and tentacled being towers above them, looking down. One hand reaches for them, the other holding that no sane person would call a sword. But in this moment, you can think of no other word for it. As it writhes the chapel implodes, eminating darkness. You watch your past self walking to and from the store. Minutes later, or instantly, you hand yourself a cup of coffee. As you take the cup, you cease to think.")
    dead()

def dead():
    print("\n\nGame over. Play again? Y/N")
    YN = input("> ")
    if YN.lower() == "y" or YN.lower() == "yes":
        start()
    else:
        exit(0)


# Start the program
start()

#2

Hi @koncerna cool idea with the user_interface() function. :+1: