0

I am completing a text based game for an intro to python class. It is not complete but I was working on the main_menu function and the functions called within the main_menu function when I ran into this error message. I have encountered this error several times in my learning experience and it was usually attributed to a basic mistake when assigning variables, but this one has me stumped... This is the script in question (lines in traceback commented in BOLD):

import random
from sys import exit


# Item variables
coal = ('Coal', 'some coal which can be used in a furnace.')
eng_key = ('Key', 'a key to a house.')
bomb = ('Bomb', 'a combustible device which creates a powerfull explosion. Possibly used for demolition...')
tonic = ('Tonic', 'a weak healing tonic. Adds \'5\' health points.')
sTonic = ('Super Tonic', 'a strong healing tonic. Adds \'10\' health points.')


# LOCATIONS
# Below are the possible locations you can 'travel' to, along with a title (first item in tuple), a description, any items that might be found in the location which can be discovered and entered into 'inventory' through 'search' command
# location variable = (title, description, item for discovery)
sub_base = ('Sub-Base', 'This is DeepBlue\'s base of operations in the Atlantis excavation zone. Your submarine is docked ahead', 'nothing useful here.')
cave = ('Underwater Cave', 'You have entered the mouth of an underwater cave with your sub.', 'nothing useful here.')
cemetery = ('Cemetery Chamber', 'You are in a large chamber within the cave. This seems to be a cemetery. There are symmetrically lined mounds of dirt, with obelisks at the head.', 'nothing useful here.')
city_gate = ('City Gate', 'You stand at a crossroad of sorts, at the bottom of an upward sloping ramp.', 'nothing useful here.')
city_outskirts = ('City Outskirts', 'You find yourself just outside the city walls.', 'nothing useful here.')
castle_outskirts = ('Rear of Castle Ruins', 'You are standing at the rear of the castle ruins. There is a layer of rubble blocking the way, but you can clearly see a passage leading inside. Perhaps you can devise a way to move it...', 'nothing useful here.')
castle_inside = ('Inside the Grand Castle of Atlantis', 'You have made it inside of the castle. All the advanced knowledge of the Atlanteans is at your disposal.', 'nothing useful here.')
city_block0 = ('Beginning of Main Avenue in City', 'You are standing at the beginning of the main avenue of the city.', 'nothing useful here.')
ruins1 = ('Rubble of Dilapidated House', 'You are standing in the middle of the ruins of what seems to have been a house.', tonic)
mystic_house = ('Mystic House', 'You are standing inside the city Mystic\'s house.', sTonic)
city_block1 = ('Second Block in City', 'You have moved to the second block of the city\'s main avenue.', 'nothing useful here.')
abandoned_house = ('Abandoned House', 'You are standing in the middle of an abandoned house.', eng_key)
blacksmith_house = ('Blacksmith\'s House', 'You are standing in what seems to be a blacksmith\'s building. There is a furnace, iron ore, smith\'s tools and various components for making swords. No coal though...', 'nothing useful here. But with the right items, something can be created here...')
city_block2 = ('Third Block in City', 'You have moved to the third block of the city\'s main avenue.', 'nothing useful here.')
marketplace = ('Abandoned Marketplace', 'You are standing in the middle of an abandoned marketplace. There might be some useful items laying around...', coal)
engineer_house = ('Engineer\'s House', 'You are standing in the engineer\'s house. There might be some useful items lying around...', bomb)
castle_main = ('Castle Main Entrance', 'You are standing in front of the main entrance of a huge castle. The grand entrance is blocked off by massive amounts of rubble. There must be another way in...', 'nothing useful here.')


# ITEMS
# below are the items which may be added to the inventory
items = {
    coal: (engineer_house,), 
    eng_key: (engineer_house,), 
    bomb: (castle_inside,),
    tonic: ('anywhere',),
    sTonic: ('anywhere',)
}


