8
\$\begingroup\$

I'm new to Python and programming in general, but I'm making it work. I have this little game I've been working on as a learning exercise and I'm a bit concerned that I'm using far too many if/else statements for the user menus. Is there perhaps a better way?

# Bizarro house text adventure game
from sys import exit
import time
#error message for an incorrect choice
def error():
    print """
    Invalid number
    Time rewinds to the beginning of your journey.
    """
    start()
#Beginning of the game
def start():
    print """
    You're standing at the entrance to an old spooky mansion.
    It's just after midnight.
    What would you like to do?
    1. Go inside
    2. leave
    """

    choice = raw_input("> ")

    if choice == "1":
        entrance()
    elif choice == "2":
        dead("This could have been an exciting adventure if you weren't too scared,")
    else:
        print "Wrong answer try again."
        start()
def dead(why):
    print why, "better luck next time."
    exit(0)
# Group of functions for the downstairs area    
def entrance():
    print """
    There is a room on the left,
    a room on the right,
    and a kitchen in the back.
    Also a set of stairs
    Which way?
    1. Left
    2. Right
    3. Kitchen
    4. Upstairs
    """

    choice = raw_input("> ")
    if choice == "1":
        left_room()
    elif choice == "2":
        right_room()
    elif choice == "3":
        kitchen()
    elif choice == "4":
        stairwell()    
    else:
        error()


def left_room():
    print """
    The room is dark.
    You hear a voice that says:
    'I see you, and so does Jesus'
    You use your cell phone for light.
    You shine the light around the room. 
    You find a lightswitch.
    You also shine the light on a parrot cage.
    The parrot repeats his warning.
    'I see you and so does Jesus'
    What do you do?
    1. Turn on light
    2. Leave room
    """

    choice = raw_input("> ")
    if choice == "1":
        dead("You turn on the light and notice a huge Rottweiller. The parrot says 'Sick em Jesus' at least you were a tasty meal,")
    elif choice == "2":
        entrance()
    else:
        error()

def right_room():
    print """
    In the room you notice something floating in mid air.
    Its a $100 bill.
    What do you do?
    1. Try to take the money
    2. Leave the room
    """
    choice = raw_input("> ")
    if choice == "1":
        cash()
    elif choice == "2":
        entrance()
    else:
        error()

def cash():
    print """
    You reach for the money.
    A ghost in the room gives its poetic warning:
    'I am the ghost of Daniel Boone,
    take this money and you'll be doomed'
    What will you do now?
    1. Take the money anyway
    2. Leave the room
    """
    choice = raw_input("> ")
    if choice == "1":
        print """ 
        You reply with your own poetic quip:
        'Yeah well I'm the ghost of Davy Crockett,
        that money is going in my pocket'
        """
        dead("You never even touch the money, something hit you in the head and that was it,")                        
    elif choice == "2":
        entrance()
    else:
        error()

def kitchen():
    print """
    Its seems to be a basic kitchen.            
    There's a refrigerator, a pantry,
    and an oven. 
    It smeels like something may have been cooking recently.
    You could use a snack.
    """
    kitchen_menu()

def kitchen_menu():
    print """
    What now?
    1. Check pantry
    2. Check refrigerator
    3. Check oven
    4. Leave room
    """

    choice = raw_input("> ")
    if choice == "1":
        print "You find a dead rat."
        kitchen_menu()
    elif choice == "2":
        print "Its empty."
        kitchen_menu()
    elif choice == "3":
        dead("You open the oven and are immediately sucked into an inter-dimensional portal,")
    elif choice == "4":
        entrance() 
    else:
        error()       




#Functions for the upstairs area

def stairwell():
    print """
    At the top of the stairwell you see three rooms.
    1. A bedroom
    2. A library
    3. A recording studio
    4. Back downstairs
    Which way?
    """

    choice = raw_input("< ")
    if choice == "1":
        bedroom()
    elif choice == "2":
        library()
    elif choice == "3":
        studio()
    elif choice == '4':
        entrance()    
    else:
        error()

