# verify.t | documentation

    #charset "us-ascii"

    /*
     *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved.
     *
     *   TADS 3 Library: verification
     *
     *   This module defines classes related to "verification," which is the
     *   phase of command execution where the parser attempts to determine how
     *   logical a command is.
     */

    #include "adv3.h"

    /* ------------------------------------------------------------------------ */
    /*
     *   Verification result class.  Verification routines return a
     *   verification result describing whether or not an action is allowed,
     *   and how much sense the command seems to make.  When a verification
     *   fails, it must include a message describing why the command isn't
     *   allowed.
     *
     *   It is important to understand that the purpose of verification
     *   results is to guess what's in the player's mind, not to reflect the
     *   full internal state of the game.  We use verification results to
     *   figure out what a player means with a command, so if we were to rely
     *   on information the player doesn't have, we would not correctly guess
     *   the player's intentions.  So, in choosing a verification result, only
     *   information that ought to be obvious to the player should be
     *   consdidered.
     *
     *   For example, suppose we have a closed door; suppose further that the
     *   door happens to be locked, but that there's no way for the player to
     *   see that just by looking at the door.  Now, if the player types
     *   "close door," we should return "currently illogical" - common sense
     *   tells the player that the door is something that can be opened and
     *   closed, so we wouldn't return "always illogical," but the player can
     *   plainly see that the door is already closed and thus would know that
     *   it makes no sense to close it again.  In other words, the player
     *   would conclude looking at the door that closing it is currently
     *   illogical, so that's the result we should generate.
     *
     *   What if the player types "open door," though?  In this case, should
     *   we return "currently illogical" as well, because the door is locked?
     *   The answer is no.  We know that the command won't succeed because we
     *   know from looking at the internal game state that the door is locked,
     *   but that doesn't matter - it's what the *player* knows that's
     *   important, not what the internal game state tells us.  So, what
     *   should we return here?  It might seem strange, but the correct result
     *   is "logical" - as far as the player is concerned, the door is
     *   something that can be opened and closed, and it is currently closed,
     *   so it makes perfect sense to open it.
     */
    class VerifyResult: MessageResult
        /*
         *   Is the action allowed?  This returns true if the command can be
         *   allowed to proceed on the basis of the verification, nil if not.
         */
        allowAction = true

        /*
         *   Is the action allowed as an implicit action?  This returns true
         *   if the command can be allowed to proceed AND the command can be
         *   undertaken simply because it's implied by another command, even
         *   though the player never explicitly entered the command.  We
         *   distinguish this from allowAction so that we can prevent certain
         *   actions from being undertaken implicitly; we might want to
         *   disallow an implicit action when our best guess is that a player
         *   should know better than to perform an action because it's
         *   obviously dangerous.
         */
        allowImplicit
        {
            /*
             *   by default, any allowable action is also allowed as an
             *   implicit action
             */
            return allowAction;
        }

        /*
         *   Am I worse than another result?  Returns true if this result is
         *   more disapproving than the other.
         */
        isWorseThan(other)
        {
            /* I'm worse if my result ranking is lower */
            return (resultRank < other.resultRank);
        }

        /*
         *   compare to another: negative if I'm worse than the other, zero if
         *   we're the same, positive if I'm better
         */
        compareTo(other)
        {
            /* compare based on result rankings */
            return resultRank - other.resultRank;
        }

        /*
         *   Determine if I should appear in a result list before the given
         *   result object.  By default, this is true if I'm worse than the
         *   given result, but some types of results use special sorting
         *   orders.
         */
        shouldInsertBefore(other)
        {
            /*
             *   by default, I come before the other in a result list if I'm
             *   worse than the other, because we keep result lists in order
             *   from worst to best
             */
            return compareTo(other) < 0;
        }

        /*
         *   Determine if I'm identical to another result.  Note that it's
         *   possible for two items to compare the same but not be identical -
         *   compareTo() is concerned only with logicalness ranking, but
         *   identicalTo() determines if the two items are exactly the same.
         *   Some subclasses (such as LogicalVerifyResult) distinguish among
         *   items that compare the same but have different reasons for their
         *   rankings.
         */
        identicalTo(other)
        {
            /* by default, I'm identical if my comparison shows I rank the same */
            return compareTo(other) == 0;
        }

        /*
         *   Our result ranking relative to other results.  Each result class
         *   defines a ranking level so that we can determine whether one
         *   result is better (more approving) or worse (more disapproving)
         *   than another.
         *
         *   To allow easy insertion of new library extension result types or
         *   game-specific result types, we assign widely spaced rankings to
         *   the pre-defined results.  This is arbitrary; the only thing that
         *   matters in comparing two results is the order of the rank values.
         */
        resultRank = nil

        /*
         *   Should we exclude plurals from being matched, when this type of
         *   result is present?  By default, we don't; some illogical types
         *   might want to exclude plurals because the result types indicate
         *   such obvious illogicalities.
         */
        excludePluralMatches = nil
    ;

    /*
     *   Verification result - command is logical and allowed.
     *
     *   This can provide additional information ranking the likelihood of the
     *   command intepretation, which can be useful to distinguish among
     *   logical but not equally likely possibilities.  For example, if the
     *   command is "take book," and the actor has a book inside his or her
     *   backpack, and there is also a book on a table in the actor's
     *   location, it would make sense to take either book, but the game might
     *   prefer to take the book on the table because it's not already being
     *   carried.  The likelihood level can be used to rank these
     *   alternatives: if the object is being carried indirectly, a lower
     *   likelihood ranking would be returned than if the object were not
     *   already somewhere in the actor's inventory.
     */
    class LogicalVerifyResult: VerifyResult
        construct(likelihoodRank, key, ord)
        {
            /* remember my likelihood ranking */
            likelihood = likelihoodRank;

            /* remember my key value */
            keyVal = key;

            /* remember my list order */
            listOrder = ord;
        }

        /* am I worse than the other result? */
        isWorseThan(other)
        {
            /*
             *   I'm worse if my result ranking is lower; or, if we are both
             *   LogicalVerifyResult objects, I'm worse if my likelihood is
             *   lower.
             */
            if (resultRank == other.resultRank)
                return likelihood < other.likelihood;
            else
                return inherited(other);
        }

        /* compare to another result */
        compareTo(other)
        {
            /*
             *   if we're not both of the same rank (i.e., 'logical'), inherit
             *   the default comparison
             */
            if (resultRank != other.resultRank)
                return inherited(other);

            /*
             *   we're both 'logical' results, so compare based on our
             *   respective likelihoods
             */
            return likelihood - other.likelihood;
        }

        /* determine if I go in a result list before the given result */
        shouldInsertBefore(other)
        {
            /* if we're not both of the same rank, use the default handling */
            if (resultRank != other.resultRank)
                return inherited(other);

            /*
             *   we're both 'logical' results, so order in the list based on
             *   our priority ordering; if we're of the same priority, use the
             *   default ordering
             */
            if (listOrder != other.listOrder)
                return listOrder < other.listOrder;
            else
                return inherited(other);
        }

        /* determine if I'm identical to another result */
        identicalTo(other)
        {
            /*
             *   I'm identical if I compare the same and my key value is the
             *   same.
             */
            return compareTo(other) == 0 && keyVal == other.keyVal;
        }

        /*
         *   The likelihood of the command - the higher the number, the more
         *   likely.  We use 100 as the default, so that there's plenty of
         *   room for specific rankings above or below the default. Particular
         *   actions might want to rank likelihoods based on action-specific
         *   factors.
         */
        likelihood = 100

        /*
         *   Our list ordering.  This establishes how we are entered into the
         *   master results list relative to other 'logical' results.  Results
         *   are entered into the master list in ascending list order, so a
         *   lower order number means an earlier place in the list.
         *
         *   The list ordering is more important than the likelihood ranking.
         *   Suppose we have two items: one is at list order 10 and has
         *   likelihood 100, and the other is at list order 20 and has
         *   likelihood 50.  The order of the likelihoods stored in the list
         *   will be (100, 50).  This is inverted from the normal ordering,
         *   which would put the worst item first.
         *
         *   The point of this ordering is to allow for logical results with
         *   higher or lower importances in establishing the likelihood.  The
         *   library uses the following list order values:
         *
         *   100 - the default ranking.  This is used in most cases.
         *
         *   150 - secondary ranking.  This is used for rankings that aren't
         *   of great importance but which can be useful to distinguish
         *   objects in cases where no more important rankings are present.
         *   The library uses this for precondition verification rankings.
         */
        listOrder = 100

        /*
         *   my key value, to distinguish among different results with the
         *   same likelihood ranking
         */
        keyVal = ''

        /* result rank - we're the most approving kind of result */
        resultRank = 100
    ;

    /*
     *   Verification result - command is logical and allowed, but is
     *   dangerous.  As with all verify results, this should reflect our best
     *   guess as to the player's intentions, so this should only be used when
     *   it is meant to be obvious to the player that the action is dangerous.
     */
    class DangerousVerifyResult: VerifyResult
        /*
         *   don't allow dangerous actions to be undertaken implicitly - we do
         *   allow these actions, but only when explicitly requested
         */
        allowImplicit = nil

        /* result rank - we're only slightly less approving than 'logical' */
        resultRank = 90

        /* this result indicates danger */
        isDangerous = true
    ;

    /*
     *   Verification result - command is currently illogical due to the state
     *   of the object, but might be logically applied to the object at other
     *   times.  For example, "open door" on a door that's already open is
     *   illogical at the moment, but makes more sense than opening something
     *   that has no evident way to be opened or closed to begin with.
     */
    class IllogicalNowVerifyResult: VerifyResult
        /* the command isn't allowed */
        allowAction = nil

        /* result rank */
        resultRank = 40
    ;

    /*
     *   Verification result - command is currently illogical, because the
     *   state that the command seeks to impose already obtains.  For example,
     *   we're trying to open a door that's already open, or drop an object
     *   that we're not carrying.
     *
     *   This is almost exactly the same as an "illogical now" result, so this
     *   is a simple subclass of that result type.  We act almost the same as
     *   an "illogical now" result; the only reason to distinguish this type is
     *   that it's an especially obvious kind of condition, so we might want to
     *   use it to exclude some vocabulary matches that we wouldn't normally
     *   exclude for the more general "illogical now" result type.
     */
    class IllogicalAlreadyVerifyResult: IllogicalNowVerifyResult
        /* exclude plural matches when this result type is present */
        excludePluralMatches = true
    ;

    /*
     *   Verification result - command is always illogical, regardless of the
     *   state of the object.  "Close fish" might fall into this category.
     */
    class IllogicalVerifyResult: VerifyResult
        /* the command isn't allowed */
        allowAction = nil

        /* result rank - this is the most disapproving of the disapprovals */
        resultRank = 30
    ;

    /*
     *   Verification result - command is always illogical, because it's trying
     *   to use an object on itself in some invalid way, as in PUT BOX IN BOX.
     *
     *   This is almost identical to a regular always-illogical result, so
     *   we're a simple subclass of that result type.  We distinguish these
     *   from the basic always-illogical type because it's especially obvious
     *   that the "self" kind is illogical, so we might in some cases want to
     *   exclude a vocabulary match for the "self" kind that we wouldn't
     *   exclude for the basic kind.
     */
    class IllogicalSelfVerifyResult: IllogicalVerifyResult
        /* exclude plural matches when this result type is present */
        excludePluralMatches = true
    ;

    /*
     *   Verification result - command is logical and allowed, but is
     *   non-obvious on this object.  This should be used when the command is
     *   logical, but should not be obvious to the player.  When this
     *   verification result is present, the command is allowed when performed
     *   explicitly but will never be taken as a default.
     *
     *   In cases of ambiguity, a non-obvious object is equivalent to an
     *   always-illogical object.  A non-obvious object *appears* to be
     *   illogical at first glance, so we want to treat it the same as an
     *   ordinarily illogical object if we're trying to choose among ambiguous
     *   objects.
     */
    class NonObviousVerifyResult: VerifyResult
        /*
         *   don't allow non-obvious actions to be undertaken implicitly - we
         *   allow these actions, but only when explicitly requested
         */
        allowImplicit = nil

        /*
         *   non-obvious objects are illogical at first glance, so rank them
         *   the same as objects that are actually illogical
         */
        resultRank = (IllogicalVerifyResult.resultRank)
    ;

    /*
     *   Verification result - object is inaccessible.  This should be used
     *   when a command is applied to an object that is not accessibile in a
     *   sense required for the command; for example, "look at" requires that
     *   its target object be visible, so a "look at" command in the dark
     *   would fail with this type of result.
     */
    class InaccessibleVerifyResult: VerifyResult
        /* the command isn't allowed */
        allowAction = nil

        /*
         *   This ranks below any illogical result - inaccessibility is a
         *   stronger disapproval than mere illogicality.
         */
        resultRank = 10
    ;

    /*
     *   Default 'logical' verify result.  If a verification result list
     *   doesn't have an explicitly set result, this is the default value.
     */
    defaultLogicalVerifyResult: LogicalVerifyResult
        showMessage()
        {
            /* the default logical result has no message */
        }
        keyVal = 'default'
    ;

    /*
     *   Verification result list.
     */
    class VerifyResultList: object
        construct()
        {
            /* initialize the results vector */
            results_ = new Vector(5);
        }

        /*
         *   Add a result to our result list.
         */
        addResult(result)
        {
            local i;

            /*
             *   Find the insertion point.  We want to keep the results sorted
             *   in order from worst to best, so insert this result before the
             *   first item in our list that's better than this item.
             */
            for (i = 1 ; i <= results_.length() ; ++i)
            {
                /*
                 *   if it's exactly the same as this item, don't add it -
                 *   keep only one of each unique result
                 */
                if (result.identicalTo(results_[i]))
                    return;

                /*
                 *   If the new result is to be inserted before the result at
                 *   the current index, insert the new result at the current
                 *   index.
                 */
                if (result.shouldInsertBefore(results_[i]))
                    break;
            }

            /* add the result to our list at the index we found */
            results_.insertAt(i, result);
        }

        /*
         *   Is the action allowed?  We return true if we have no results;
         *   otherwise, we allow the action if *all* of our results allow it,
         *   nil if even one disapproves.
         */
        allowAction()
        {
            /* approve if the effective result approves */
            return results_.indexWhich({x: !x.allowAction}) == nil;
        }

        /*
         *   Do we exclude plural matches?  We do if we have at least one
         *   result that excludes plural matches.
         */
        excludePluralMatches()
        {
            /* exclude plural matches if we have any result that says to */
            return results_.indexWhich({x: x.excludePluralMatches}) != nil;
        }

        /*
         *   Is the action allowed as an implicit action?  Returns true if we
         *   have no results; otherwise, returns true if *all* of our results
         *   allow the implicit action, nil if even one disapproves.
         */
        allowImplicit()
        {
            /* search for disapprovals; if we find none, allow it */
            return results_.indexWhich({x: !x.allowImplicit}) == nil;
        }

        /*
         *   Show the message.  If I have any results, we'll show the message
         *   for the effective (i.e., most disapproving) result; otherwise we
         *   show nothing.
         */
        showMessage()
        {
            local res;

            /*
             *   Find the first result that disapproves.  Only disapprovers
             *   will have messages, so we need to find a disapprover.
             *   Entries are in ascending order of approval, and we want the
             *   most disapproving disapprover, so take the first one we find.
             */
            if ((res = results_.valWhich({x: !x.allowAction})) != nil)
                res.showMessage();
        }

        /*
         *   Get my effective result object.  If I have no explicitly-set
         *   result object, my effective result is the defaut logical result.
         *   Otherwise, we return the most disapproving result in our list.
         */
        getEffectiveResult()
        {
            /* if our list is empty, return the default logical result */
            if (results_.length() == 0)
                return defaultLogicalVerifyResult;

            /*
             *   return the first item in the list - we keep the list sorted
             *   from worst to best, so the first item is the most
             *   disapproving result we have
             */
            return results_[1];
        }

        /*
         *   Compare my cumulative result (i.e., my most disapproving result)
         *   to that of another result list's cumulative result.  Returns a
         *   value suitable for sorting: -1 if I'm worse than the other one, 0
         *   if we're the same, and 1 if I'm better than the other one.  This
         *   can be used to compare the cumulative verification results for
         *   two objects to determine which object is more logical.
         */
        compareTo(other)
        {
            local lst1;
            local lst2;
            local idx;

            /* get private copies of the two lists */
            lst1 = results_.toList();
            lst2 = other.results_.toList();

            /* keep going until we find differing items or run out of items */
            for (idx = 1 ; idx <= lst1.length() || idx <= lst2.length(); ++idx)
            {
                local a, b;
                local diff;

                /*
                 *   Get the current item from each list.  If we're past the
                 *   end of one or the other list, use the default logical
                 *   result as the current item from that list.
                 */
                a = idx <= lst1.length() ? lst1[idx] : defaultLogicalVerifyResult;
                b = idx <= lst2.length() ? lst2[idx] : defaultLogicalVerifyResult;

                /*
                 *   If the two items have distinct rankings, simply return
                 *   the sense of the ranking.
                 */
                if ((diff = a.compareTo(b)) != 0)
                    return diff;

                /*
                 *   The two items at the current position have equivalent
                 *   rankings, so ignore them for the purposes of comparing
                 *   these two items.  Simply proceed to the next item in each
                 *   list.  Before we do, though, check to see if we can
                 *   eliminate that current item from our own list - if we
                 *   have an identical item (not just ranked the same, but
                 *   actually identical) in the other list, throw the item out
                 *   of both lists.
                 */
                for (local j = 1 ; j < lst2.length() ; ++j)
                {
                    /*
                     *   if this item in the other list is identical to the
                     *   current item from our list, throw out both items
                     */
                    if (lst2[j].identicalTo(a))
                    {
                        /* remove the items from both lists */
                        lst1 -= a;
                        lst2 -= lst2[j];

                        /* consider the new current item at this position */
                        --idx;

                        /* no need to scan any further */
                        break;
                    }
                }
            }

            /*
             *   We've run out of items in both lists, so everything must have
             *   been identical in both lists.  Since we have no 'verify' basis
             *   for preferring one object over the other, fall back on our
             *   intrinsic vocabLikelihood values as a last resort.
             */
            return obj_.obj_.vocabLikelihood - other.obj_.obj_.vocabLikelihood;
        }

        /*
         *   Determine if we match another verify result list after remapping.
         *   This determines if the other verify result is equivalent to us
         *   after considering the effects of remapping.  We'll return true if
         *   all of the following are true:
         *
         *   - compareTo returns zero, indicating that we have the same
         *   weighting in the verification results
         *
         *   - we refer to the same object after remapping; the effective
         *   object after remapping is our original resolved object, if we're
         *   not remapped, or our remap target if we are
         *
         *   - we use the object for the same action and in the same role
         *
         *   Note: this can only be called on remapped results.  Results can
         *   only be combined in the first place when remapped, so there's no
         *   need to ever call this on an unremapped result.
         */
        matchForCombineRemapped(other, action, role)
        {
            /* if our verification values aren't identical, we can't combine */
            if (compareTo(other) != 0)
                return nil;

            /*
             *   check the other - how we check depends on whether it's been
             *   remapped or not
             */
            if (other.remapTarget_ == nil)
            {
                /*
                 *   The other one hasn't been remapped, so our remapped
                 *   object, action, and role must match the other's original
                 *   object, action, and role.  Note that to compare the two
                 *   actions, we compare their baseActionClass properties,
                 *   since this property gives us the identifying canonical
                 *   base class for the actions.
                 */
                return (remapTarget_ == other.obj_.obj_
                        && remapAction_.baseActionClass == action.baseActionClass
                        && remapRole_ == role);
            }
            else
            {
                /*
                 *   The other one has been remapped as well, so our remapped
                 *   object, action, and role must match the other's remapped
                 *   object, action, and role.
                 */
                return (remapTarget_ == other.remapTarget_
                        && remapAction_.baseActionClass
                           == other.remapAction_.baseActionClass
                        && remapRole_ == other.remapRole_);
            }
        }

        /*
         *   The remapped target object.  This will filled in during
         *   verification if we decide that we want to remap the nominal
         *   object of the command to a different object.  This should be set
         *   to the ultimate target object after all remappings.
         */
        remapTarget_ = nil

        /* the action and role of the remapped action */
        remapAction_ = nil
        remapRole_ = nil

        /* our list of results */
        results_ = []

        /*
         *   The ResolveInfo for the object being verified.  Note that this
         *   isn't saved until AFTER the verification is completed.
         */
        obj_ = nil

        /*
         *   The original list index for this result.  We use this when sorting
         *   a list of results to preserve the original ordering of otherwise
         *   equivalent items.
         */
        origOrder = 0
    ;

TADS 3 Library Manual
Generated on 5/16/2013 from TADS version 3.1.3