"""
 An interactive fiction game
 implemented as a finite state machine.

 Jim Mahoney | Oct 2012 | MIT License
"""
from random import randint

# This is designed to be an illustration one of the cleaner ways to
# implement an interactive fiction program, using what computerists
# call a "finite state machine".
#
# The game itself is a simplified "dungeon crawl" with this room layout.
#           
#                        North
# 
#                        kitchen
#                        |
# West       entrance -- parlor            East
#                        |
#                        bedroom
# 
#                        South           
#
# You can be in any one of the four rooms.  That's all that can change
# in this tiny game; therefore, there are 4 states, where "a state" is
# a complete description of the current situation.
#
#    states = ('entrance', 'parlor', 'kitchen', 'bedroom', '?')
#
# OK, that's actually 5 states. I've added an extra to represent
# any error situations that might arise. If all goes well, we'll
# never get to '?'.
#
# The state changes in response to possible actions.  There are only 5
# possible actions in this game (I said it was tiny), namely the
# 4 compass directions and 'look'.
#
#    actions = ('n', 's', 'e', 'w', 'l')

# A complete specification of what can happen in this situation
# (including what is told to the user) can be thought of as a big
# table of (current_state, action) => (new_state, output) that defines
# what change happens when. This can be encoded as a python
# dictionary, as given below.
#
# In the dictionary I'm using a 6th action-like wildcard symbol '*'
# to mean "all the other actions I haven't specified".
#
# In a larger game the state would include much more than just the current
# room.  For example, if there was a cat in the rooms, it could be a
# tuple such as (person_location, cat_location).  For any practical
# game, the full table would be gigantic. So typically, only part of
# the transition logic is hardcoded into a table, while the rest is done
# with some sort of program logic, as in the transition() function
# below. Nevertheless, the transition function still accomplishes
# the same thing, to map current states and actions to future
# states and output dialogs.
#
# Additional game behaviors may also be implemented outside the finite
# state model. In the code below I have two such special behaviors,
# namely 'quit' and 'magic'. But the essense of the engine is 
# still in the finite state machine.
# 
# OK, enough talking. Here's the first of the game data,
# the guts of the finite state machine transition map.

transition_dict = {
#    prev_state  action  next_state   output_dialog
#    ----------  ------  ----------   ----------------------------
    ('entrance', 'e') : ('parlor',   'You walk into the parlor.'),
    ('entrance', 'l') : ('entrance', 'You see muddy boots in the entrance.'),
    ('entrance', '*') : ('entrance', 'You bump into an entrance wall.'),

    ('parlor',   'n') : ('kitchen',  'You walk into the kitchen.'),
    ('parlor',   's') : ('bedroom',  'You stroll into the bedroom.'),
    ('parlor',   'l') : ('parlor',   'There is a comfy chair in the parlor.'),
    ('parlor',   '*') : ('parlor',   'You bump into a parlor wall.'),

    ('kitchen',  's') : ('parlor',   'You saunter into the parlor.'),
    ('kitchen',  'l') : ('kitchen',  'The kitchen has many dirty dishes.'),
    ('kitchen',  '*') : ('kitchen',  'You bump into a kitchen wall.'),

    ('bedroom',  'n') : ('parlor',   'You jump into the parlor.'),
    ('bedroom',  'l') : ('bedroom',  'The bedroom ... hmmm.'),    
    ('bedroom',  '*') : ('bedroom',  'You trip over the bedpost.'),
}

# And here's the rest of the game data.  
#
# Note clearly that I have *not* embedded this stuff into the game
# logic.  By putting it here, outside the functions, it's all in one
# place, can be adapted for other games, and the variable names
# document the purpose of the various strings and characters.
#
# It's usually good practice to separate the data from the program logic.

default_action = '*'

error_state = '?'
error_dialog = 'An unexpected earthquake has killed you.'

magic_exit_states = ['bedroom']
magic_odds = 10   # i.e. 1 chance in 10
magic_dialog = 'You find a portal under the bed \n' + \
               'that drops you into a fantasy realm \n' + \
               'where you live happily ever after.'

prompt = 'Choose n,s,e,w,look,quit : '
quit_dialog = 'Quitting? So soon? Fine - be that way.'
quit_action = 'q'

start_state  = 'entrance'
start_dialog = '\nYou are standing in the entrance.'
end_dialog = 'THE END'

# With the game transition data defined, next we implement the mapping
# from previous to new states given the user chosen action, extending
# the transition_dict to cover all possible (state,action) pairs.
def transition(state, action):
    """ Return (new_state, "dialog output") """
    for key in [(state, action), (state, default_action)]:
        if transition_dict.has_key(key):
            return transition_dict[key]
    return (error_state, error_dialog)

# All that's left is to define up the loop that changes the state, and
# then to inoke it. A few utility functions help keep things tight.

def interactive_loop(beginning_state, ending_state):
    """ Get user actions, change state, and print dialog text. """
    
    state = beginning_state
    while state != ending_state:

        # Get the user's action choice.
        action = input2action(raw_input('\n' + prompt))

        # End the game quickly if the user has had enough.
        if action == quit_action:
            print quit_dialog
            return

        # The heart of the finite state algorithm :
        (state, dialog) = transition(state, action)
        print dialog

        # And just for fun, a random 'magic' game exit
        if state in magic_exit_states and one_chance_in_(magic_odds):
            print magic_dialog
            return

def input2action(user_input):
    """ Convert the user_input string to a possible game action """
    # In a larger game this might take some sophisticated parsing;
    # here we're just looking at the first letter typed, lowercase.
    return user_input[0].lower()

def one_chance_in_(x):
    """ Return True with odds 1 out of x """
    return randint(1, x) == 1

def main():
    print start_dialog
    interactive_loop(start_state, error_state)
    print end_dialog

if __name__ == '__main__':
    main()

syntax highlighted by Code2HTML, v. 0.93pm6