verify.t
#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