9
\$\begingroup\$

So this is my first time taking a coding class. It's my second project for my IT140 class and I am making a text based game. This is my code so far. I do have a warning on Pycharm for

Shadows name current_room from outer scope on [lines 23, 76, 84]

but I haven't seen it create any issue so far when running the game. Thanks for any tips or suggestions!

# DKozzy

# Define the room dictionary and other global variables
rooms = {
    'Entrance to the Crypt': {'North': 'Research Room'},
    'Research Room': {'North': 'Chamber of Shadows', 'East': 'Bed Chambers', 'West': 'Treasury', 'South':
                      'Entrance to the Crypt', 'Item': 'Tome of Dark Incantations'},
    'Treasury': {'East': 'Research Room', 'Item': 'Amulet of Undead Warding'},
    'Bed Chambers': {'West': 'Research Room', 'Item': 'Phylactery Orb'},
    'Chamber of Shadows': {'East': 'Armory', 'North': 'Throne Room', 'South': 'Research Room',
                           'Item': 'Orb of Shadow Veil'},
    'Armory': {'West': 'Chamber of Shadows', 'Item': 'Soul Reaper Scythe'},
    'Throne Room': {'South': 'Chamber of Shadows', 'West': 'Ritual Sanctum', 'Item': 'Cursed Crown of Dominion'},
    'Ritual Sanctum': {'East': 'Throne Room'}
}

current_room = 'Entrance to the Crypt'
player_inventory = []
Lich_King_room = 'Ritual Sanctum'


# Define the get_new_state function
def get_new_state(direction_from_user, current_room):
    global player_inventory
    global rooms

    if direction_from_user in rooms[current_room]:
        new_room = rooms[current_room][direction_from_user]
        print("You moved to", new_room)

        # Check if the new room has an item
        if 'Item' in rooms[new_room]:
            item = rooms[new_room]['Item']
            print("You found an item:", item)

        return new_room
    else:
        print("You can't go that way.")
        return current_room


# Function to display game instructions
def show_instructions():
    instructions = (
        "The Lich King's Terror!\n"
        "Collect 6 items to win the game.\n"
        "Move commands: go North, go South, go East, go West\n"
        "Get item command: get [item name]\n"
        "Check status command: status"
    )
    print(instructions)


# Function to display player's status
def show_status():
    global current_room
    print("Current Room:", current_room)

    if 'Item' in rooms[current_room]:
        print("Item in Room:", rooms[current_room]['Item'])
    else:
        print("No item in this room.")

    print("Inventory:", player_inventory)

    print("Available Directions:")
    available_directions = rooms[current_room].keys()
    available_directions = [direction for direction in available_directions if direction != 'Item']
    print(", ".join(available_directions))


# Main function containing the gameplay loop
def main():
    show_instructions()

    current_room = 'Entrance to the Crypt'  # Initialize current room here
    while True:
        command = input('Enter a command: ').lower()
        action, *params = command.split()

        if action == 'go':
            direction = params[0].capitalize()
            if direction in rooms[current_room]:
                current_room = get_new_state(direction, current_room)  # Pass current_room as parameter
            else:
                print('Invalid direction!')
        elif action == 'get':
            if len(params) == 0:
                print('Please specify the item name.')
            else:
                item_name = ' '.join(params).lower()  # Input item name to lowercase
                # Check if the item exists in the current room
                if 'Item' in rooms[current_room]:
                    room_item = rooms[current_room]['Item']
                    if item_name.lower() == room_item.lower():
                        player_inventory.append(room_item)
                        del rooms[current_room]['Item']
                        print(f'You obtained the {room_item} from this room.')
                    else:
                        print('Item not found in this room.')
                else:
                    print('No item in this room.')
        elif action == 'status':
            show_status()  # Uses the show_status function here
        elif action == 'quit':
            break
        else:
            print('Uh... What kind of command was that?')

        # Check win condition
        if len(player_inventory) == 6 and current_room == Lich_King_room:
            print('Congratulations!\n'
                  'You have collected all items and defeated the Lich King.\n'
                  'You have saved the world!\n'
                  'You win!')
            break

        # Check lose condition
        if current_room == Lich_King_room and len(player_inventory) < 6:
            print("You encounter the Lich King, but you don't have all the required items to stop his treachery.\n"
                  'You lose!')
            break


if __name__ == "__main__":
    main()
\$\endgroup\$

2 Answers 2

6
\$\begingroup\$

Wow, this is pretty good!

