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];
    let last_turn = this.input_history[1];
    const player = this.game.getPlayer();

    this.game.log(
      "L1284",
      "log",
      "high",
      `[verifySentence.js] verifySentence() receive: ${this_turn.input}`,
      "Parser"
    );

    let count = {
      adjective: 0,
      adverb: 0,
      direction: 0,
      exclusion: 0,
      noun: 0,
      number: 0,
      phrase: 0,
      possessive: 0,
      preposition: 0,
      string: 0,
      unknown: 0,
      verb: 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.log(
        "L1608",
        "log",
        "high",
        `[verifySentence.js] verifySentence() ${this_word.type}: ${this_word.word}`,
        "Parser"
      );

      this.game.debug(
        `D1636`,
        `verifySentence.js `,
        `${this_word.type}: ${this_word.word}`
      );

      // --------------------------------------------------
      // character directive without comma
      // ex: "Floyd go east" vs "Floyd, go east"
      // we're expecting character directives in the form of
      // "Floyd, go east" and have already accounted for that
      // but we'll also try to handle "Floyd go east"
      // --------------------------------------------------
      if (
        position === 0 &&
        this_word.type === "noun" &&
        next_word.type === "verb"
      ) {
        // is first word the name of a character who is present?

        const parsed_noun = this.parseNoun(this_word.word);

        let asset;
        let char;
        let chars = [];
        let error = "";
        let debug = "";

        // the base assumption is that this is bad input
        // but if first word is a clearly present character,
        // we'll treat player input as a character directive
        let token = this_turn.tokens[this_word.word];
        switch (parsed_noun.matches.qualified.length) {
          case 0:
            // no asset found - continue parse
            break;
          case 1:
            // found an asset
            asset = this.game.getAsset(parsed_noun.matches.qualified[0]);
            if (!asset) {
              // no asset found
              error += `{We} {don't} need to use the word ${this_word.word}. `;
              debug = "non-existent";
            } else if (!player.knowsAbout(asset)) {
              if (this.game.settings.if_word_is_unknown_print_this) {
                error += this.game.settings.if_word_is_unknown_print_this;
              } else {
                error += `{We} {don't} know of any ${token ? token.source : this_word.word}. `;
              }
              debug = "not known";
            } else if (!asset.is.present) {
              // not present
              error = `{We} {don't} see ${asset.article_name} here. `;
              debug = "not present";
            } else if (!asset.hasClass("Character")) {
              // no character found
              error += `${asset.Article_name} ignores {us}. `;
              debug = "not character";
            } else {
              // appears to be a present character
              char = asset;
            }
            break;
          default:
            // found multiple assets
            // can we find a singular character?
            for (let i = 0; i < parsed_noun.matches.qualified.length; i++) {
              let asset = this.game.getAsset(parsed_noun.matches.qualified[i]);
              if (asset.hasClass("Character") && asset.is.present) {
                chars.push(asset);
              }
            }
            switch (chars.length) {
              case 0:
                // no character found - continue parse
                break;
              case 1:
                char = chars[0];
                break;
              default:
                error = `{We'll} have to be more specific. `;
                debug = `not unique`;
                break;
            }
            break;
        }

        if (error) {
          // any error means we failed to identify a target character
          this.game.debug(
            `D1186`,
            `verifySentence.js `,
            ` ${this_word.word} is ${debug}`
          );
          this.game.print(error);
          return false;
        }

        if (char) {
          // since player input something like "Floyd go east",
          // we haven't yet set input_verb as we otherwise would have
          this_turn.input_verb = next_word.word;

          // set specified character as subject for this turn
          this_turn.setSubject(char);

          // delete character from parsed sentence
          this_turn.parsed_sentence.shift();

          // restart the count to put verb in first position
          position = -1;

          continue;
        }
      }

      // --------------------------------------------------
      // 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.game.settings.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.game.settings.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 = {};
        }

        // this_turn.verified_sentence_structure += "adverb ";
        // not doing this for now
        // currently we don't show adverbs in the sentence structure because
        // an adverb may appear in any of several different positions
        // which overly complicates structure handling
        // but leaving it open to change in future

        // 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.game.settings.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]
              );
              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.nest.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.game.settings.getUnparsedMessage(this_turn.input);
          this.game.print(msg);
          return false;
        }

        count.preposition++;
        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
      // @TODO rehydrate tokens here?
      // --------------------------------------------------

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

      // --------------------------------------------------
      // ambiguous_pronoun - probably only applies to "her"
      // is both objective pronoun and possessive determiner
      // --------------------------------------------------
      if (this_word.type === "ambiguous_pronoun") {
        // need to determine if "her" is objective_pronoun
        // as in "examine her"
        // vs possessive_determiner
        // as in "examine her possession"
        // if her is followed by noun it's probably possessive
        if ("undefined" === typeof next_word) {
          this_word.type = "objective_pronoun";
        } else if (
          "undefined" !== typeof next_word &&
          next_word.type === "noun"
        ) {
          this_word.type = "possessive_determiner";
        }
        // else let it go to error
      }

      // --------------------------------------------------
      // possessives - possessive_determiner
      // my, his, her, its, theirs
      // try to resolve to an asset by inference
      // --------------------------------------------------

      if (this_word.type === "possessive_determiner") {
        const recent_pronouns = this.getRecentPronouns();

        // @TODO save substitutions back to input record

        if (this_word.word === "my") {
          this_word.owner = this.game.getPlayer();
          this_word.word = this.game.world._player + "'s";
          this_word.type = "possessive_noun";
        } else if (recent_pronouns[this_word.word]) {
          this_word.owner = recent_pronouns[this_word.word];
          this_word.word = recent_pronouns[this_word.word].id + "'s";
          this_word.type = "possessive_noun";
        } else {
          const room_assets = this.game
            .getRoom()
            .findAssetsByPronoun(this_word.word);

          switch (room_assets.length) {
            case 0:
              this.game.debug(
                `D1637`,
                `verifySentence.js `,
                `no asset found for objective pronoun ${this_word.word}`
              );
              msg += `It's not clear ${this_word.word === "it" ? "what" : "whom"} {we} mean by <em>${this_word.word}</em>. `;
              this.game.print(msg);
              return false;
            case 1:
              // if found set this_word.type to possessive_noun
              this_word.owner = room_assets[0];
              this_word.word = room_assets[0].id + "'s";
              this_word.type = "possessive_noun";
              break;
            default:
              // @TODO can we find a singular owner?

              this.game.debug(
                `D1645`,
                `verifySentence.js `,
                `multiple assets found for objective pronoun ${this_word.word}`
              );
              msg += `{We'll} need to be more specific ${this_word.word === "it" ? "what" : "whom"} {we} mean by <em>${this_word.word}</em>. `;
              this.game.print(msg);
              return false;
          }
        }

        if (this_word.owner) {
          // if necessary create a new phrase
          if (!last_word || last_word.type !== "preposition") {
            count.phrase++;
            this_turn.verified_sentence[`phrase${count.phrase}`] = {}; // add new phrase
          }

          // save the owner
          this_turn.verified_sentence[`phrase${count.phrase}`].possessive =
            this_word.owner;

          count.possessive++;

          continue;
        }
      }

      // --------------------------------------------------
      // possessives - possessive_noun
      // strip apostrophe to get base name, try to get an asset
      // word.match(/\b[A-Za-z-.]+(?:'s|s')\b/g);
      // --------------------------------------------------

      if (this_word.type === "possessive_noun") {
        const base_word = this_word.word.slice(0, this_word.word.indexOf("'"));
        const parsed_noun = this.parseNoun(base_word);
        let asset;
        let owner;
        let owners = [];
        let error = "";
        let debug = "";
        let token = this_turn.tokens[this_word.word];

        switch (parsed_noun.matches.qualified.length) {
          case 0:
            // no asset found - continue parse
            break;
          case 1:
            // found an asset
            asset = this.game.getAsset(parsed_noun.matches.qualified[0]);
            if (!asset) {
              // no asset found
              error += `{We} {don't} need to use the word ${base_word}. `;
              debug = "non-existent";
            } else if (!player.knowsAbout(asset)) {
              error += `{We} {don't} know of any ${token ? token.source : base_word}. `;
              debug = "not known";
            } else if (!asset.is.present) {
              // not present
              error = `{We} {don't} see ${asset.article_name} here. `;
              debug = "not present";
            } /* else if (!asset.hasClass("Character")) {
              // no character found
              error += `${asset.Article_name} ignores {us}. `;
              debug = "not character";
            } */ else {
              // appears to be a present character
              owner = asset;
            }
            break;
          default:
            // found multiple assets
            // can we find a singular character?

            for (let i = 0; i < parsed_noun.matches.qualified.length; i++) {
              let asset = this.game.getAsset(parsed_noun.matches.qualified[i]);
              // @TODO can we figure an asset for next word
              // and see if any of these assets contain it?
              if (/*asset.hasClass("Character") &&*/ asset.is.present) {
                owners.push(asset);
              }
            }
            switch (owners.length) {
              case 0:
                // no character found - continue parse
                break;
              case 1:
                owner = owners[0];
                break;
              default:
                error = `{We'll} have to be more specific. `;
                debug = `not unique`;
                break;
            }
            break;
        }

        if (error) {
          // any error means we failed to identify a target character
          this.game.debug(
            `D1191`,
            `verifySentence.js `,
            ` ${base_word} is ${debug}`
          );
          this.game.print(error);
          return false;
        }

        if (owner) {
          this_word.owner = owner;
          // we found a possessive asset

          // if necessary create a new phrase
          if (!last_word || last_word.type !== "preposition") {
            count.phrase++;
            this_turn.verified_sentence[`phrase${count.phrase}`] = {}; // add new phrase
          }

          // save the owner
          this_turn.verified_sentence[`phrase${count.phrase}`].possessive =
            owner;

          count.possessive++;

          continue;
        }
      } // possessives

      // --------------------------------------------------
      // objective pronouns
      // me, him, her, it, they/them, etc
      // try to resolve to an asset
      // consider available assets
      // consider last turn's assets
      // --------------------------------------------------
      // Subjective (he/she/they…)
      // Objective (him/her/them…)
      // Possessive determiner (his/her/their …)
      // Possessive pronoun (his/hers/theirs …)
      // Reflexive (himself/herself/themselves …)

      if (this_word.type === "objective_pronoun") {
        const recent_pronouns = this.getRecentPronouns();

        // @TODO save substitutions back to input record

        if (this_word.word === "me") {
          this_word.type = "noun";
          this_word.word = this.game.world._player;
          this_word.asset = this.game.getPlayer();
        } else if (recent_pronouns[this_word.word]) {
          // if found set this_word.type to noun and proceed
          this_word.type = "noun";
          this_word.word = recent_pronouns[this_word.word].id;
          this_word.asset = recent_pronouns[this_word.word];
        } else {
          const room_assets = this.game
            .getRoom()
            .findAssetsByPronoun(this_word.word);

          switch (room_assets.length) {
            case 0:
              this.game.debug(
                `D1646`,
                `verifySentence.js `,
                `no asset found for objective pronoun ${this_word.word}`
              );
              msg += `It's not clear ${this_word.word === "it" ? "what" : "whom"} {we} mean by ${this_word.word}. `;
              this.game.print(msg);
              return false;
            case 1:
              // if found set this_word.type to noun and proceed
              this_word.type = "noun";
              this_word.word = room_assets[0].id;
              this_word.asset = room_assets[0];
              break;
            default:
              this.game.debug(
                `D1648`,
                `verifySentence.js `,
                `multiple assets found for objective pronoun ${this_word.word}`
              );
              msg += `{We'll} need to be more specific ${this_word.word === "it" ? "what" : "whom"} {we} mean by ${this_word.word}. `;
              this.game.print(msg);
              return false;
          }
        }
      } // objective_pronoun

      // ========================================
      // noun (and variations)
      // ========================================

      // --------------------------------------------------
      // noun + itself
      // @TODO we have no other handling for itself yet
      // @TODO roll itself into reflexive pronoun handling
      // --------------------------------------------------

      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.game.settings.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

      // --------------------------------------------------
      // last turn possessive + this turn noun
      // --------------------------------------------------
      // if(last_word.type === "possessive" && this_word.type==="noun"){
      //   // does the possessor possess the noun?

      // }

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

      if (this_word.type === "noun") {
        // if necessary create a new phrase
        if (
          !last_word ||
          (last_word && last_word.type !== "preposition" && !last_word.owner)
        ) {
          count.phrase++;
          this_turn.verified_sentence[`phrase${count.phrase}`] = {}; // add new phrase
        }

        // 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;
        }

        if (last_word.owner) {
          // @TODO do we know enough yet to test if owner has noun?
        }

        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") {
        this_turn.unknown_word = this_word.word;
        this.game.debug(
          `D1200`,
          `verifySentence.js `,
          ` ${this_word.word} is unknown`
        );

        const phrase = this_turn.verified_sentence["phrase" + count.phrase];

        // is unknown word in the verb position?
        if (position === 0 && this_turn.parsed_sentence.length > 1) {
          msg += this.game.settings.getUnknownVerbMessage(this_word.word);
        }

        // is phrase possessive and unknown word a body part?
        else if (
          phrase &&
          "undefined" !== typeof phrase.possessive &&
          this.game.dictionary.hasBodyPart(this_word.word)
        ) {
          this.game.debug(
            `D1635`,
            `verifySentence.js `,
            `${this_word.word} appears to be an unused body part`
          );
          if (this.game.settings.if_word_is_unused_bodypart_print_this) {
            // try to get game settings message
            msg += this.game.settings.if_word_is_unused_bodypart_print_this;
            msg = msg.replace(
              /{name}/g,
              phrase.possessive.proper_name || phrase.possessive.name
            );
            msg = msg.replace(/{bodypart}/g, this_word.word);
          } else {
            // default message
            msg += `{We} {don't} notice anything special about ${phrase.possessive.proper_name}'s ${this_word.word}. `;
          }
          this.game.print(msg, this_turn.output_class);
          return false;
        }

        // is unknown word in our list of common words?
        else if (this.game.dictionary.hasCommonWord(this_word.word)) {
          msg += this.game.settings.getCommonWordMessage(this_word.word);
        }

        // get the generic unknown message
        else {
          msg += this.game.settings.getUnknownWordMessage(this_word.word);
        }

        this.game.print(msg, this_turn.output_class);
        return false;
      } // unknown
    }

    // final check for misparsed nouns
    if (this_turn.verified_sentence_structure.includes("noun noun")) {
    }

    //

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

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