Command Matching - ThePix/QuestJS GitHub Wiki

This page discusses how the parser matches a command to what the user typed. Each command has a "matchItems" function that scores the command against the input.

In a sense this function is the heart of the parser - when the player enters a command, the parser goes through every command and runs this function, allowing the command itself to decide how good a match it is to the text. The command that gives the highest score is selected. As part of the process, it will also attempt to match objects to the command (if there is a decent match; otherwise it will not bother).

This function is automatically added to all your commands, but it is just possible you will want to write your own. As an example, the GIVE command uses this to allow GIVE MAN BOOTS.

The function is passed the normalised string as a parameter, but is also passed the text the user types as a second parameter in case you ever want to use that.

Note that rather than returning a value, it sets an attribute on the command itself. This allows the command's script to access the values later.

More specifically, it creates a dictionary attribute for the command called "tmp" that will exist only for the this turn. This will hold the results of the matching process.

  • objectTexts - the matched object names from the player input
  • objects - the matched objects (lists of lists ready to be disambiguated)
  • score - a rating of how good the match is
  • error - a string to report why it failed, if it did!

The objects will be an array for each object role (so PUT HAT IN BOX is two), of arrays for each object listed (so GET HAT, TEAPOT AND GUN is three), of possible object matches (so GET HAT is four if there are four hats in the room). After disambiguation, the lowest level will be resolved into a single object, but the "matchItems" function can rely on that happening elsewhere.

The score is a rating for how well this command matches, based on the score attribute of the command itself (defaults to 10); if zero or less, this is an error.

If this does give an error, it is only reported if no command is a success, and then only if this command scored highest.

In the first place, this uses the "_test" function to decide if the regular expressions match. If not, it sets the score to parser.NO_MATCH (i.e., -100) and gives up.

Regular Expressions

The standard "matchItems" function in each command uses regular expressions to match input to commands, and then fragments of the text to match items. It is a big subject, discussed here.

You can give a command multiple regular expressions, using "regexes" rather than "regex". You can put some of your alternatives in dictionaries and tell Quest to rearrange the ordering. Here is a command that will let the player do USE x TO SLICE y or USE y to SLICE x. in the latter case, the regex is flagged so the objects will be reversed. You can also flag it with "reverseNotFirst", which you would have to use if you were telling an NPC to do it. You can also use a custom function, "func", which will be passed the object list, and you can manipulate it as you see fit.

new Cmd('SliceCarrot', {
  regexes:[
    /^use (.+) to slice (.+)$/,
    /^use (.+) slice (.+)$/,
    { regex:/slice (.+) with (.+)/, mod:{reverse:true}},
  ],
  objects:[
    {scope:parser.isPresent},
    {scope:parser.isPresent},
  ],
  script:function(objects) {
    msg("You slice {nm:ob1:the} with {nm:ob2:the}.", {ob1:objects[1][0], ob2:objects[0][0]})
    // ...
  },
})

There is a concrete example of this here.

Anti-regexes

You can give a command an array of regular expressions that it must not match.

This can be helpful if you have two similar commands. One example is with GIVE where we want to match GIVE MAN BOOTS, but not GIVE BOOTS TO MAN (which is handled by the GIVE TO command).

Another is for casting spells, where you want CAST STONESKIN and CAST STONESKIN ON ME to be handled by one command, but CAST ICE SHARD AT ORC to be handled with another. The issue is that CAST STONESKIN ON ME is going to be matched by both, and the latter will win as it matches an item. The solution is to give an antiRegex.

new Cmd('CastSpell', {
  regexes:[
    /^(?:cast|invoke) (.+)$/,
    /^(?:cast|invoke) (.+) on (?:me|myself|self)$/,
  ],
...


new Cmd('CastSpellAt', {
  antiRegexes:[
    /^(?:cast|invoke) (.+) on (?:me|myself|self)$/,
  ],
  regex:/^(?:cast|invoke) (.+) (?:at|on) (.+)$/,
...

lang.regex

The language file, lang-en.js, contains a dictionary, lang.regex, where the regular expressions for all the built-in commands are stored. This allows other language files to use the same commands.

You can add your own entries:

lang.regex.Attack = /^(?:attack|att) (.+)$/
lang.regex.Search = /^(?:search) (.+)$/

This is probably only useful for libraries that might one day get translated, but is worth mentioning in case anyone is wondering why the built-in commands have no regex entries.

Disabling a command

You might want to do this to implement your own command. The RPG library does it to disable the generic HIT command, so it will not interfere with the various attack commands it has.

Just set the "matchItems" to always fail.

findCmd("Attack").matchItems = function() { this.tmp = {score:parser.NO_MATCH} }

The "score" attribute

There are various ways to point the parser to a command or item, but an easy one is to give the command a "score" attribute, or the item a "parserPriority" attribute, both of which should be a number. The higher the number, the more the parser will tend to pick that one. Or set it to a negative number to discourage the parser from picking it.