shadowing

a warning on Pycharm for "Shadows name 'current_room' from outer scope" ... but I haven't seen it create any issue so far when running the game.

You actually do want to tidy up that detail. There's no point in writing code which will confuse the author.

First, let's touch on a vocabulary word: "to shadow". Here's an example, a terrible horrible example:

x = 1
print = 7
print(x)

What happens here? That's right, it blows up with TypeError. There used to be a python object that was a very nice function which will print out its argument. Actually it's still there. You just can't see it, because it is shadowed by an int with the same name. We can evaluate print + 1 and get 8. But we can no longer call the print() function, not easily.

Don't do that.

Avoid shadowing names. It leads to confusion, incorrect results, or both.

current_room = 'Entrance to the Crypt'
...
def show_status():
    global current_room
...
def main():
    show_instructions()

    current_room = 'Entrance to the Crypt'  # Initialize current room here

The name of your module is __main__, and you created a current_room string at module scope. Which show_status() accesses via global (not that it needed that). Over in main() you create a completely different str object having the same name. Don't do that. And then you sometimes pass it in as a parameter, for example in the signature of get_new_state().

Pick a place for that variable to live, either as a module scope global or as a main() local that gets passed around, and consistently stick with that decision.

In descending order of preference, I would put it

  • in an object attribute after defining a class: self.current_room
  • a main() local variable that gets passed around via function signatures
  • a module scope global

Any one of those would work.

A function that just reads a global typically doesn't need that global keyword, since cPython will search the function namespace and, upon no match, will then search the enclosing module namespace and locate your variable.

A function that writes to a global definitely needs a global keyword. Otherwise we'll create a function local variable of the same name, shadowing the global and failing to update it. My preference is to avoid use of the global keyword wherever feasible, to reduce coupling and to encourage good designs.

comments

We use code to reveal how to compute a result, and we use a # comment to reveal why we chose to compute it that way.

# Define the get_new_state function
def get_new_state( ...

Here, the code has already spoken eloquently, and the # comment adds nothing. Elide it.

docstrings

# Main function containing the gameplay loop
def main():

This is a helpful comment, explaining that the function's single responsibility is to run the main gameplay loop, something that isn't self-evident just from the identifier.

Python has a special way for author to express such things. It's called a """docstring""". Let's turn that comment into a docstring.

def main():
    """Main is responsible for running the gameplay loop."""

Keep writing code, keep paying attention to code quality, and you'll be writing giant python programs in no time!

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your detailed explanation. I was murky on the subject matter, and have since rectified the issue you've sighted. \$\endgroup\$
    – DKozzy
    Feb 21 at 2:32
1
\$\begingroup\$

For the most part, the code layout is good, and you chose meaningful names for variables and functions. When I run it, the instructions are easy to follow.

The layout of the rooms assignment is a bit ad-hoc. I think it is easier to read and maintain if you put each direction and item on a separate line, something like:

rooms = {
    'Entrance to the Crypt': {'North': 'Research Room'},
    'Research Room': {  'North': 'Chamber of Shadows', 
                        'East' : 'Bed Chambers',
                        'West' : 'Treasury', 
                        'South': 'Entrance to the Crypt',
                        'Item' : 'Tome of Dark Incantations'},

When I run pylint, it reports potential issues like the global variables and missing comments that the other answer addresses. I recommend you run it yourself.

When I run the code, I think I might see a bug. Consider the following transcript:

Enter a command: status
Current Room: Entrance to the Crypt
No item in this room.
Inventory: ['Tome of Dark Incantations', 'Orb of Shadow Veil', 'Cursed Crown of Dominion']
Available Directions:
North
Enter a command: go north
Invalid direction!
Enter a command: 

The status command seems to tell me the only available direction is north, but when I then enter the command go north, it tells me it is an invalid direction. Is this a bug?

If I look earlier in the transcript, I also noticed this inconsistency with the room I am in:

You moved to Research Room
You found an item: Tome of Dark Incantations
Enter a command: get tome of dark incantations
You obtained the Tome of Dark Incantations from this room.
Enter a command: status
Current Room: Entrance to the Crypt

First it tells me I'm in the Research Room, but the subsequent status contradicts that and says I'm in the Entrance to the Crypt. Am I interpreting this the right way?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thank you for your insight, I've since rectified that error. I also took your advice on the rooms assignment and it is much simpler to read. Again thank you for your input \$\endgroup\$
    – DKozzy
    Feb 21 at 2:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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