Getting Started with Perplexity

Perplexity games are created by creating at minimum two files, named with the name you want to use for your game:

Hello World

Here’s a simple example. It creates a person named “Adam” and places him in a room. He has two hands (although he could have more, or less) so that he can “get” things:

// TestWorld.adv

// Define an object that represents the game
idTestWorldGame { instanceOf idGame }

// Create the concept of a Person
idPerson { specializes idPhysicalObject,
    Noun('person', 'character', 'human', 'individual', 'somebody')}

// Give the player a bottom face so they can be located in space on something. Give them left
// and right sides so we can connect the hands there (see below)
idAdam { instanceOf idPerson, .idProperName = 'Adam', Face(Bottom, Left, Right)}

// Place adam on the inside of the room (his bottom is touching the inside)
// The name `idAdamBottom` is generated automatically by the Face() macro above
idAdamBottom { idTouchingRel idRoom1Inside }

// Define the player with two hands so they can grab things. idHand is defined in System.adv and is special.
// The system knows that hands can grab things if they are attached to the player.
idAdamLeftHand { instanceOf idHand, Face(Back, Inside), .idSide = 'left'}
idAdamRightHand { instanceOf idHand, Face(Back, Inside), .idSide = 'right'}

// We connect them to the left and right sides of Adam and make them a "part of" him
// so that "Is your hand part of you?" will work
idAdamLeftHandBack { idConnectedPartOfRel idAdamLeft }
idAdamRightHandBack { idConnectedPartOfRel idAdamRight }

// Define the initial room
idRoom1 { instanceOf idRoom,
    Face(Inside, Bottom, Top, Back, Front, Left, Right) }

To compile it, run python DslParser.py TestWorld.adv. It will generate an output file TestWorldComp.pl.

If you open the compiled file, you can see the actual Prolog that is used by the game. Everything in the *.adv file gets compiled into a series of “data triples”, which are of the form rel(Object, Relationship, Target). Perplexity natively runs on data stored in triples like that. More information on the Perplexity language and triples can be found here.

In the Perplexity language, you just declare all the facts that you want to be true about the world. So the following statement creates a Prolog “atom” called idTestWorldGame and creates the triple rel(idTestWorldGame instanceOf idGame) in the game database as you can see by looking at TestWorldComp.pl:

idTestWorldGame { instanceOf idGame }

Everywhere that declares something is an instanceOf something is creating an actual instance of an object in the world. Whatever it is an instance of must be defined somewhere as a Concept in the system. This is done by saying what it specializes (which could be as simple as idPhysicalObject for any old thing). The concept of a “person” is not built into Perplexity, so we have to define that one (we could have made the actor an alien, a dog, etc):

// Create the concept of a Person
idPerson { specializes idPhysicalObject,
    Noun('person', 'character', 'human', 'individual', 'somebody')}

Noun is a macro that takes the first word given: person and makes that the primary name of this new concept, and all the others are synonyms. Most of the items so far like idGame, idPhysicalObject, idHand are built in concepts that Perplexity knows how to deal with. They are defined in System.adv.

Parts of the language that start with a capitol letter like Face(Bottom, Left, Right) are macros which expand out into many different Prolog triple statements. They create Prolog atoms for you using naming conventions. Face() creates the 3D faces of an object so it can be in a location, have things on it, etc. It creates atoms for the object it is used on by attaching Bottom, Top, etc to them. That’s why we can say idAdamBottom { idTouchingRel idRoom1Inside }. Both idAdamBottom and idRoom1Inside were created by the Face() macro. Most commonly you need instances to have a Bottom face so they they can be placed in the world. An Inside face is needed if the object is a container for things. Other faces are used if you want things to be put on top, etc.

And here is the associated TestWorld.pl file. Notice that it tells the system to load just the TestWorldComp.pl file at this point. It also tells the system that idAdam is the Prolog atom that is the main actor in the game.

% TestWorld.pl

% What IDs to pronouns currently refer to?
defaultActor(idAdam).
defaultActorPerspective(1).

% Scenario files must only load predicates, no directives that change data
scenarioFiles(["TestWorldComp.pl"]).

