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