How to write a generic GIVE <object> TO <character> handler

I am continually frustrated by the need to duplicate masses of code in match statements, so I thought I’d start writing more generic action handlers. After giving this some thought, it strikes me that this requires three steps:

  1. Pre-processing where you handle all the generic checks to make sure the syntax is valid, make sure objects are carried or present or whatever, and print error responses otherwise. This can be done in the on_pre_command section. By the time this step is finished, you know that the referred objects are in scope and you don’t have to repeat the checks in the next step.
  2. Processing for specific actions. This can be done in the on_command section.
  3. Post-processing for generic actions. As there is no on_post_command section, this can be done at the end of the on_command section. This is where you print a default response if none of the previous steps have handled the action.

As an example, I am trying to write an action handler for GIVE <object> TO <character>. The object may be carried or not carried or unknown. The character may be present or absent or not a character at all (an inanimate object) or unknown. The following are the significant test cases and the expected responses, where COINS is a valid object, BLAH is rubbish, VENDOR is a character and TREE is rubbish. If the character is not specified, it does an implicit GIVE with parenthesised text a la Inform. The responses marked with (***) do not behave properly because Adventuron is returning the wrong values for some of the variables. The actual response follows the asterisks. (See sample program with debugging code after the test cases.)

>GIVE
You'll have to tell me what to give.

>GIVE BLAH
You're not carrying any blah.

>GIVE BLAH TO
You're not carrying any blah. (***You'll have to tell me what to give.)

>GIVE BLAH TO VENDOR
You're not carrying any blah. (***You're not carrying any vendor.)

>GIVE BLAH TO TREE
You're not carrying any blah. (***You're not carrying any tree.)

>GIVE COINS
(to the street vendor)
The vendor returns your coins and says it's not enough.

>GIVE COINS TO
(to the street vendor)
The vendor returns your coins and says it's not enough.

>GIVE COINS TO VENDOR
The vendor returns your coins and says it's not enough.

>GIVE COINS TO TREE
You can only give the coins to something animate.

>DROP COINS
You drop the coins.

>GIVE COINS TO VENDOR
You're not carrying any coins.

>GIVE VENDOR BLAH
You're not carrying any blah. (***You're not carrying any vendor.)

>GIVE VENDOR COINS
The vendor returns your coins and says it's not enough.

The problem is that noun1 returns ‘blah’ when there is no preposition or noun2, but it ignores it otherwise and the remainder of the input becomes complete rubbish and results in nonsense responses. This is inconsistent and clearly a bug in Adventuron. Is there a workaround for this?

Here’s the test code:

start_at = room01

game_settings {
   add_standard_prepositions = false
   experimental_new_parser = true
}

strings {
   is_or_are : dynamic_string {(s1_has_trait "plural_t" ? "are" : "is")}
   it_or_them : dynamic_string {(s1_has_trait "plural_t" ? "them" : "it")}
}

traits {
   creature_t : trait;
   plural_t : trait;
}

locations {
   room01 : location "You're in a marketplace.";
}

objects {
   coins : object "some coins" at = "inventory" {traits = [plural_t]}
   note : object "a note" at = "inventory";
   stall : scenery "a stall" at = "room01";
   vendor : scenery "a street vendor" at = "room01" {traits = [creature_t]}
}

on_pre_command {
   : match "_ _" {
      : mask {
         : print {("^n^verb = " + original "verb")}
         : print {("^n^preposition1 = " + original "preposition1")}
         : print {("^n^noun1 = " + original "noun1")}
         : print {("^n^preposition2 = " + original "preposition2")}
         : print {("^n^noun2 = " + original "noun2")}
         : print {("^n^s1 = " + s1())}
         : print {("^n^s2 = " + s2() + "^m^")}
      }
   }
   : match "give _" {
      : if (noun1_is "") {
         : print {("You'll have to tell me what to " + original "verb" + ".")}
         : done;
      }
      : if (preposition2_is "" && !noun2_is "") {
         : set_sentence "give $2 to $1";
      }
      : if (!is_carried (s1())) {
         : print {("You're not carrying any " + original "noun1" + ".")}
         : done;
      }
      : if (is_worn(s1())) {
         : print "You'll have to remove {it_or_them} first.";
         : done;
      }
      : if ((preposition2_is "" || preposition2_is "to") && noun2_is "") {
         : if (is_present "vendor") {
            : print "^n^(to the street vendor)";
            : set_sentence "give $1 to vendor";
         }
         : else {
            : print "There's no one to give {it_or_them} to.";
            : done;
         }
      }
      : if (preposition2_is "to" && !s2_has_trait "creature_t") {
         : print {("You can only give the " + original "noun1" + " to something animate.")}
         : done;
      }
   }
}

on_command {
   : match "give coins" {
      : print "The vendor returns your coins and says it's not enough.";
      : done;
   }
   : match "give note" {
      : print "The vendor reads the note and returns it to you.";
      : done;
   }
}

vocabulary {
   : verb / aliases = [give, offer]
   : preposition / aliases = [to]
}

Yes, I think there’s a bug where if it’s an unknown noun, then it won’t show if there are three words or more. Or maybe not so much a bug, but for some reason it’s in there for a reason. This is in the help text for original “noun1”:

Returns the first noun that the player entered (untrimmed and unaliased). If the player types one word, then this may be empty. It will never be empty if the player types two words, but it may be empty if the player types three or more words and none of those words are matched nouns. When typing two words, one word is always understood as a verb and one word is understood as a noun, whether or not the verb or noun are found in the vocabulary.