With those two files defined, you can now run the game using python Adventure.py game=TestWorld. The game will start and give you a prompt like below. It isn’t very interesting yet because haven’t used much of the system, but note that the system gives you a lot by default:

 python Adventure.py game=TestWorld

You see a room. 

? where are you?
inside a room

? what do you see?
a room and you

? what is your name?
Adam

? what are you holding?
I'm not holding a thing

Defining Objects

To make it more interesting we can add an object to the room. Note that there are not many nouns defined in the system by default, and they are all defined in System.adv. You can look at that file, or just ask Perplexity:

? what nouns do you know?
action, actor, adjective, adverb, article type, back, book, bottom, building, ceiling, 
compound name, concept, description, direction, door, east, floor, front, game, greeting,
 hand, handle, hi, inside, key, keyhole, left, lit state, lock, lock mechanism, 
 locked state, name, north, nothing, noun, number, open state, opening, order, page, 
 part of, physical object, place, power control, power state, preposition, prologue, 
 proper name, quantifier, right, room, second target, side, south, subordinate clause, 
 target, text, thing, time, title, top, verb, wall, west, when, word, and world

You can see that most of them concern grammar, although there are some things like book, key, etc that Perplexity has built in logic to handle. So, you’ll need to define the objects you want to use. Let’s define a frog:

// TestWorld.adv

...

idFrog { specializes idPhysicalObject,
    Noun('frog') }

If you compile and run TestWorld now, you can ask about frogs and it won’t complain that it doesn’t know the word:

? what is a frog?
a frog, a physical object, a place, and a thing

? where is a frog?
a frog is not in a place

? find a frog
Not sure how to go to a frog

So far, all we’ve done is teach Perplexity about the “concept” of frogs. Because we used the specializes relationship when defining it, we are still dealing with a concept. If we want to manipulate one, we need to define an instanceOf one and put it somewhere.

Perplexity decides where something is by what its bottom face is touching, so the frog needs a bottom face and it needs to be touching the inside of the computer room (see the Perplexity Physics Ontolology for more information):

// TestWorld.adv

...

idFrog1 { instanceOf idFrog,
    Face(Bottom) }
idFrog1Bottom { idTouchingRel idRoom1Inside }

Now if you compile and run:

You see a room, a frog. 

? look around
I see a frog, a room, and you. 

? where is the frog?
inside a room

? get the frog
OK

? what are you holding?
a frog

Creating and Connecting Places

A one room game is (usually) not that interesting. Let’s add a place to go:

// TestWorld.adv

...
// Define the next room
idBathroom { specializes idRoom,
    Noun('bathroom')}
idBathroom1 { instanceOf idBathroom,
    Face(Inside, Bottom, Top, Back, Front, Left, Right) }

// Put an opening on the front face of idBathroom1
idBathroom1FrontOpening { instanceOf idOpening }
idBathroom1FrontOpening { idIntegralRel idBathroom1Front }

// Put a door in the opening
idBathroom1Door { instanceOf idDoor,
    .idOpenState =  'closed' }
idBathroom1Door { idIntegralRel idBathroom1FrontOpening }

Note that we have to tell Perplexity what a “bathroom” is by defining it as a concept using specializes idRoom. This says it is a special kind of room. Otherwise, the user can’t say things like “go to the bathroom” – Perplexity won’t know the noun bathroom.

In order to get in and out of the bathroom, we have to create an opening idBathroom1FrontOpening and decide which of the faces of the bathroom it is an integral part of using idIntegralRel idBathroom1Front. Openings need to be “integral to” a face to tell Perplexity that the face has that opening. idOpening is a special object that Perplexity knows about.

Next we create an instance of a door and make it “integral” to the opening. This tells Perplexity that there is a door in that opening. These relationships are described more here.

Note that we did something new with the language: .idOpenState = 'closed'. This sets a “property” of an object. idOpenState is a type of property defined in System.adv that says if something is “openable” and what its current state is: ‘open’ or ‘closed’.

And compile and run:

You see a room, a frog. 

? what rooms are there?
a bathroom and a room just created

