/* ---------------------------------------------------------------------- */
/*
 *   TADS 3 Hypothetical Action Tester
 *   Version 4
 *   by Steve Breslin (steve.breslin@gmail.com)
 *      Krister Fundin (fundin@yahoo.com)
 */

/* ---------------------------------------------------------------------- */
/*
 *   Introduction.
 */

   As the name implies, this extension is about examining the results of a
hypothetical action -- one that we might be interested in executing, but
only under certain circumstances. Without making any commitments, we can
determine whether or not it would pass verify() and/or check(), and, if we
are careful, we can even take a peek at the future game state after the
action has taken place.

   This sort of testing might be useful for suggesting commands to the
player (sorting out those that don't make any sense), or for making NPCs
look smarter by not trying things that would fail for various reasons.

   To use the extension, we start by adding the library file "hypoTester.tl"
to our project. We may also want to include "hypoTester.h" from any source
files involved with hypothetical action testing.

/* ---------------------------------------------------------------------- */
/*
 *   Two types of tests.
 */

   The hypoTester extension consists of two similar but actually quite
different parts. The first part provides these functions:

      hypoVerify()
      hypoCheck()
      hypoVerifyCheck()
      isHypoActionAchieved()
      getHypoVerifyResults()

   and the second part provides these:

      hypoTest()
      hypoWhatIf()

   All of these functions have a similar purpose -- to determine what would
happen if a certain action was executed, and, most importantly, whether it
would succeed or not -- but they operate differently. The functions from the
first category all create an action instance and silently carries out the
verify() and/or check() stages through it. This does not result in any side
effects, since one of the rules of these two stages is that game state is
not allowed to change during them.

   As for the question of success or failure, there are many other reasons
why an action could fail: implicit actions, beforeAction() methods, etc.
These cannot be caught by the functions in the first category. Indeed, we
might not want them to in some cases. An NPC, for instance, should act
according to its knowledge of the world, and the verify() stage is precisely
the right place for dealing with knowledge-based issues.

   The two functions in the second category are different, since they
determine the consequences of an action by actually executing it, after
which the game state can be analyzed freely. When the test is completed, the
VM's built-in undo facility is used in order to revert any changes caused by
the action.

   This kind of test will tell us without any doubt whether an action
succeeded or not, but it won't necessarily tell us _why_ an action failed,
and thus it is hard to know whether the failure would have been obvious to
the actor.

   We should also beware that these undo tests have their dangers. Not all
changes can be undone, so we have to make sure that the action we evaluate
doesn't E.G. end the game, ask for input, update banners, write to external
files, etc.

   There is also the potential problem of running out of undo records. The
more changes we make, the more records we use up, and there is only a finite
supply of these. (The exact number can vary from one interpreter to
another.) If we run out, then we won't be able to get back to the original
savepoint, and that means serious trouble. Even if we don't run out of
records, we may limit the number of consecutive turns that the player can
take back with the UNDO command.

   In summary, the functions that use verify() and check() should be safe to
call, but they might not give us all the information we need. Undo tests can
give us more information, but this information is necessarily of a slightly
different type, and we must also use these tests with greater caution.

/* ---------------------------------------------------------------------- */
/*
 *   Hypothetical verify() and check().
 */

   To see whether an action would pass verify() or not, we can use the
hypoVerify() function. The arguments are the actor, the action class and the
involved objects, if any. To test whether it would be logical for the actor
bob to open the front door, we could make the following call:

      hypoVerify(bob, OpenAction, frontDoor)

   Note that we have to write the name of the action in full, including the
"-Action" suffix.

   This function returns true if the action was logical and would have been
allowed to proceed if executed normally. Verify results such as nonObvious
and dangerous are not disallowing in this sense. We can, however, disallow
an action in a hypoVerify() test while still allowing it during normal
object resolution, by using the special hypoIllogical verify result.

   If we need more detailed information about the verify results, we can
also use the getHypoVerifyResults() function. The arguments are the same,
but this function returns a VerifyResultList object. For more information on
these, consult the adv3 sources (the verify.t file in this case).

   Another variation of hypothetical verification is the
isHypoActionAchieved() function, which returns true if the action would be
illogical because it has already been achieved. This is done by checking for
an illogicalAlready verify result.

   Hypothetical check() is similar. We can use the hypoCheck() function to
test if an action would pass the check() stage. Also, the hypoVerifyCheck()
is a short-cut for testing both verify() and check() at the same time.
Again, the arguments are the same:

      hypoVerifyCheck(bob, OpenAction, frontDoor)

/* ---------------------------------------------------------------------- */
/*
 *   Hypothetical action execution.
 */

   Two similar functions are provided for undo testing. They are called
hypoTest() and hypoWhatIf(). The first one, hypoTest(), is relatively
straight-forward. It simply executes a given action and determines whether
it succeeded or not. In this case, success is defined as the absence of a
failure report in the transcript.

   The hypoWhatIf() function is a bit more powerful. It takes a callback
function as the first argument. After executing the action, it evaluates the
callback and returns the result. This way, we can make arbitrary queries
about a future game state.

   Now we can go one step further with our front door example, and actually
see what would happen when trying to open it. We could use either of the two
functions we have learned:

      hypoTest(bob, OpenAction, frontDoor)

      hypoWhatIf({: frontDoor.isOpen() }, bob, OpenAction, frontDoor)

   In the first case, we just look for a failure report, and in the second
case, we explicitly check if the door has been opened. Which one we want to
use could depend on the circumstances, but here, hypoTest() would probably
be the more natural choice.

   Another special feature of these two functions is that they can execute
more than one action at the same time. This can be achieved by wrapping each
action specification in a list, like so:

      hypoTest([bob, UnlockWithAction, frontDoor, houseKey],
               [bob, OpenAction, frontDoor])

   Note that the hypoTest() function will return nil as soon as the first
action has failed. There is no need to test the remaining actions, since the
overall result would still be failure. The hypoWhatIf() function, however,
always executes all the actions.

   Finally, having already warned against doing un-undoable things during an
undo test, we could have use for the gUndoTesting variable. This is set to
true only during a call to one of these two functions, so that if we have to
test an action that could potentially end the game, for instance, then we
could check gUndoTesting in our action code and simply skip that part during
an undo test.

/* ---------------------------------------------------------------------- */
/*
 *   Bugs and limitations.
 */

   When using hypothetical verify() and check(), remapping may not produce
the exact same results as when executing an action, if the action has two or
more object slots. Put simply, if the direct object remaps action A to
action B, then the verify/check handlers for action B will be called on the
direct object, as expected, but on the indirect object, the handlers for the
unremapped action A are called instead.