The only workaround for it that I can see is to not specify the word in the “You’re not carrying …” In the below code, I changed all responses to “You’re not carrying that.”
(I also changed the code around because I was experimenting…so I did things in an inefficient way to catch cases…)

start_at = room01

game_settings {
   add_standard_prepositions = false
   experimental_new_parser = true
}

strings {
   is_or_are : dynamic_string {(s1_has_trait "plural_t" ? "are" : "is")}
   it_or_them : dynamic_string {(s1_has_trait "plural_t" ? "them" : "it")}
}

traits {
   creature_t : trait;
   plural_t : trait;
}

locations {
   room01 : location "You're in a marketplace.";
}

objects {
   coins : object "some coins" at = "inventory" {traits = [plural_t]}
   note : object "a note" at = "inventory";
   stall : scenery "a stall" at = "room01";
   vendor : scenery "a street vendor" at = "room01" {traits = [creature_t]}
}

on_pre_command {
   : match "_ _" {
      : mask {
         : print {("^n^verb = " + original "verb")}
         : print {("^n^noun0 = " + original "noun0")}
         : print {("^n^noun1 = " + original "noun1")}
         : print {("^n^preposition0 = " + original "preposition0")}         
         : print {("^n^preposition1 = " + original "preposition1")}         
         : print {("^n^preposition2 = " + original "preposition2")}
         : print {("^n^noun2 = " + original "noun2")}
         : print {("^n^verb0 = " + original "verb0")}
         : print {("^n^adv = " + original "adverb")}
         : print {("^n^prep = " + original "preposition")}
         : print {("^n^adj1 = " + original "adjective1")}
         : print {("^n^adj2 = " + original "adjective2")}
         : print {("^n^s1 = " + s1())}
         : print {("^n^s2 = " + s2())}
         : print "^m^";
      }
   }
   : match "give _" {
      : if (inputs() == 1) {
         : print {("You'll have to tell me what to " + original "verb" + ".")}      
      }
      : else_if (preposition_is "to") {
         : if (s1()=="unknown") {
            : print "You're not carrying that. - unknown s1";
            : done;
         }
         : else {
            : if (!is_carried (s1())) {
               : print "You're not carrying that. - known s1";
               : done;
            }
            : if (noun1_is "") { // noun1 is empty
               : if (is_present "vendor") {
                  : print "^n^(to the street vendor)";
                  : set_sentence "give $1";
               }
               : else {
                  : print "There's no one to give {it_or_them} to.";
                  : done;
               }   
            } 
            : else_if (!s2_has_trait "creature_t") {
               : print {("You can only give the " + original "noun1" + " to something animate.")}
               : done;
            }            
         }
      }
      : else {
         : if (noun1_is "vendor") {
            : if (s2() != "unknown") {
               : set_sentence "give $2";
            } 
            : else {
               : print "You're not carrying that.";
            }
         } 
         : else_if (s2()=="vendor" && s1() !="unknown") {
            : set_sentence "give $1";
         }
         : else_if (s2()=="unknown" && s1() =="unknown") {
            : print "You're not carrying that. - 1 & 2 unknown";
         }
         : else_if (is_present "vendor") {
            : print "^n^(to the street vendor)";
            : set_sentence "give $1";
         }
         : else {
            : print "There's no one to give {it_or_them} to.";
            : done;
         }              
      }
   }
}

on_command {
   : match "give coins" {
      : print "The vendor returns your coins and says it's not enough.";
      : done;
   }
   : match "give note" {
      : print "The vendor reads the note and returns it to you.";
      : done;
   }
}

vocabulary {
   : verb / aliases = [give, offer]
   : preposition / aliases = [to]
}

That doesn’t really help, as the noun is unknown, so you don’t know whether to say ‘that’ or ‘those’, as you don’t know whether the unknown noun is singular or plural. It is better to use a generic expression such as ‘any blah’ that applies irrespective of whether the unknown noun is singular or plural. It is better still to explicitly say what word is not understood or you are left guessing. Was it the first noun that was not understood or the second?

More importantly, by skipping words, the entire meaning of the sentence is changed - preposition2 becomes preposition1 and noun2 becomes noun1, so that the logic in your code becomes completely screwed up.

This skipping of words was done back in the days when Adventuron was essentially a verb/noun parser, but it is now possible to use multi-word input with a direct object and an indirect object and the skipping of words no longer works in these circumstances.

Yes, the noun1 I also noticed as a bug a bit ago working with this.
Since a work around is doable, I just gave a work around that’s not perfect, but still more understandable than “You’re not carrying any vendor”. :smiley:

Please try beta 66c.

I tried Beta 66c. It’s a bit better, but still fails in one test case. If noun1 is unknown, but noun2 is known, it incorrectly puts noun2 into noun1 and noun2 is unknown. For example:

>GIVE BLAH TO VENDOR
You're not carrying any vendor.

Interestingly, if noun1 is known and noun2 is unknown, it does the right thing. noun1 is the object and noun2 is unknown. For example:

>GIVE COINS TO BLAH
You can only give the coins to something animate.

Please try 66d.

2 Likes

Brilliant! All tested and working with Beta 66d.

I was also working on a generic routine for CUT <object> WITH <held_object>. In Beta 66c, this had a strange bug whereby one of the test cases with an unknown noun gave the correct response, but then it ran the parser a second time as though there was a second sentence. This has also been fixed.

Unless I come across any other weird scenarios, I think the multi-word input is now ready for prime time. Thank you!