? where is the bathroom?
the bathroom is not in a place

? go to the bathroom
Not sure how to go to a bathroom

? does the bathroom have a door?
Yes

? where is the door?
in front of a bathroom somewhere

? is the door open?
the door is not open

Notice that Perplexity says “just created” on the first question. That’s a hint that you’ve got something that isn’t located anywhere as you can see from the other questions.

We did everything right, but failed to connect the two rooms together! We need to put an opening in the first room too, and make the same door be a part of that opening, this is one way of connecting rooms:

// TestWorld.adv

...

// Put an opening on the back face of idRoom1
idRoom1BackOpening { instanceOf idOpening }
idRoom1BackOpening { idIntegralRel idRoom1Back }

// Put the *same* door in idRoom1's opening so the two rooms
// share it
idBathroom1Door { idIntegralRel idRoom1BackOpening }

Now compile and run:

You see a room, a door, a frog.

? where is the bathroom?
south somewhere

? s
an opening is not open

? open the door
The door is now open. 

? s
You see a bathroom, a door. 

That’s more like it!

Descriptive Language

So far the language we’re using is pretty basic. Perplexity knows about some common nouns like “bathroom”, but normally you’d like a game to have more colorful descriptions in it.

The idDescription property sets what to say when describing a room, and idExamineText says what to say when an object is examined. Let’s set those properties:

// TestWorld.adv
...

// Define the initial room
idRoom1 { instanceOf idRoom,
    Face(Inside, Bottom, Top, Back, Front, Left, Right),
    .idDescription = 'The room is bare, lacking even the most basic furniture.'}

...

idFrog1 { instanceOf idFrog,
    Face(Bottom) ,
    .idExamineText = 'This little frog is bright green!'}
    
...

After compile and run:

The room is bare, lacking even the most basic furniture. You see a door, a frog.

? examine the frog
This little frog is bright green!

One of the pitfalls of adding freeform text like this is that users will use the terms in their own questions. For example:

? get the bright green frog
I didn't understand 'bright' and 'green'. Maybe try: 'get the [thing] on the floor'?


? what is green?
I didn't understand 'green'. Maybe try: 'what is in your hand?'?

When you use words in text that users might latch on to, you need to teach them to Perplexity too. Let’s define bright and green and make the frog be them.

Perplexity doesn’t know colors or tints like “bright” natively. It does know what an adjective is, though (that is defined in System.adv). So we can define new adjectives and apply them to the frog:

// TestWorld.adv
...

idFrog1 { instanceOf idFrog,
    Face(Bottom) ,
    .idColor = 'green',
    .idTint = 'bright',
    .idExamineText = 'This little frog is bright green!'}
    
idColor { specializes idAdjective,
    Noun('color', 'hue', 'chroma', 'chromaticity', 'coloration', 'coloring'),
    AdjectiveValue('green', 'greenish')
    }

idTint { specializes idAdjective,
    Noun('tint'),
    AdjectiveValue('bright', 'light')
    }

...

In the definition of idColor you can see that the first Noun is “color”, but there are several others. That means that “color” is the primary word (the word Perplexity uses by default) and the others are synonyms that Perplexity will recognize as well. The same is true for the AdjectiveValue words.

To use them, set a property on the frog using the identifiers just created: idColor and idTint and set the properties to the values we want.

After compiling and running:

? get the green frog
OK

? get the bright green frog
OK

? what is green?
a frog

? what is bright?
a frog

? get the light frog
OK

? get the light greenish frog
OK

? what hue is the frog?
green

? what tint is the frog?
bright

For a real game, we’d probably want to define bare and little as well since it is in the room and frog descriptions.

Containers

Let’s say we wanted to put a wooden cabinet in the bathroom that has a drawer in it. We know how to create a cabinet, how to define an adjective for “wood”, and we know how to create a drawer. How do you make the drawer be “in” the cabinet?

There are many ways to model this kind of thing, but if we make the bottom of the drawer be “connected and part of” the cabinet, we get a few benefits:

// TestWorld.adv

...

idMaterial { specializes idAdjective,
    Noun('material'),
    AdjectiveValue('wood')
    }

