Choose Your Own Pyventure/CLI CYOA Advanced

#!/usr/bin/env python

## the special line above this is called the "shebang".  On unix/linux/mac
## if the first line starts with '#!' this tells the operating system
## which program to use to run the file.  Windows tends to depend on the 
## file entension.  

## by convention, python interprets the first string in the file as 
## a module level DOCSTRING, describing the contents of the file, and what it
## does

#  '''  # note that wikibooks doesn't properly grok triple-quote strings
#  
#  Mysterious House -- a creepily fun game, in the style of a 
#  'choose your own adventure'
#  
#  
#  DATA DESCRIPTION FOR 'PAGES' 
#  
#  pages is a dictionary.  Keys are unique 'pageids', values are PAGEs,
#     described below
#  
#  each PAGE is a dictionary, with this format:
#     _key_   _value_
#     text:  (string) the content of the page DESCRIPTION
#     choices:  a LIST of CHOICES
#     (optional) end:  (choice) 'win'|'die'
#  
#  each choice is a TUPLE of this form:
#     _element_  _value_
#     0          (string) the description of the choice
#     1          (string) the 'pageid' of the page that this choice 
#                         leads to.  
#      
#      example:  
#      ('press the big red button', 'bottomless pit') will print in the game:
#      "1) press the big red button", 
#      
#      which if the user types '1', will take the user to page 'bottomless pit'
#  
#  
#  To be valid, all pages must be end, or have additional choices
#  '''

## by convention, imports come after the module level docstring
import random
import sys

## specific game data
pages = {
    'foyer': {'desc' : "the foyer of the haunted mansion  There is a door and a big red button.  creepy!", 
              'choices' : [('open the door', 'rainbow'),
                         ('press big red button', 'bottomless pit'),
                         ('chillax, and look around a bit','foyer2')]},
    'bottomless pit': {'desc' : "Oh no!  A bottomless pit.  Well, at least you haven't "+
        "found the bottom yet.  There *might* be one.", 
        'end': 'die'},
    'foyer2':  { 'desc': 'You notice another doorway in the shadows to your left, '+
        'which you pass through.  Through it, there is another room.  Something creaks '+
        'ominously. \n\n  The door slams shut behind you, and the room fills with acrid smoke.',  
           'choices': [('Copout ending?', 'copout'), ('Face your destiny','bottomed pit')]},
    'copout': dict(desc='You wake up, safe in our own bed.  It was just a dream!', end='die'),
    'bottomed pit':  dict(desc="Oh no!  A bottomless pit.  Well, at least you haven't"+
        "found the bottom yet.  There *might* be one. \n\n\nOh wait.  There is a bottom.  Or rather, "+
        "You've been laying here on the floor, and it feels like you fell down a large hole."+
        "The smoke clears and you realize that you're actually quite fine, and that you have " +
        "some new ideas about barbecue.  You rush home, and patent your recipe, which is" +
        "successful beyond belief!", end="win"),
    'rainbow': {'desc' : "a room filled with cotton candy and rainbows!", 
              'end': 'win'},
    "INTRODUCTION":  "Welcome to Mysterious Mansion!\n\nMystery, suspense, action await!",
    "COPYRIGHT":  "2009, by Gregg Lind, Creative Commons Share-Alike",
    "OUTRO":  "You've been a great audience!",
}



#### Machinery for running all games

def check_pages(pages):
    #''' make sure that every page is a win,die, or has choices coming off it.
    # We also assume that UPPERCASE pageids are special, and aren't real pages.
    #'''
    allok = True
    for pageid in pages:
        if pageid.isupper():   # we skip over INTRODUCTION and friends
            continue  # remember, 'continue' means -> go to the next iteration
        page = pages[pageid]
        choices = page.get('choices',[])
        end = page.get('end',None)
        if choices and end:
            print pageid, ": choices and end both defined"
            allok = False
        if not choices and end not in ("win","die"):
            print pageid, ": no choices, but end not one of win|die"
            allok = False
    
    return allok


def win():
    #''' a string to be printed when the player wins.'''
    win_phrases = [
       "You win!  Have some candy, then floss!",
       "The word 'squee' doesn't even begin to describe your joy."]
    
    return random.choice(win_phrases)

def lose():
    #''' a string to be printed when the player loses.'''
    lose_phrases = ["Death comes for us all someday.  Luckily, this is just digital death, so you can just pretend that it was really bad injury, followed by years of painful rehab, if you prefer.",
    "Good thing this game was free, your you'd be wanting your money back now!"]
    
    return random.choice(lose_phrases)


def move(choices):
    # ''' get player move from choices, returning a pageid
    # 
    # choices:  a list of page choices, where each choice is:
    # 
    # text, locationid'''
    print "----From here, you can -----"
    
    ii = 1  # start our numbering with 1
    valid_choices = {'q': None} # 'q' is always a valid choice
    for choice in choices:
        ## name the parts of the choice, with more meaningful names
        text = choice[0]
        pageid = choice[1]
        ## we str(ii) here because the user input will be a string
        valid_choices[str(ii)] = pageid  
        print ii, ")", text 
        ii = ii+1 
    
    ans = None
    ## get the user input, clean it up.
    while ans is None:
        ans = raw_input("Choose from " + str(sorted(valid_choices)) + ":  " ).lower().strip()
        if ans in valid_choices:
            if ans == 'q':
                sys.exit("quitting")
            return valid_choices[ans]  # return a page id
        else:
            print "Your answer,", ans, ", isn't in the choices\n"
            ans = None # so that we can ask again


def game_cli(pages,startpage):
    # '''
    # pages:  a dictionary of game pages
    # pageid: starting pageid
    # '''
    if not check_pages(pages):
        print "there is some error in the pages data, ending game"
        return None
     
    print pages.get("INTRODUCTION","Welcome to the Game!")
    print "\n\nCopyright: " + pages.get("COPYRIGHT","date unknown, by Anonymous") + "\n"
    
    # go to the first page
    page = pages[startpage]
    print page['desc'] + '\n'
    
    while not page.get("end",None):
        # find out where the person moved to, moving them
        pageid = move(page['choices'])
        # print the description for the new position
        page = pages[pageid]
        print "\n", page['desc'], "\n"

    if page['end'] == 'win':
        print win()
    elif page['end'] == 'die':
        print lose()
    else:
        print "this shouldn't happen!"        
    
    print "\n" + pages.get("OUTRO","Thank you for playing, come back soon!") 


## actually start the game, with our data
if __name__ == "__main__":
    game_cli(pages,"foyer")