Provides basic interactive fiction definitions, classes, objects, and functions.
The Basic Interactive Fiction library provides definitions, classes, objects, and functions that are necessary for interactive fiction to work. Using this library is highly recommended, since without it, you'll have to write your own.
01 How command parsing works
Describes how a command typed in by a user is processed.
How command parsing works
This tutorial describes how a command, like "pick up
the sword", is received by the
server, processed, and then acted upon.
Commands from the client are sent to the appropraite connection
object. (Usually a cConnection.) The ConnectionMessage()
method is called with the command message.
Assuming that the user isn't in a specific state (like logging
in), the ConnectionMessage() function eventually passed
the message down to a handler that calls two functions:
-
CommandParse() - This parses the command and
determines which parse makes the most sense, based upon
where the player's character and other inff.
-
CommandAct() - Once the best choice is determined,
the command is acted upon.
Simple... except that a lot is done in these two functions.
When the oParserVerb() object (in the Basic IF Library) initializes,
it loads a few commonly used NLPRuleSets into the "Commands"
parser. These are rParserVerbRuleSet and rNLPCommon. Let me explain.
Circumreality comes with some built in utility functions for helping
parse commands and speech passed to a chatterbot. The
built in functions are all accessed from the NLPParserXXX()
and NLPRuleSetXXX() functions. You may wish to look
through their documentation for complete instructions,
but here's an overview:
The built-in parser functions accept a string from the
user, such as "pick up the lantern", breaks it into
individual words ("pick", "up", "the", "lantern") and
then uses some NLP rules to figure out what the
command means.
The NLP rules are actually very simple (and primitive).
All they do is match a series of words and convert them
to a new series of words. In the case of parsing "pick up the lantern",
there is a rule someplace in rParserVerbRuleSet that
converts "pick up" to "`get". (Note the '`' in front
of "get". It tells code later-on down the pipe
that "`get" is a langauge-indepent result, and is not a
word typed in by the user.)
A temporary rule (see below) will convert "the lantern"
into "|0123456789abcdef0123456789abcdef" (or some other GUID),
indicating the exact object that's referenced.
The parser ends up producing a list of hypothesis.
One entry is the original, "pick up the lantern", with a score of
1.0. One entry is the correct one, "`get |GUID" (where GUID is
that really long number that identifies the object).
There are also other, less conclusive parses, like "pick up |GUID"
and "`get the lantern", which will be discarded later.
The function doing the parsing is NLPParse(). It returns
all the hypothesis in a list.
The CommandParse() call eventually calls into NLPParse(). However,
it first does some other work...
CommandParse() enumerates all the objects that are near the
user, using ObjectsEnumNearby(). oParserVerb is added onto
this list even though it technically isn't nearby.
Each nearby object is asked for any one-off rules that need
to be added to the list of rules passed into NLPParse().
This is done by calling NLPRuleSetTemp(), which
in turns calls NLPRuleSetTempName() and NLPRuleSetTempVerbs().
NLPRuleSetTempName() will return a rule that converts the
object's name, such as "the lantern" or "lantern" or even
"the lamp" into the "|GUID" string. Since every nearby
object is queried for a name, the call ensures that
the parser will handle the names.
Some objects will also support NLPRuleSetTempVerbs(), which
identifies verb-rules that are only available when the
object is nearby. In the example below about moving
a chess piece on a chessboard object, the rule
might be to convert "move" to "`MOVECHESSPIECE".
Finally, temporary rules are added for pronouns, so that
"it" will refer to the last object referenced, etc.
See NLPPronounGet() for more information.
The user's command ("pick up the lantern"), along
with the temporary rules, are passed into NLPParse(),
and a list of hypothesis is returned. Each hypothesis
has the possible meaning (such as "`get |GUID") and
a score.
Each nearby object is asked if it supports its own command
parser. If it does, it will return either 1 or 2 from
the NLPCommandParseQuery() call. Most objects will not support
their own parser. The reason they would is if having the
nearby allowed for some new commands. For example: If a chessboard
were nearby, a user could type "Move CHESSPIECENAME to LOCATION".
The normal verb parser in oParserVerb doesn't know about
chess board moving, but because the object is around, suddenly
the parser understands chessboard commands.
oParserVerb returns 2 from a NLPCommandParseQuery() to indicate
that it wants to parse commands.
Once CommandParse() knows all of the nearby objects that want
to parse commands. It then procedes to send all of
the hypothesis to all of the flagged objects and
asks them if they can make heads or tail of the command.
This call is made through NLPCommandParse().
If a parser can understand the command, it will return a
confidence score along with a callback that will take action
on the parse, and parameters to be passed into the callback.
The returned confidence score is multiplied by the score for
the hypothesis. The final hypothesis chosen is the one
with the highest total score resulting from the multiplication.
This way, the confidence score allows one NLPCommandParse()
call to make a higher "bid" than others, ensuring that it
gets to act upon the command.
The winner (highest score) is returned from CommandParse()
and then passed into CommandAct(). The information
about the winner includes the callback and parameters
supplied by NLPCommandParse().
CommandAct() checks to see if there was a winner from
CommandParse(). If there wasn't one, it sends a message back
to the player saying that the command wasn't understood.
If there is a winning parse, its callback is called, and the
parameters it requested are passed into it. The callback
then does whatever action is deems necessary.
While any object can providing a parser, most of the
parsing is done in the oParserVerb object. This section
will go into detail about how it works.
When NLPCommandParse() is called for a hypothesis,
such as "`get |GUID", the oParserVerb object will remove
the '`' character from the first token (in this case "`get")
and then prepend "Parse_". Therefore, "`get" is turned
into "Parse_get", "`look" into "Parse_Look", etc.
If the oParserVerb object supports the Parse_Get (or Parse_Look, etc.)
method then
that is called, passing in exactly the same parameters
as were passed into NLPCommandParse(). If Parse_Get is
not supported then NLPCommandParse() returns without
setting a callback.
Tip: If you wish to add new verbs to the command parser,
just add them the oParserVerb object. First, produce rules
in a NLPRuleSet resource that convert the verb into a token.
(For example: To allow jumping, you'd make a rule that converts
"Jump" or "Leap" to "`jump".) You'll need to call NLPRuleSetAdd()
with your resource whenever the IF session starts.
Then, add a new public method to oParserVerb called "Parse_Jump".
Have it parse the command (see below), and return
information about the parse. That's all.
Back to the call into Parse_Get()...
Parse_Get() knows that the first token is "`get". All it has
to do is look at whatever follows and determine what should
happen if this command is actually acted upon.
First, lets assume a valid command was passed in: It
would be "`get" followed by an object (from "|GUID").
Parse_Get() would first figure out a score. If the object
was visible to the actor (see ObjectCanSee()), and
accessible (sse ObjectCanAccess()), and not too heavy to
pick up, then the score would be 1.0. However, if any of
these conditions was not met, the score would be lower, such as
0.1.
This way, if there are two lanterns, only one of
which can be picked up, and the user
types "get lantern", then the lantern which can be picked up
will have a score of 1.0, while the one that can't will have
a score of 0.1. The higher score will be chosen for the command,
so the lantern which can be picked up will be taken. However,
if the only lantern around can't be picked up, its callback
will be run, and will tell the user that he can't reach the lantern.
Sometimes the hypothesis already has an acceptable parse.
If Parse_Get() has produced a lower score than what's already
there then it just returns. If its score is higher, Parse_Get()
will overwrite the existing parse with its own.
Next, Parse_Get() needs to provide a callback and parameters
to pass to it. For convention's sake, the callback will
be Act_Get(), and the parameters depend upon whether or
not the checks to see if the lantern could be reached succeded.
If the player can pick up the lantern, then the parameter might
just be the lantern object. If the player can't pick it
up (because it's on a high shelf, or is nailed to the ground)
then the parameters would be the object and some sort of value
indicating that it couldn't be picked up and why.
ParseGet() returns its score, callback, and callback parameters.
It then waits for the callback to be called. (The callback might
not be, since there may be better parses.)
When/if the callback is called, it can look at the parse parameters
and act accordingly. In the case of Act_Get(), it would
move the object to the player's possession and alter the user,
or maybe inform the user that the object couldn't be moved.
NOTE: Act_Get() needs to call NLPPronounSet() to ensure that
the pronouns such as "it" and "he" are modified to the
most-recently referenced objects.
oParserVerb - Tips and tricks
If you want to occasionally override a verb handled
by oParserVerb, but only for certain types of objects,
there are two options:
-
You can write a custom parser for each object that has
the special get code. This was explained above.
-
You can create a new method in oParserVerb to handle
the extra verb. See below...
To handle the alternate form of the verb by modifying
oParserVerb:
-
Create a NLPRuleSet resource that converts the tokenized
form of the verb, such as "`get" to "`MySpecialGet" with
a probability of 99. (As close to 100% as you can get.)
-
Make sure to load this rule set into the "Commands" parser
when the program stars up. The easiest way to do this
it to have an automatically-created object whose Constructor()
and Constructor2() load it in.
-
Add Parse_MySpecialGet() to oParserVerb. If it does successfully
parse it will need to return a score higher than 1.0 to
ensure that its parse it used instead of the one
from Parse_Get().
Share with your friends: |