idCabinet { specializes idPhysicalObject,
    Noun('cabinet', 'cupboard')}

idCabinet1 { instanceOf idCabinet,
    .idMaterial = 'wood',
     Face(Inside, Bottom, Top, Back, Left, Right),
     .idExamineText = 'The cabinet is made of wood.',
     .idDisplayName = 'wooden cabinet'}

idCabinet1Bottom { idTouchingRel idBathroom1Inside }

idDrawer { specializes idPhysicalObject,
    Noun('drawer')}
idDrawer1 { instanceOf idDrawer,
    Face(Inside, Bottom, Top, Back, Front, Left, Right),
    .idOpenState =  'closed'}
idDrawer1Bottom { idConnectedPartOfRel idCabinet1Inside }

We’ve used another new property here: idDisplayName. This property sets the default thing to call an object. It is another way to make the text more colorful. Without it, the object would just be called “a cabinet” since that is what kind of object it is.

The key to allowing the drawer to be opened and closed is giving it a idOpenState property. This can be given to any object and Perplexity will know how to open and close it, answer questions about it, etc.

Compiling and running:

The room is bare, lacking even the most basic furniture. You see a door, a frog. 

? open the door
The door is now open. 

? s
You see a bathroom, a door, a wooden cabinet. 

? describe the cabinet
The cabinet is made of wood. It also has a drawer. 

? is the drawer open?
the drawer is not open

? open the drawer
The drawer is now open. 

? open the cabinet
The drawer of the wooden cabinet is already open. 

? close the cabinet
The drawer is now closed. 

? is the drawer part of the cabinet?
Yes

? get the drawer
OK

? what are you holding?
a wooden cabinet

Perplexity is smart about reporting the state of the cabinet as “open” when something connected to it is “open”.

Also: because the drawer is part of the cabinet, “getting” it, picks up the whole thing. Really, we want neither to be moveable. So we add the idImmovable property to it (again, all the predefined properties and objects in the world are defined in System.adv):

// TestWorld.adv

...

idCabinet1 { instanceOf idCabinet,
    .idMaterial = 'wood',
     Face(Inside, Bottom, Top, Back, Left, Right),
     .idExamineText = 'The cabinet is made of wood.',
     .idImmovable = 'yes',
     .idDisplayName = 'wooden cabinet'}

...
The room is bare, lacking even the most basic furniture. You see a door, a frog. 

? open the door
The door is now open. 

? s
You see a bathroom, a door, a wooden cabinet. 

? get the drawer
a wooden cabinet can't be moved

? get the cupboard
a wooden cabinet can't be moved

Better! OK, now let’s put something in the drawer:

// TestWorld.adv

...

idPearl { specializes idPhysicalObject,
    Noun('pearl')}

idPearl1 { instanceOf idPearl,
    Face(Bottom) }

idPearl1Bottom { idTouchingRel idDrawer1Inside }
The room is bare, lacking even the most basic furniture. You see a door, a frog. 

? open the door
The door is now open. 

? s
You see a bathroom, a door, a wooden cabinet. 

? describe the cabinet
The cabinet is made of wood. It also has a drawer. 

? what is in the drawer?
a pearl

Wait, what? Turns out Perplexity by default will tell you anything…If you want it to be hidden you have to say so:

// TestWorld.adv

...

idDrawer1Inside { .idUnknownObject = true }

...

idPearl1 { instanceOf idPearl,
    Face(Bottom),
    .idUnknownObject = true }

...

This says that anything inside of the drawer should not be reported and that the existence of the pearl itself is also hidden. Both are needed, otherwise you might be able to ask Perplexity leading questions about the pearl to discover that it exists like “where is a pearl?”

The room is bare, lacking even the most basic furniture. You see a door, a frog. 

? where is a pearl?
a pearl is not in a place

? open the door
The door is now open. 

? s
You see a bathroom, a door, a wooden cabinet. 

? what is in the drawer?
I don't know what is in the drawer

? open the drawer
The drawer is now open. Inside is a pearl. 

? get the pearl
OK

