So what I want to do is kind of hard to describe in the title.
Here's what I want to do: In the code below, I want to have some general methods someone can call on the Room class (ex. Search, Loot) and the Player class (ex. quit, heal). The way I want that to happen is the player enters what they want to do in an input, and python will look up that choice in a dict which matches the choice to a method.
I've already successfully done that with the exits on the rooms. I can probably do it by making a child class and listing the methods there, but I really don't want to do that as that would seem cluttery.
When I run the code below, it just auto exits. If I run it with that first dictionary commented out, I get an error saying that __init__() is missing a required positional argument.
from textwrap import dedent
from sys import exit
class Player(object):
actions = {
'QUIT': quit
}
def __init__(self, actions):
self.actions = actions
# Want actions to be a list of actions like in the Room Class
#
def quit(self):
# Quits the game
exit(0)
class Room(object):
# Description is just a basic room description. No items needed to be added here.
def __init__(self, desc, exits, exitdesc):
self.desc = desc
self.exits = exits
self.exitdesc = exitdesc
# Also want list of general actions for a room here.
def enterroom(self):
#First print the description of the room
print(self.desc)
#Then print the list of exits.
if len(self.exits) > 1:
print(f"You see the following exits:")
for exd in self.exitdesc:
print(self.exitdesc[exd])
elif len(self.exits) == 1:
print(f"There is one exit:")
for exd in self.exitdesc:
print(self.exitdesc[exd])
else:
print("There are no exits.")
# Then allow the player to make a choice.
self.roomactivity()
# Here's what I mean about calling the methods via a dictionary
def roomactivity(self):
while True:
print("What do you want to do?")
choice = input("> ").upper()
if choice in self.exits:
self.exits[choice].enterroom()
#And here's where I want to call actions other than directions.
elif choice in player.actions:
player.actions[choice]
else:
print("I don't understand.")
class VoidRoom(Room):
def __init__(self):
super().__init__(
desc = "ONLY VOID.",
exits = {},
exitdesc = {})
class TestRoom(Room):
def __init__(self):
super().__init__(
desc = dedent("""
This room is only a test room.
It has pure white walls and a pure white floor.
Nothing is in it and you can hear faint echoes
of some mad sounds."""),
exitdesc = {
'NORTH': 'To the NORTH is a black door.',
'SOUTH': 'To the SOUTH is a high window.',
'EAST': 'To the EAST is a red door.',
'WEST': 'To the WEST is a blue door.'},
exits = {
'NORTH': void_room,
'SOUTH': void_room,
'EAST': void_room,
'WEST': void_room})
void_room = VoidRoom()
test_room = TestRoom()
player = Player()
test_room.enterroom()
I hope I've explained the issue clearly. Still learning this language and I may have bitten off more than I can chew at the moment.
EDIT: New Code Below:
I've changed some things around, I have the player commands and stuff in a separate py file so I can expand the player scope without cluttering up the rooms.py file.
from textwrap import dedent
from sys import exit
from player import *
from enemies import *
# This is the base class for a room.
class Room(object):
# Description is just a basic room description. No items needed to be added here.
def __init__(self, desc, exits, exitdesc, inventory):
self.desc = desc
self.exits = exits
self.exitdesc = exitdesc
self.inventory = inventory
def enterroom(self):
#First print the description of the room
Player.currentroom = self
print(self.desc)
for item in self.inventory:
print(self.inventory[item].lootdesc)
#Then print the list of exits.
if len(self.exits) > 1:
print(f"You see the following exits:")
for exd in self.exitdesc:
print(exd)
elif len(self.exits) == 1:
print(f"There is one exit:")
for exd in self.exitdesc:
print(exd)
else:
print("There are no exits.")
# Then allow the player to make a choice.
self.roomactivity()
def roomactivity(self):
while True:
print("What do you want to do?")
choice = input("> ").upper()
if choice in self.exits:
self.exits[choice]().enterroom()
elif choice in Player.actions:
Player.actions[choice]()
else:
print("I don't understand.")
#Player.actions[choice]()
class Room3(Room):
def __init__(self):
super().__init__(
desc = dedent("""
You are in a large, dimly lit room.
Torches sit in empty alcoves, giving off an eerie red glow.
You hear scratching and squeaking from behind the walls."""),
exits = {
'NORTHEAST': StartRoom
},
exitdesc = [
'A sturdy looking door leads to the NORTHEAST'
],
inventory = {})
class Room1(Room):
def __init__(self):
super().__init__(
desc = dedent("""
You are in a medium sized, dimly lit room.
Busts of dead men you don't know sit atop web-strewn pedestals."""),
exits = {
'EAST': StartRoom
},
exitdesc = [
'An arch leading into a dimly lit hall lies to the EAST.'
],
inventory = {'IRON SWORD': iron_sword}
)
class StartRoom(Room):
def __init__(self):
super().__init__(
desc = dedent("""
PLACEHOLDER LINE 49"""),
exits = {
'SOUTHWEST': Room3,
'WEST': Room1
},
exitdesc = [
'An arch leading into a dimly lit room lies to the WEST',
'A sturdy looking door lies to the SOUTHWEST'],
inventory = {}
)
class HelpPage(Room):
def __init__(self):
super().__init__(
desc = dedent("""
All actions will be listed in all caps
When asked for input you may:
QUIT the game
Check your INVENTORY
Check your player STATUS
SEARCH the room
EXAMINE an object or point of interest
USE an item from your inventory or the room
ATTACK a creature
GET an item from the room
or pick a direction (listed in caps)"""),
exits = {},
exitdesc = [
'Press ENTER to return to the Main Menu'],
inventory = []
)
def enterroom(self):
print(self.desc)
for exd in self.exitdesc:
print(exd)
self.roomactivity()
def roomactivity(self):
input()
MainMenu.enterroom()
help_page = HelpPage()
# Main menu, lil bit different from a regular room
class MainMenu(Room):
def __init__(self):
super().__init__(
desc = dedent("""
THE DARK DUNGEON OF THE VAMPIRE KNIGHT
A game by crashonthebeat"""),
exits = {
'START': StartRoom,
'HELP': HelpPage
},
exitdesc = [
'Press START to Start the Game',
'Or go to the HELP Menu'],
inventory = []
)
def enterroom(self):
print(self.desc)
for exd in self.exitdesc:
print(exd)
self.roomactivity()
def roomactivity(self):
while True:
choice = input("Choose an Option: ")
if choice in self.exits:
self.exits[choice]().enterroom()
else:
print("I don't understand")
And the relevant code from player.py
from items import *
from rooms import *
class Player(object):
@property
def actions(self):
actions_map = {
'QUIT': 'quit_',
'STATUS': 'status',
'INVENTORY': 'printinventory',
'EXAMINE': 'examine',
'USE': 'useitem',
'SEARCH': 'searchroom',
'GET': 'getitem',
'CURRENTROOM': 'getcurrentroom'
}
return actions_map
I see a few potential problems:
Where does quit
come from in Player's actions
dictionary? It is presented as a known name of some kind (variable/method/object) but the only time you define quit is as a method of Player, so the class attribute actions
cannot access it.
quit is never actually called. When player.actions[choice]
is executed on user input "QUIT" for example, even if quit
did exist, that just returns whatever function it points to. It does not call that function. This is bad. player.actions[choice]()
would get you there.
Defining a variable in your script and referencing the script variable in your class is a no-no. It's OK to have your class method call VoidRoom() or TestRoom(), but having it reference variables test_room
and void_room
from a completely different namespace, not so much.
See examples below:
actions = {
'QUIT': quit
}
This will not quit your program. "quit" is also a reserved word in the python IDLE, so not the best choice for a method. Python convention is to put a '_' at the end to avoid clashing with reserved words: quit_
. I would remove that attribute entirely and make it a property, so you can just override it in its children and add extra functionality. You sacrifice the ability to initialize players with custom actions, but wouldn't those make more sense as classes with associated actions anyway?
class Player(object):
@property
def actions(self):
actions_map = {
'QUIT': self.quit_
}
return actions_map
def quit_(self):
print("Quitting the game.")
exit(0)
class PlayerThatCanSing(Player):
@property
def actions(self):
default_actions = super().actions # We still want Player actions
new_actions = {
'SING': self.sing
}
combined_actions = new_actions.update(default_actions) # Now player can quit AND sing
return combined_actions
def sing(self):
print("Do Re Ma Fa So La Te Do")
Now referencing player.actions['QUIT']()
calls player.quit_, which is what you want.
Regarding #3:
class TestRoom(Room):
def __init__(self):
super().__init__(
desc = dedent("""
This room is only a test room.
It has pure white walls and a pure white floor.
Nothing is in it and you can hear faint echoes
of some mad sounds."""),
exitdesc = {
'NORTH': 'To the NORTH is a black door.',
'SOUTH': 'To the SOUTH is a high window.',
'EAST': 'To the EAST is a red door.',
'WEST': 'To the WEST is a blue door.'},
exits = {
'NORTH': void_room,
'SOUTH': void_room,
'EAST': void_room,
'WEST': void_room})
void_room = VoidRoom()
test_room = TestRoom()
player = Player()
You are declaring void_room
and test_room
at script run time, which is fine. The only problem is your classes do not know anything about your runtime variables, so if you want North, South, East, and West to map to an instance of VoidRoom (which is a class that TestRoom knows about, because it's sitting right there above it in the module), just reference VoidRoom() directly, not void_room. Never assume your classes know anything about anything that happens outside that class and hasn't been passed into the __init__
for the class.
I hope that Player example with the actions property (just think of property in this case as a way to reference a function as a variable - since actions returns a dict
, we can just treat it like a dict
without calling the method with actions(). player.actions
will return the dict, nice and readable) makes sense, because if you implement it that way, you can have specific types of bards that inherit many layers down, and overriding actions with the call to super().actions
(their parent class) means even the most specific DwarfBlacksmithWhoSingsInHisSpareTime class gets all of the parent actions all the way up (because each actions method calls its parent, and on and on until it hits Player), so you get a Dwarf guy that can quit, sing, and blacksmith. Pretty elegant, and I hope it's not too confusing, because it's a very cool concept. Best of luck!