def bedroom():
    print """
    In the room is a very large kid.
    He's 10 years old, 6'7, 290 lbs.
    He's watching cartoons.
    He tells you his mom said he could watch.
    What do you do?
    1. Turn off TV
    2. Leave room
    3. Ask if you can watch too.
    """

    choice = raw_input("> ")
    if choice == "1":
        dead("The kid gets mad, picks you up and throws you through the second story window, without opening it,")
    elif choice == "2":
        stairwell()
    elif choice == "3":
        print "You sit and watch for a while, then leave the room."
        time.sleep(5)
        stairwell()                            
    else:
        error()

def library():
    print """
    Its a nice looking old library.
    You notice one particular book.
    Fear and Loathing in Las Vegas.
    You've always wanted to read that.
    What do you think?
    1. Take the book off the shelf
    2. Leave the room
    """

    choice = raw_input("> ")
    if choice == "1":
        secret_room()
    elif choice == "2":
        stairwell()
    else:
        error()

def secret_room():
    print "The bookshelf and floor spin around into a secret room."
    time.sleep(5)
    dead("In the secret room was a very hungry bear,")

def studio():
    print """
    You enter the studio and meet the owner of the house.
    He turns out to be a very eccentric record producer.
    He asks if you'd like to put down some vocals on his newest track.
    What do you tell him?
    1. Yes
    2. No
    """

    choice = raw_input("> ")
    if choice == "1":
        big_win()
    elif choice == "2":    
        print """
        The producer gives you an angry scowl,
        Then presses a button on his soundboard.
        You are teleported to Sandusky Ohio's
        annual meat parade.
        """
        dead("You are run over by the pork chop float,")
    else:
        error()    

def big_win():
    print "You lay a vocal track."
    time.sleep(3)
    print "It gets you signed to a major record label."
    time.sleep(3)
    print "The song is a hit."
    time.sleep(3)
    print "You make a fortune."
    time.sleep(3)
    print "You have won this game."
    exit(0)

start()
\$\endgroup\$
3
\$\begingroup\$

While a full object-oriented approach would work, I would go for a more lightweight solution that leveraged Python's native data structures. First, define all of your states like so:

states = {
  "start": {
    "message": '\n'.join("You're standing at the entrance to an old spooky mansion.",
                         "It's just after midnight.",
                         "What would you like to do?"),
    "options": [("Go inside", "entrance"), ("Leave", "dead")]
  },
  "entrance": {...
  }
}

Then you can handle all of the game logic in one loop:

state = states['start']

while True:
  print state['message']

  if state is states['dead']:
    exit(0)

  for i, opt in enumerate(state['options']):
    print "{}. {}".format(i+1, opt[0])

  answer = raw_input("> ")
  if validate(answer, state['options']):
    state = states[ state['options'][int(answer)-1][1] ]
  else:
    state = states['dead']

validate() would just do basic checking (is it a number, is it actually an option in the list, etc.). I'll leave the body of that function to you.

There are a few advantages to this approach. Compared to your original, it operates within a single stack frame rather than creating a new frame for every new state. It also removes a lot of duplicate code. Compared to the fully object-oriented approach, it uses a lot less code, which means it's easier to maintain, has less potential bugs, less overhead, etc.

The most significant advantage to this approach, though, is that it fully separates data from logic. You don't even have to touch the game code to change the adventure. In fact, you could store the adventure data in a serialization format (JSON and YAML are popular choices) and not even open the source code to make changes. If you worked in a game design company, you could pass off the adventure design to the artists/writers and dust your hands off, knowing you'd fully completed the engine with that single while-loop.

\$\endgroup\$
8
\$\begingroup\$

Python allows for object oriented programming. Using this would simplify your program immensely. So let's walk through remaking this.

Let's think about what your game consists of. You have locations, options the player can choose from, and actions those options should trigger. In addition, you want to keep track of some state about the player (alive, any items, etc.).

First, we want to define a game state. The state should keep track of our current location, if we are alive, and a map of locations. A few extra functions are added to make it easier later.

