After dealing with the open/closed/locked situation in containers in my last post, I think it is time to take a look at something that is fairly similar in its mechanics… doors.
I already created Exits a little while ago that I use to connect rooms, but any adventure game worth its salt also has doors, and like containers, these doors have different states.
My first step is, therefore, to define the necessary states, just like I did for the containers.
class DoorStates ( Enum ): Open = auto() Closed = auto() Locked = auto()
Since doors are a special kind of exit, the next step is to set up a class that derives from my Exit class.
class Door ( Exit ): """ Define a door as a special kind of exit """ def __init__ ( self, _direction, _target, _initState=globals.DoorStates.Closed, _key=None ): super ().__init__ ( _direction, _target, _initState, _key ) def TryUnlock ( self ): if globals.DoorStates.Locked == self.State: if globals.theInventory.Has ( self.Key ): print ( "(After unlocking it first.)" ) self.SetState ( globals.DoorStates.Closed ) def TryOpen ( self ): if globals.DoorStates.Closed == self.State: print ( "(After opening it first.)" ) self.SetState ( globals.DoorStates.Open )
Nothing spectacular here at all, and as you can see, I’ve already brought across the
TryOpen() methods that are virtually identical from the containers, because like in those cases, I think it is an important game feature that the game is smart enough to open a door automatically, if the player tries to walk through it while it’s closed or if he tries to open it while it’s still locked.
There is a call to a
SetState() function that needs a bit of explanation, I think.
def SetState ( self, _state ): self.State = _state _adjacent = rooms.theRooms [ self.Target ].HasExit ( self.InvertDirection ( self.Direction ) ) # get the other side _adjacent.State = _state
When you have a door in the game, the door actually appears in two rooms, technically. If in the room you’re currently in there is a door leading West, for example, it means there also has to be a door in the room it leads to, leading in the opposite direction. If I open the door in my current room, by definition, the door in the other room will also have to be opened.
That’s what this function does. It looks where the door leads to and then grabs the definition for that adjacent room. There, it searches the exits for one that leads in the opposite direction and flags it as Open as well. I hope this made sense…
The function to invert the direction, is a rather crude little bit of code. It may not be elegant, but it gets the job done, and since it’s in a method of its own, I can go back at any time and expand it to allow for more directions.
def InvertDirection ( _direction ): if Tokens.North == _direction: return Tokens.South if Tokens.South == _direction: return Tokens.North if Tokens.West == _direction: return Tokens.East if Tokens.East == _direction: return Tokens.West
With all that in place, it is now time to implement the actual actions.
def Open ( self ): self.TryUnlock () if globals.DoorStates.Open == self.State: print ( "The door is already open." ) elif globals.DoorStates.Locked == self.State: print ( "The door is locked." ) else: print ( "You open the door." ) self.SetState ( globals.DoorStates.Open ) return True def Close ( self ): if globals.DoorStates.Closed == self.State: print ( "The door is already closed." ) else: print ( "You shut the door." ) self.SetState ( globals.DoorStates.Closed ) return True def Lock ( self ): if self.Key: if globals.DoorStates.Locked == self.State: print ( "The door is already locked." ) else: if globals.theInventory.Has ( self.Key ): print ( "You lock the door." ) self.SetState ( globals.DoorStates.Locked ) else: print ( "You do not have a fitting key." ) else: print ( "The door has no lock." ) return True def Unlock ( self ): if self.Key: if globals.DoorStates.Open == self.State: print ( "The door is already unlocked and standing wide open." ) elif globals.DoorStates.Locked != self.State: print ( "The door is already unlocked." ) else: if globals.theInventory.Has ( self.Key ): print ( "With a soft click, you hear the door unlock." ) self.SetState ( globals.DoorStates.Closed ) else: print ( "You do not have a fitting key." ) else: print ( "The door has no lock." ) return True
As you can see, the logic of these actions is essentially identical to the one I used in my Container class. After all, the behavior of a lid compared to a door is not all that different.
Catching the respective actions in my Evaluate() function allowed me to make the necessary calls and before I knew it, I had doors I could open, lock and close. Neat.
One key ingredient is still missing at this point, however. Since we are talking about a type of exit, I want to make sure that player can actually walk through doors and, as I mentioned before, I want the game to automatically open doors if necessary. In order to make that happen, I had to modify the
Move() function that is part of my Room class.
def Move ( self, _dir ): """ Check if there is an exit leading a direction; If there is, change to the new room; Print the new room's description """ _exit = globals.theRoom.HasExit ( _dir ) _adjacent = rooms.theRooms [ _exit.Target ].HasExit ( _exit.InvertDirection ( _exit.Direction ) ) # get the other side if _exit: _exit.TryUnlock () _exit.TryOpen () if globals.DoorStates.Locked == _exit.State: print ( "You can't go that way. That door is locked." ) return True globals.theRoom = rooms.theRooms [ _exit.Target ] globals.theRoomID = _exit.Target globals.theRoom.Describe () return True return False
And just like that, the player can move through doors in the game and he can manipulate doors as well, which adds further depth to the game. Throw in a couple of additional responses when the player tries to break down a door, or when he tries to put his ear to it to eavesdrop, and you have some more powerful game features your player will enjoy.