# INTERACTIONS(location-based)
# below is a dictionary of events. Each location has certain events which can only take place there.
# interactions dictionary = {location: (use+item response, search response)}
lEvents = {sub_base: (cave,),
    cave: (sub_base, cemetery, city_gate),
    cemetery: (cave, city_outskirts),
    city_gate: (cave, city_outskirts, city_block0),
    city_outskirts: (cemetery, castle_outskirts, city_gate),
    castle_outskirts: (city_outskirts,castle_inside),
    castle_inside: (castle_outskirts,),
    city_block0: (city_gate, ruins1, mystic_house, city_block1),
    ruins1: (city_block0,),
    mystic_house: (city_block0,),
    city_block1: (city_block0, abandoned_house, blacksmith_house, city_block2),
    abandoned_house: (city_block1,),
    blacksmith_house: (city_block1,),
    city_block2: (city_block1, marketplace, engineer_house, castle_main),
    marketplace: (city_block2,),
    engineer_house: (city_block2,),
    castle_main: (city_block2,)
}


# TRAVEL OPTIONS
# Below is a dictionary outlining the possible places to travel to depending on where you are currently located, this peice is essential to the travel function
travelOpt = {
    sub_base: (cave,),
    cave: (sub_base, cemetery, city_gate),
    cemetery: (cave, city_outskirts),
    city_gate: (cave, city_outskirts, city_block0),
    city_outskirts: (cemetery, castle_outskirts, city_gate),
    castle_outskirts: (city_outskirts,castle_inside),
    castle_inside: (castle_outskirts,),
    city_block0: (city_gate, ruins1, mystic_house, city_block1),
    ruins1: (city_block0,),
    mystic_house: (city_block0,),
    city_block1: (city_block0, abandoned_house, blacksmith_house, city_block2),
    abandoned_house: (city_block1,),
    blacksmith_house: (city_block1,),
    city_block2: (city_block1, marketplace, engineer_house, castle_main),
    marketplace: (city_block2,),
    engineer_house: (city_block2,),
    castle_main: (city_block2,)
}


def eHouseAccess(action, location, eHouse):
    if eHouse == 'locked':
        print "The door is locked! You need to find a key for this door."
        travel(location)
    else:
        location = travelOpt[location][action - 1]
        travel(location)


def cInsideAccess(action, location, cInside):
    if cInside == 'blocked':
        print "The path is blocked by rubble! You need to find a way to clear the rubble."
        travel(location)
    else:
        location = travelOpt[location][action - 1]
        travel(location)


def travel(location):
    while True:
        print "You are in the", location[0]+"." 
        print location[1]
        print 'You can travel to:'

        for (i, t) in enumerate(travelOpt[location]):
            print i + 1, t[0]

        action = raw_input("Pick a destination, or enter 'menu' for the main menu: ")
        if action == 'menu':
            main_menu(location, inventory, items)
        else:
            action = int(action)      
        if travelOpt[location][action - 1] == engineer_house:
            eHouseAccess(action, location, eHouse)
        elif travelOpt[location][action - 1] == castle_inside:
            cInsideAccess(action, location, cInside)
        else:
            location = travelOpt[location][action - 1]


def main_menu(location, inventory, items):
    travel = travel(location) # **this is line 133**
    invtry = showInv(inventory)
    use = use(items, inventory)
    quit = exit(0)
    while True:
        print "You are in the", location[0]
        menu_list = [('Travel', travel), ('Inventory', invtry), ('Use', use), ('Search', search), ('Map', map), ('Quit', quit)]
        print "Choose one:"
        for (num, t) in enumerate(menu_list):
            print num + 1, t[0]
        main_choice = int(raw_input("> "))
        action = menu_list[main_choice - 1]
        action[1]


def search(location):
    pass


def map(location):
    pass


def showInv(inventory):
    if inventory == []:
        print "Your inventory is empty"
        inv = 'empty'
        return inv
    else:
        for (num, i) in enumerate(inventory):
            print num + 1, i
        inv = inventory
        return inv

def use(items, inventory):
    a = showInv(inventory)
    if a == 'empty':
        print "There is nothing to use."
    else:
        showInv(inventory)
        uItem = int(raw_input("Choose an item to use: "))


location = sub_base
inventory = []
eHouse = 'locked'
cInside = 'blocked'
hp = 20

map = """
Key:
    * = indicates a point of entry
            ______ ______
           |Castle|Castle|
           |Outsk-|      |
           |irts         |
        ___|**____|__**__|
       | City |   |      |
       |Outsk-|   | City |
       | irts |   |      |
  _____|*____*|___|*_____|
 |       |  |       |
 | Grave |  | City  |
 | Yard  |  | Gates |
 |_____**|__|*______|
      |       |
      | Cave  |
      |       |
      |__**___|
      |       |
      | Sub-  |
      | Base  |
      |_______|
"""


