Time to open those doors

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 TryUnlock() and 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.

Leave a Reply

Your email address will not be published. Required fields are marked *