Pre-release
AdventureJS Docs Downloads
Score: 0 Moves: 0
// verifySentence.js

(function () {
  /*global adventurejs A*/

  var p = adventurejs.Parser.prototype;

  /**
   * After parse, verify each word in sentence.
   * @memberOf adventurejs.Parser
   * @method adventurejs.Parser#verifySentence
   */
  p.verifySentence = function Parser_verifySentence() {
    let this_turn = this.input_history[0];
    this.game.log(
      "L1284",
      "log",
      "high",
      "verifySentence.js > " + this_turn.input,
      "Parser"
    );

    let count = {
      noun: 0,
      prep: 0,
      verb: 0,
      adverb: 0,
      adjective: 0,
      direction: 0,
      exclusion: 0,
      unknown: 0,
      string: 0,
      phrase: 0,
    };
    let firstverb;

    for (
      let position = 0;
      position < this_turn.parsed_sentence.length;
      position++
    ) {
      let this_word = this_turn.parsed_sentence[position];
      let last_word = this_turn.parsed_sentence[position - 1];
      let word_before_last = this_turn.parsed_sentence[position - 2];
      let next_word = this_turn.parsed_sentence[position + 1];
      let msg = "";

      // this.game.debug(
      //   `D1203 | verifySentence.js | ${this_word.type}/${this_word.word} `
      // );

      // ----------------------------------------
      // verb
      // ----------------------------------------

      if (this_word.type === "verb") {
        // we don't accept double verbs except for "oops verb"
        if (
          last_word &&
          last_word.type === "verb" &&
          last_word.word !== "oops"
        ) {
          // too many verbs
          this.game.debug(
            `D1198 | verifySentence.js | input found two verbs in a row`
          );
          msg += this.getUnparsedMessage(this_turn.input);
          this.game.print(msg);
          return false;
        }
        count.verb++;
        this_turn.verified_sentence_structure += "verb ";
        if (!this_turn.verified_sentence["verb" + count.verb]) {
          this_turn.verified_sentence["verb" + count.verb] = {};
        }
        this_turn.verified_sentence["verb" + count.verb].verb = this_word.word;
        this_turn.verified_sentence["verb" + count.verb].verb_properties =
          this_word.properties;

        continue;
      } // verb

      // ----------------------------------------
      // adverb
      // ----------------------------------------

      if (this_word.type === "adverb") {
        if (last_word && last_word.type === "adverb") {
          // too many adverb
          this.game.debug(
            `D1650 | verifySentence.js | parser found two or more adverbs`
          );
          msg += this.getUnparsedMessage(this_turn.input);
          this.game.print(msg);
          return false;
        }

        count.adverb++;
        // an adverb may appear before its verb, as in the case of
        // "carefully examine asset"
        if (count.verb === 0) {
          this_turn.verified_sentence.verb1 = {};
        }

        // currently we don't show adverbs in the sentence structure because
        // the same adverb may appear in any of several different positions
        // in a sentence which overly complicates structure handling
        // but still may change in future
        // this_turn.verified_sentence_structure += "adverb ";

        // this should work with
        // "carefully look at asset"
        // "look carefully at asset"
        // "look at asset carefully"
        // "tell character to look carefully"
        // @TODO it will break with
        // "tell character to carefully examine asset"
        // because it will record "tell carefully"
        this_turn.verified_sentence[
          `verb${count.verb === 0 ? 1 : count.verb}`
        ].adverb = this_word.word;
        continue;
      } // adverb

      // ----------------------------------------
      // direction
      // ----------------------------------------

      // if (this_word.type === "direction") {

      //   continue;
      // } // direction

      // ----------------------------------------
      // preposition
      // ----------------------------------------

      if (this_word.type === "preposition") {
        if (last_word && last_word.type === "preposition") {
          // too many prepositions

          // are prepositions identical?
          if (last_word.word === this_word.word) {
            this.game.debug(
              `D1320 | verifySentence.js | parser found two identical prepositions`
            );
            msg += this.getUnparsedMessage(this_turn.input);
            this.game.print(msg);
            return false;
          }

          // is it possible that the last preposition was one of: in, out, up, down ?
          // we treat these as prepositions, adverbs, or nouns depending on context
          // some possible scenarios:
          // 1) "go down through manhole" where manhole is an exit down - HANDLED
          // 2) "go up on ladder" where player can climb ladder - HANDLED
          // 3) "go down on [person]" - currently unhandled
          // 4) "look up at [asset]" - currently unhandled
          // 5) "throw up in bucket" - currently unhandled
          // 6) "turn down bed" - currently unhandled

          const last_word_is_direction = this.game.dictionary.getDirection(
            last_word.word
          );

          // 1) do last word preposition and next word noun both map to same exit?
          if (
            last_word_is_direction &&
            word_before_last?.type === "verb" &&
            next_word?.type === "noun"
          ) {
            const exit_from_last_word = this.game.getExitFromDirection(
              last_word.word
            );
            const nouns_from_next_word = this.parseNoun(next_word.word);
            let match = false;
            for (
              let n = 0;
              n < nouns_from_next_word.matches.qualified.length;
              n++
            ) {
              const exit_from_next_word = this.game.getAsset(
                nouns_from_next_word.matches.qualified[n]
              );
              console.warn({ exit_from_last_word, exit_from_next_word });
              if (
                exit_from_next_word?.direction ===
                exit_from_last_word?.direction
              ) {
                // we found a match
                match = true;
                continue;
              }
            }
            if (match) {
              // remove the leading preposition
              // remove last preposition from sentence structure
              // and save it as an adverb
              // - set verb.direction to last preposition
              this_turn.verified_sentence[`verb${count.verb}`].adverb =
                last_word.word;
              // - set last preposition in phrase to this preposition
              this_turn.verified_sentence["phrase" + count.phrase].preposition =
                this_word.word;
              this_turn.verified_sentence[
                "phrase" + count.phrase
              ].preposition_properties = this_word.properties;
              // don't advance counts or update structure
              continue;
            }
          }

          // 2) does last word preposition map to an aspect on next word noun?
          // ex: go up on ladder
          if (
            last_word_is_direction &&
            word_before_last?.type === "verb" &&
            next_word?.type === "noun"
          ) {
            // get nouns from next word
            const nouns_from_next_word = this.parseNoun(next_word.word);
            let found = false;
            for (
              let n = 0;
              n < nouns_from_next_word.matches.qualified.length;
              n++
            ) {
              const asset_from_next_word = this.game.getAsset(
                nouns_from_next_word.matches.qualified[n]
              );
              if (
                asset_from_next_word?.$is("present") &&
                asset_from_next_word.hasAspectAt(this_word.word)
              ) {
                // is player in this aspect or can player enter this aspect?
                const aspect = asset_from_next_word.getAspectAt(this_word.word);
                if (aspect.player.can.enter) {
                  found = true;
                  continue;
                }
              }
            }
            // keep last word and discard this word
            // ex: treat "go up on ladder" like "go up ladder"
            // don't advance counts or update structure
            if (found) continue;
          }

          // is it possible that the last preposition was an adverb?
          // ex: walk carefully down stairs
          // - was word before last a verb?
          // - is next word a noun?
          const last_word_is_adverb = this.game.dictionary.getAdverb(
            last_word.word
          );
          if (
            last_word_is_adverb &&
            word_before_last?.type === "verb" &&
            next_word?.type === "noun"
          ) {
            // setting last preposition to adverb means
            // - set verb.adverb to last preposition
            this_turn.verified_sentence[`verb${count.verb}`].adverb =
              last_word.word;
            // - set last preposition in phrase to this preposition
            this_turn.verified_sentence["phrase" + count.phrase].preposition =
              this_word.word;
            this_turn.verified_sentence[
              "phrase" + count.phrase
            ].preposition_properties = this_word.properties;
            // don't advance counts or update structure
            continue;
          }

          this.game.debug(
            `D1030 | verifySentence.js | parser found two or more prepositions`
          );
          msg += this.getUnparsedMessage(this_turn.input);
          this.game.print(msg);
          return false;
        }

        count.prep++;
        count.phrase++; // a preposition starts a new phrase
        this_turn.verified_sentence_structure += "preposition ";
        this_turn.verified_sentence["phrase" + count.phrase] = {
          preposition: this_word.word,
          preposition_properties: this_word.properties,
        };

        continue;
      } // preposition

      // ----------------------------------------
      // string
      // ----------------------------------------

      // if(this_word.type==="string") {
      //   continue;
      // } // string

      // ----------------------------------------
      // noun
      // ----------------------------------------

      // noun and itself ----------------------------------------
      // @TODO we have no other handling for itself yet

      if (this_word.type === "noun" && this_word.word === "itself") {
        if (!this_turn.verified_sentence["phrase" + count.phrase]?.noun) {
          this.game.debug(`D1862 | verifySentence.js | itself has no referent`);
          msg += this.getUnparsedMessage(this_turn.input);
          this.game.print(msg);
          return false;
        }
        if (last_word && last_word.type !== "preposition") {
          count.phrase++; // a new noun starts a new phrase
        }
        this_turn.verified_sentence_structure += "noun ";
        this_turn.verified_sentence["phrase" + count.phrase].noun = {
          noun: this_turn.verified_sentence["phrase" + (count.phrase - 1)].noun,
          noun_properties: this_word.properties,
        };
        // if there is a prior phrase...
        if (
          this_turn.verified_sentence["phrase" + (count.phrase - 1)].parsedNoun
        ) {
          // copy that phrase's noun to this phrase
          this_turn.verified_sentence["phrase" + count.phrase].parsedNoun =
            new adventurejs.ParsedNoun().set(
              this_turn.verified_sentence["phrase" + (count.phrase - 1)]
                .parsedNoun
            );
        }

        continue;
      } // itself

      // relative_direction aka up/down/in/out ----------------------------------------

      if (
        this_word.type === "noun" &&
        this_word.properties.relative_direction &&
        last_word &&
        last_word.type === "verb" &&
        next_word &&
        next_word.type === "noun"
      ) {
        // player has likely said something like "climb up tree"
        // we typically parse directions as nouns but in cases like this
        // we want to treat the direction as a preposition

        this.game.debug(
          ` | verifySentence.js | ${this_word.word} appears to be a spatial direction / preposition for ${next_word.word}`
        );

        count.phrase++;
        this_turn.verified_sentence["phrase" + count.phrase] = {}; // add new phrase
        this_turn.verified_sentence["phrase" + count.phrase].preposition =
          this_word.word;

        continue;
      } // this_word.properties.relative_direction

      // noun and this_word.adjective ----------------------------------------

      if (
        this_word.type === "noun" &&
        this_word.adjective &&
        next_word &&
        next_word.type === "noun"
      ) {
        // word is both noun and adjective
        // if next word is noun then it's likely that
        // this is an adjective for the next word
        // if we treat the two nouns as one phrase,
        // do they have an unambiguous match?

        // first try getting an asset
        let found_asset_id;
        let found_asset = this.game.getAsset(
          this_word.word + " " + next_word.word
        );
        if (found_asset) found_asset_id = found_asset.id;

        // otherwise parseNoun which does a wider search
        if (!found_asset) {
          let parse_both = this.parseNoun(
            this_word.word + " " + next_word.word
          );
          found_asset_id = parse_both.matches.unambiguous;
          found_asset = this.game.getAsset(found_asset_id);
        }

        if (!found_asset_id) {
          this.game.debug(
            `D1219 | verifySentence.js | ${this_word.word} appears to be an adjective for ${next_word.word} and no ${this_word.word} ${next_word.word} was found`
          );
          msg += `$(We) don't know of any ${this_word.word} ${next_word.word}. `;
          this.game.print(msg, this_turn.output_class);
          return false;
        }

        // we found a noun that matches both words

        this.game.debug(
          `D1863 | verifySentence.js | ${found_asset_id} was found to match ${this_word.word} + ${next_word.word} `
        );

        this_word.asset = found_asset;
        this_word.word = found_asset_id;

        position++; // advance past next word

        // allow to go on to default noun handling
      } // noun and adjective

      // noun default ----------------------------------------

      if (this_word.type === "noun") {
        if (last_word && last_word.type !== "preposition") {
          count.phrase++;
          this_turn.verified_sentence["phrase" + count.phrase] = {}; // add new phrase
        }
        // console.warn("this_word", this_word);
        // save the noun

        this_turn.verified_sentence["phrase" + count.phrase].noun =
          this_word.word;
        // carry forward properties from parseSentence
        this_turn.verified_sentence["phrase" + count.phrase].noun_properties =
          this_word.properties;
        // save any exclusions
        if (this_word.exclusion) {
          this_turn.verified_sentence["phrase" + count.phrase].exclusion =
            this_word.exclusion;
        }
        this_turn.verified_sentence_structure += "noun ";
        count.noun++;

        continue;
      } // noun

      // ----------------------------------------
      // adjective
      // ----------------------------------------

      if (this_word.type === "adjective") {
        // Adjectives are tricky because nouns can have them defined in
        // their names, like "red door", and nouns can also have specific
        // adjectives assigned to them, so that "red" and "door" will find
        // the "door" asset with adjective "red". If player entered an
        // adjective that resolved to a noun, that noun will have been recorded
        // and probably our sentence structure will have two nouns in it,
        // something that we won't catch here, so we'll have to check again
        // in handleSentence.js.

        // We only end up here if an
        // adjective has been typed that is not attached to a noun. For
        // instance, no noun in the game uses "violet" but we understand that
        // "violet" is an adjective and if a user inputs "x violet door"
        // we can say "you don't see any violet door".

        if (next_word && next_word.type === "noun") {
          this.game.debug(
            `D1218 | verifySentence.js | ${this_word.word} appears to be an adjective for ${next_word.word} and no ${this_word.word} ${next_word.word} was found`
          );
          msg += `$(We) don't know of any ${this_word.word} ${next_word.word}. `;
          this.game.print(msg, this_turn.output_class);
          return false;
        } // adjective + noun

        continue;
      } // adjective

      // ----------------------------------------
      // unknown
      // ----------------------------------------

      if (this_word.type === "unknown") {
        // @TODO: soft prompt for "oops"
        this_turn.unknown_word = this_word.word;
        this.game.debug(
          `D1200 | verifySentence.js | ${this_word.word} is unknown`
        );
        if (position === 0 && this_turn.parsed_sentence.length > 1) {
          msg += `$(We) don't know how to ${this_word.word} anything. `;
        } else {
          msg += `$(We) don't know of anything referred to as ${this_word.word}. `;
        }
        this.game.print(msg, this_turn.output_class);
        return false;
      } // unknown
    }

    // trim the sentence structure
    this_turn.verified_sentence_structure =
      this_turn.verified_sentence_structure.trim();

    return true;
  }; // verifySentence
})();