Notice that we never described what part of the drawer is actually “opening”. We kind of cheated. We just gave the drawer an idOpenState property and Perplexity just sets its value and reports it as “open” or “closed” without really doing anything. Which you can see if you say:

? what part of the drawer is open?
a part doesn't have an open state

Which is Perplexity’s way of saying “no part has an open state”. See next section.

Safes, Locks and Parts That Open

Let’s build a cool jewelry box that unlocks…using the pearl as the key! But this time, let’s have the top be the part that opens up:

// TestWorld.adv

...

idJewelry { specializes idPhysicalObject,
    Noun('jewelry') }

idBox { specializes idPhysicalObject,
    Noun('box') }

idJewelryBox { specializes idBox,
    CompoundPrefix(idJewelry),
    .idDisplayName = 'jewelry box' }

idJewelryBox1 { instanceOf idJewelryBox,
    Face(Inside, Left, Right, Back, Front, Bottom, Top) }
idJewelryBox1Inside { .idUnknownObject = true }
idJewelryBox1Bottom { idTouchingRel idCabinet1Top }

idJewelryBox1Top {
    .idOpenState ='closed',
    .idLockMechanism = idLock1 }
idLock1 { instanceOf idLock, Face(Left, Right, Back, Front, Bottom, Top),
    .idLockedState = 'locked' }
idLock1Back { idConnectedPartOfRel idJewelryBox1Top }
idLock1_prop_idLockedState { .idRequiredTool = idPearl1 }

“jewelry box” is a compound name (i.e. more than one word) which we haven’t encountered yet. Perplexity is good at figuring out what users mean by compound names if they are logically deducible. For example, without any special declarations, Perplexity can handle “open the cabinet drawer” because it knows the drawer is part of the cabinet. However, “jewelry box” won’t have any cues like that since it is just a single object. For cases like this we use the CompoundPrefix(idJewelry) macro when we define the idJewelryBox concept. This tells Perplexity that any of the words for idJewelry can be put in front of any of the words for idBox when naming this object. We also set idDisplayName to whatever we want Perplexity to call it by default. If we didn’t set this, Perplexity would use the name of the thing it specializes which is simply “box”.

To make the top of the jewelry box be the part that open and closes, we set idOpenState on the face of the object we want to open and close (not the whole object itself).

If we want questions like “does the box have a lock?” to work, we need to define a “lock” object (idLock1) and connect it to the jewelry box using idLock1Back { idConnectedPartOfRel idJewelryBox1Top }. We connected it to the top so that “does the top of the box have a lock” would work, but it could be anywhere.

To actually make the idLock1 object lock and unlock, we give it an idLockedState. To make this locked state control whether the box can be opened, we give the object that opens (in this case, the Top of the box) an idLockMechanism property and set it to idLock1.

At this point, Perplexity will do everything you’d expect, but doesn’t require any tool to do the unlocking. The user can just say “unlock the box” and it will work. Giving the idLockState property a property of its own called idRequiredTool says what object is required to lock and unlock something. Properties of an object in Perplexity can be accessed using the syntax: object_prop_property. Thus to give theidLockedState property of idLock1 a property of its own, we use the syntax: idLock1_prop_idLockedState { .idRequiredTool = idPearl1 }. Now, the pearl can unlock and unlock the jewelry box!

The reason for all of these properties is to allow separating out things that are locked from what unlocks them. The lock could be in a completely different place from the thing it unlocks.

Let’s try it:

The room is bare, lacking even the most basic furniture. You see a door, a frog. 

? open the door
The door is now open. 

? s
You see a bathroom, a door, a wooden cabinet, a jewelry box. 

? what is in the box?
I don't know what is in the box
Maybe try 'what is on the box?' instead...

? what is on the box?
a lock

? what is in the jewelry box?
I don't know what is in the box
Maybe try 'what is on the jewelry box?' instead...

? open the box
It is locked

? unlock the box
not sure what to lock or unlock it with...

? open the drawer
The drawer is now open. Inside is a pearl. 

? unlock the jewelry box with the pearl
The jewelry box is now unlocked. 

? open the box
The on top of a jewelry box is now open. 

? what part of the box is open?
on top of a jewelry box