class State:
    def __init__(self, starting_loc):
        self.alive = True
        self.location = starting_loc
        self.locations = {}

    def addloc(self, location):
        self.locations[location.name] = location

    def gotoloc(self, locname):
        self.location = self.locations[locname]

Now we want to define a location. Locations have their description and a list of things you can do there. Simple enough, but let's also give them a name, since we want to map their name to them in the State class. We also define a method for getting user input and executing the action if it was good.

class Location:
    def __init__(self, name, desc, options=None):
        self.name = name
        self.desc = desc
        self.options = options

    def start(self):
        print self.desc

    def print_opts(self):
        if(self.options != None):
            for i in range(len(self.options)):
                print "  {0}. {1}".format(i, self.options[i].text)

    def get_choice(self, state):
        choice = raw_input("> ")
        print "You chose \"{0}\"".format(choice)
        try:
            index = int(choice)
            self.options[index].action.execute(state)
            return True
        except Exception as e:
            print(e)
            print("Please choose a valid option")
            return False

Now, what should we put in options? You can see above that I want to be able to get an action from them, and I want to print some text from them, so we define the class like so

class Option:
    def __init__(self, text, action):
        self.text = text
        self.action = action

Now what about actions themselves? They need to define an execute method that takes a state object. So lets define an action for moving and killing the player.

class GoToLocation:
    def __init__(self, location):
        self.loc = location

    def execute(self, state):
        state.gotoloc(self.loc)
        state.location.start()

class KillPlayer:
    def __init__(self, message):
        self.message = message

    def execute(self, state):
        state.alive = False
        print(self.message)

Now let's create a few locations:

start_loc = Location("start",
                     "You're standing at the entrance to a spooky mansion",
                     [Option("Go Inside", GoToLocation("entrance")),
                      Option("Leave", KillPlayer("Scardy Cat"))])

entrance = Location("entrance",
                    """You are standing in the entrance hall.
There is a room on the left, a room on the right, and a kitchen in the back.    
There is also a set of stairs in front of you.""",
                    [Option("Left", KillPlayer("The building collapses")),
                     Option("Right", KillPlayer("The building collapses")),
                     Option("Kitchen", KillPlayer("The building collapses")),
                     Option("Upstairs", KillPlayer("The building collapses")),
                     Option("Outside", GoToLocation("start"))])

And now we can put it all together:

if(__name__=="__main__"):
    s = State(start_loc)
    s.addloc(start_loc)
    s.addloc(entrance)
    s.location.start()
    while(s.alive):
        s.location.print_opts()
        s.location.get_choice(s)

If you want different actions, you can now create a simple class that defines the execute method. So, let's say you wanted a Message action that doesn't do anything but print it's message.

class Message:
    def __init__(self, msg):
        self.msg = msg
    def execute(self, state):
        print self.msg

This class won't change the state of the game but will print the message given.

A class to change what an option does is also possible

class OptionMutator:
    def __init__(self, location, index, newoption):
        self.locname = location
        self.index = index
        self.newoption = newoption
    def execute(self, state):
        loc = state.locations[self.locname]
        if(self.index < 0 or self.index >= len(loc.options)):
            loc.options.append(self.newoption)
        else:
            loc.options[self.index] = self.newoption

How about an action that does multiple actions? Easy!

class MultiAction:
    def __init__(self, actions=None):
        self.actions = actions
    def execute(self, state):
        if(self.actions == None): return
        for action in self.actions:
            action.execute(state)

And so on. You can define loads of new locations, actions, etc. as data instead of with functions now.

So let's do so:

newlocation = Location("upstairs", """You arrive in the attic.
It is very creepy up here""",
                       [Option("Downstairs", GoToLoc("entrance")),
                        Option("Explore",
                               MultiAction([Message("You find a trapdoor!"),
                                            OptionMutator("upstairs", 3, Option("Trapdoor", KillPlayer("You die. It was a trapped door.")))]))])

If you then add that to the state, and change the stairs option to

Option("Upstairs", GoToLoc("upstairs"))

then the player can now go upstairs.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.