cityMap = """
Key:
    * = indicates a point of entry
            ________
           |        |
           | Castle |
           |        |
     ______|___**___|________
    |      |  City  | Engin- |
    |Market|  Block | eer    |
    |Place *    3   * House  |
    |______|___  ___|________|
    |Aband-|  City  | Black- |
    | oned |  Block | smith  |
    |House *    2   * House  |
    |______|___**___|________|
    |      |  City  |        |
    |Rubble|  Block |Mystic's|
    |      *    1   * House  |
    |______|________|________|
"""


name = raw_input("Enter your name: ")
print "Welcome to the Atlantis Excavation Zone, %s." % name
print "Your first day on the job and we already have a new cave for you to map... LUCKY!"
print "The DeepBlue team takes you down to the sub base. Time to get to work."
main_menu(location, inventory, items) # **this is line 236**

And here is the traceback:

Traceback (most recent call last):
File "ex36_2.py", line 236, in <module>
main_menu(location, inventory, items)
File "ex36_2.py", line 133, in main_menu
travel = travel(location)
UnboundLocalError: local variable 'travel' referenced before assignment

I thought the variable had been assigned in line 133. What am I missing here?

Verbal_Kint
  • 1,366
  • 3
  • 19
  • 35

1 Answers1

10

The first line

travel = travel(location)

implicitly marks the name travel as local for the whole function. All look-ups for this name look for a local name, including the one on the right-hand side of the cited line. At that time, there is no value assigned to the local name, though, hence the error. There might be a global name travel, but since the compiler identified travel as a local name, it will only look in the local namespace. Use a different name for the local variable.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • I just edited this post so it was easier to read before I read your post. When you mention "on the right-hand side of the cited line", I assume you mean travel(location). If that is in fact what you are referencing then you should know that travel(location) is a function previously defined in the script. Should I post the entire script? – Verbal_Kint Nov 03 '11 at 18:02
  • @Verbal_Kint: That's what I'm referring to, and I also inferred that `travel()` is a function previously defined. And the solution to your problem still is to rename the local variable `travel` to something else. Try it! (And have a look at this [minimal example](http://ideone.com/PTpq2).) – Sven Marnach Nov 03 '11 at 18:08
  • @Verbal_Kint the fact that `travel(location)` is already defined **doesn't matter** here. In Python, once you have written `travel =`, `travel` is now a local variable that **hides** the function, and which will be defined by the part after the = sign. The part after the = sign is `travel(location)`. When Python tries to make sense of that, it will interpret the `travel` part as referring to the variable you just mentioned, and **not** the function. Thus, the variable is being defined in terms of itself, which is a logical impossibility. – Karl Knechtel Nov 03 '11 at 18:18
  • You can avoid that error by explicitly telling Python that `travel` is a global variable with `global travel`, but now you have a worse problem: the function will be called, and then you will **overwrite the global variable** `travel` with the result of the function call. Now any attempt to call the function again will attempt to use that result as a function. In fact, *the way you have written the function, it is illogical to assign the result to a variable at all*, because you don't explicitly return a value anyway. – Karl Knechtel Nov 03 '11 at 18:20
  • @SvenMarnach That did work... If I understand you correctly, the variable is assigned to a function of the same name and is therefore assigned to itself? I thought that the () associated with the function would differentiate it... Thanks for the help, I really appreciate it. – Verbal_Kint Nov 03 '11 at 18:48
  • That's the kind of thing that makes me hate python, I love it but why do this? If it's the compiler's job to check this, the program won't be running faster if python doesn't look for global functions or check that `travel` and `travel()` are not the same string. I'm a noob so there's probably something I don't get, please tell me what. – sinekonata Dec 09 '12 at 01:14
  • @sinekonata: Without statically determining variable scope, Python would be significantly slower. Moreover, changing the scope of a name from global to local within one statement is bad practice anyway, so the tradeoff is between better performance and allowing a questionable programming style. Understandably, Guido chose the former. – Sven Marnach Dec 12 '12 at 00:16