Multiplayer Interactive-Fiction Game-Design Blog


Documentation – Basic interactive Fiction Library



Download 8.87 Mb.
Page106/151
Date02.02.2017
Size8.87 Mb.
#15199
1   ...   102   103   104   105   106   107   108   109   ...   151

Documentation – Basic interactive Fiction Library


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.




From the server

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:







  1. CommandParse() - This parses the command and

    determines which parse makes the most sense, based upon

    where the player's character and other inff.





  2. CommandAct() - Once the best choice is determined,

    the command is acted upon.





Simple... except that a lot is done in these two functions.





NLPParse()

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.



CommandParse() call

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() call

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.





oParserVerb

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:





  1. You can write a custom parser for each object that has

    the special get code. This was explained above.



  2. 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:



  1. 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.)





  2. 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.



  3. 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().







Download 8.87 Mb.

Share with your friends:
1   ...   102   103   104   105   106   107   108   109   ...   151




The database is protected by copyright ©ininet.org 2024
send message

    Main page