Automating "hands-off" testing in adv3 without reading commands from file

I’m trying to test various kinds of “autonomous” NPC behaviors, where by “automomous” I mean that they’re not reacting to player input.

The “standard” way of scripting a T3 game is to create a file containing a list of commands to be executed and then feed it to the interpreter via -i or -I.

I guess I could just make a file full of line after line of zs, but that seems kinda silly and a little difficult to control (having to adjust the length of a long string of "z"s in order to adjust the number of turns to run). So I put together this:

DefineIAction(AutoTestWait) execAction() { ""; };
VerbRule(AutoTestWait) 'autotestwait': AutoTestWaitAction
        verbPhrase = 'autotestwait/autotestwaiting';

class AutoTestActor: Actor
        autoTestMaxTurns = 100
        autoTestScriptName = nil
        autoTestTurnCounter = 0
        executeTurn() {
                if(autoTestTurnCounter == 0)
                autoTestTurnCounter += 1;
                if(autoTestTurnCounter >= autoTestMaxTurns)
        autoTestStartScript() {
                if(autoTestScriptName == nil)
                executeCommand(self, self, cmdTokenizer.tokenize('script "'
                        + toString(autoTestScriptName) + '"'), true);
        autoTestTurn() { autoTestWait(); }
        autoTestWait() { newActorAction(self, AutoTestWait); }
        autoTestEnd() { throw new QuittingException(); }

Then if you want the game to run ten turns and then exit, you can declare me (or whatever you define gameMain.initialPlayerChar to be) like:

me: AutoTestActor
        autoTestMaxTurns = 10

Each of the “player” turns will use an action that doesn’t do anything apart from outputting a zero-length string (so no extraneous “Time passess…” messages in the transcript).

You can also define a file to save the transcript to via autoTestScriptName, and do something else with each of the turns by changing the autoTestTurn() method. So something like:

me: AutoTestActor
        autoTestMaxTurns = 10
        autoTestScriptName = 'test_output.txt'
        autoTestTurn() {
                "\nTaking turn <<toString(autoTestTurnCounter)>>\n ";

…will run the game for ten turns, outputting “Taking turn [number]” every turn, writing the transcript to test_output.txt.

Anyway, that’s what I’m currently using. Is there a cleaner/simpler way to do this kind of thing?

1 Like

That seems to be a pretty tight/terse implementation to me. I have gotten in the habit of appending Date() to transcript filenames, to avoid overwriting multiple runs. Other than that, not much to add.

I used a file-less hardcoded solution here (Output filters not working in action handlers - #6 by jjmcc) but as the game grew quickly shifted to file based input. Not nearly as flexible as yours.


Well, whenever I run into something like this I’m kinda hoping that I’m just missing something that already exists in the library/parser.

Anyway, I went ahead and put this together as a little module here.

Code is slightly changed from what I posted above. Notably, the module uses AutoTestActor.executeTurn() as the entry point, but now always does a newActorAction(self, AutoTestWait), and AutoTestWaitAction.execAction() then calls AutoTestActor.autoTestMain() to do most of the work.

The reason for all of this tap-dancing is that now the main logic will now be called “inside” an action instead of “before” the action for that turn. For most purposes this won’t matter at all, but some debugging actions might want access to all of the stuff you expect to be present during a normal, interactive turn.

Pared-down example (demo/src/sample.t from the repo, here with fewer comments):

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>
#include "autoTest.h"

startRoom: Room 'Void' "This is a featureless void.";
+me: AutoTestActor
        // Exit after ten turns.
        autoTestMaxTurns = 10

        // Output the "timestamp" every five turns.
        autoTestCheckpointInterval = 5
+alice: Person 'Alice' 'Alice'
        "She looks like the first person you'd turn to in a problem. "
        isProperName = true
        isHer = true
        initiallyActive = true
        isReady = true
        invokeItem() {
                defaultReport('Alice announces, <q>Turn
                        <<spellInt(libGlobal.totalTurns)>> and all is

versionInfo:    GameID;
gameMain:       GameMainDef initialPlayerChar = me;

All that happens in the “game” is that the NPC announces the turn number every turn. We define me.autoTestMaxTurns = 10 so the game will run ten turns and exit, and we define me.autoTestCheckpointInterval = 5 which means autoTestCheckpoint() will be called every 5 turns. By default autoTestCheckpoint() just outputs the number of times autoTurnMain() has been called and the current global turn number (which in this case will be the same).

The autoTestTurn() method is also always called every turn, but we didn’t define a custom method and the default one doesn’t do anything, so it doesn’t affect anything here.

A transcript:

This is a featureless void.

Alice is standing here.
autoTest: auto test turn 0, global turn 0

Alice announces, “Turn zero and all is well.”

Alice announces, “Turn one and all is well.”

Alice announces, “Turn two and all is well.”

Alice announces, “Turn three and all is well.”

Alice announces, “Turn four and all is well.”
autoTest: auto test turn 5, global turn 5

Alice announces, “Turn five and all is well.”

Alice announces, “Turn six and all is well.”

Alice announces, “Turn seven and all is well.”

Alice announces, “Turn eight and all is well.”

Alice announces, “Turn nine and all is well.”
autoTest: auto test turn 10, global turn 10
autoTest: Exiting after 10 turns.