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()
2 Answers 2
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!
-
\$\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– DKozzy2024年02月21日 02:32:16 +00:00Commented Feb 21, 2024 at 2:32
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?
-
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– DKozzy2024年02月21日 02:28:41 +00:00Commented Feb 21, 2024 at 2:28