Adding Color with Scenery
With the parser in place and the exits connecting the different rooms, the next step to turn this project into something more than an experiment is to add Scenery.
By that, I mean objects that are part of the room description or are mentioned in some of the responses that the player can actually interact with. Like a painting, for example. If the player enters Look at the painting
I want to print a description of the painting. If the player enters Destroy the painting
I want some kind of response as well. So I call these kinds of objects that the player can interact with, but cannot pick up, Scenery. I will refer to objects that the player can pick up and take with him as Items. Since Items require a slightly different logic I will treat them separately in their own class. For now, let me focus on relatively static scenery items only and then go from there.
Like we did before, to begin let’s determine what traits are unique to all scenery objects.
↳ Token
↳ Name
↳ Description
The name and description are instantly obvious, but what do I need the token for, you may wonder? The answer is really simple. The token connects the object to the vocabulary. When the player enters the word painting, I need to have some way of knowing which object he’s referring to. By making the token part of the object, I can simply go through the list of all scenery objects in a room and see if any matches theNoun
. If it does, I have found the corresponding object and I can run its logic by calling the Evaluate()
method that I will make part of ANY scenery object.
In Python terms, the definition of the Scenery
class looks like this.
class Scenery ( object ): def __init__ ( self, _token, _name, _description ): self.Token = _token self.Name = _name self.Description = _description def Describe ( self ): print ( self.Description ) return True
As you can see, aside from the initialization method, I have also already implemented a Describe()
method. Since we create and initialize each scenery object with a description, clearly we will need a method to print that description, so there.
And then, of course, I need the logic function.
def Evaluate ( self ): if Tokens.Look == globals.theVerb: self.Describe () elif Tokens.Take == globals.theVerb: print ( "You can't take that." ) elif Tokens.Smell == globals.theVerb: print ( "It doesn't smell like anything." ) else: return False return True
I am firmly embedding some default behavior for all scenery objects in this function. Look, obviously prints the object’s description, as you can see, but I also made sure that any attempt to pick up a scenery object will result in the message that it’s not possible. Just for fun I also added a response if the player decides to smell the object—not quite sure why I did that, but oh, well…
You may wonder why I have an else branch that returns True
at the end.
This might be a bit counter-intuitive at first, but let me explain. As you may recall, whenever my program successfully responds to a command I want the respective function to return True
. So why am I not returning True
after printing the responses?
I a manner of speaking, I am by simply letting the program continue to the end of the method where it returns True
. Since my elif
operations are mutually exclusive, any untreated command will end up in the else
branch, returning False
. Like I said, it may seem counter-intuitive at first, but it saves me the work of typing return True
after every successful print statement. I am not entirely sure if this is considered good or bad coding practice, but it works for me.
Now comes one of the key elements to make some magic. You may recall the Evaluate()
method I wrote for my rooms.
def Evaluate ( self ): if Tokens.Look == theVerb and None == theNoun: return self.Describe ()
With a few lines of code, I can extend this to now check and handle any scenery in the room. Here’s the new version.
def Evaluate ( self ): if None != globals.theNoun: for _obj in self.Scenery: if _token == _obj.Token: return _obj.Evaluate() elif if Tokens.Look == theVerb: return self.Describe ()
The first thing I do in the room logic now is to check if there is a noun in the player command. If there is, I know that the player wants to manipulate something in the room. In response, I run through all scenery objects in the room and if one matches this something, I call its Evaluate()
method.
Almost done, but before it all works, I need to go back to my Room definitions and add some scenery objects to each room. There!
theRooms = [ Room ( "Room0", "This is room 0 with a lonely painting on the wall.", [ Scenery ( Tokens.Painting, "painting", "It’s Van Ghogh’s ‘The Church at Auvers’." ) ], [], [ Exit ( Tokens.South, 1 ) ] ), Room ( "Room1", "This is room 1 with another lonely painting on the wall.", [ Scenery ( Tokens.Painting, "painting", "It’s Claude Monet’s ‘Woman with a Parasol’." ) ], [], [ Exit ( Tokens.North, 0 )] ) ]
When running my game, I can now walk from room to room and look at the paintings. Because of the logic in the Evaluate()
function that each scenery object has, no additional code is required because, thanks to object-oriented programming, each room knows exactly which objects are in it, and each of those objects is self-contained and knows exactly what its description looks like. Programming magic!