Zipping Ahead N Turns?

Since I’m trying to build some simulator activity that unfolds over time (stochastically), it’d be helpful to see how the game is behaving without sitting there typing Z a number of times.

Therefore, I’d like to be able to trigger TADS to ignore user input for some arbitrary number of turns, and zip ahead so I can see how the simulator will play out.

I’m currently looking at turnAction() for clues, but this seems a dead end.

Any thoughts?

Conrad.

You could use the test scripts extension that Jim uploaded to IF Archive. Define a script that enters >wait as many times as you’d like, scroll to the end of output, and observe the result.

Another approach would be to dispense with turn-based input and switch over to real time input, where NPCs act if they haven’t done anything in a certain amount of time, potentially interrupting the player’s turn.

If that’s where you’re headed, it might be best to get it out of the way now, since it’s a major design decision. With real time in place, you can just speed up the NPCS turn time from once per 30 seconds to every few seconds or even milliseconds to fast forward.

I use the tests.t extension a lot. It’s very handy, and setting up a test script, even a long one, is as easy as in Inform 7. (Note that it does add small text files to your hard drive.) If you want to see what happens for 20 turns, you just do this:

Test 'xyz' [ 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z' ] ;
And then in the game you type ‘test xyz’.

I’ll try both versions. I guess there are a few questions, which I’ll come back to after some experimentation.

Ben, can you give me a lead on switching to realtime npcs?

Conrad.

ps - Can switching to realtime be done temporarily? Switch to realtime, have the sim run a bit, and switch back?

I would probably commit to one model or the other.

I haven’t looked into the implementation details, though, and if it’s just a matter of swapping a RealTimeDaemon for the PromptDaemon that runs AgendaItems, it could be rather painless to switch back and forth.

Well that wouldn’t work. Fuses and such wouldn’t know to fire.

I’m looking up PromptDaemon.

Conrad.

update: I’m currently looking through tests.t, trying to figure out where its main engine is. As opposed to the list management and such.

The basic idea is to replace the list management, which maps a series of strings onto parsed commands and runs them through the surrogate input, with a counter that puts a Z command through the surrogate input N times. Seems doable… I may repost a more intelligent question here later.

Conrad.

Ok, I’m out to break this down… So I’ll comment on the parts and then anyone jump in and comment on my comments, if you like.

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

//directions-for-use code snipped
    
    /*
     *   The 'list tests' and 'list tests fully' commands can be used to list 
     *   your test scripts from within the running game.
     */
    
#ifdef __DEBUG

Now this makes the contents only work if we’re in debugging mode --which is automatic when we haven’t created a for-release version?

DefineSystemAction(ListTests)
    execSystemAction

Immediately I have a question, in that I cannot see what calls this method. It seems to be the primary driver for executing the testing script, so I would expect it to be linked in from the TEST X verb rule, but as far as I can tell, it’s not.

So how does TADS know to fire this method? – are we overwriting some existing TADS thing called SystemAction? – Must be, I suppose.

    {

This is a check to see that we’re not ready to go.

        if(allTests.lst.length == 0)
        {
            reportFailure('There are no test scripts defined in this game. ');
            exit;
        }

I say that SystemAction must be an existing TADS method because this seems to take over the handling of commands.

[research]

Oh, it’s a class. That’s weird.

Ah! We’re defining a system action called ListTests. Uh-HUH. That makes some sense.

        foreach(local testObj in allTests.lst)
        {
            "<<testObj.testName>>";
            if(gAction.fully)               
            {
                ": ";
                foreach(local txt in testObj.testList)
                    "<<txt>>/";
            }
            "\n";
        }
    }
    fully = nil
;

And now we have the verb for it. All right, all is well in the universe.

VerbRule(ListTests)
    ('list' | 'l') 'tests' (| 'fully' -> fully)
    : ListTestsAction
    verbPhrase = 'list/listing test scripts'
;

    /*
     *   The 'test X' command can be used with any Test object defined in the source code:
     */

DefineLiteralAction(Test)
   execAction()
   {
      local target = getLiteral().toLower();
      local script = allTests.valWhich({x: x.testName.toLower == target});
      if (script)
         script.run();

Ok, so the TEST X whirligig seems to run off this thing called script.run(), which in turn is a deep mystery.

Script is got from (I’m standing back and squinting more than understanding here)
pulling together everything in allTests, so it is presumably a list of allTest objects,
allTest being an object defined below and having a .run() method.

      else
         "Test sequence not found. ";
   }
;

VerbRule(Test)
   'test' singleLiteral
   : TestAction
   verbPhrase = 'test/testing (what)'
;

The individual test object… but in fact a class. So, Test is a class,
individual Test objects are not named, but pulled from a file, whereas
the list of unnamed Test objects does have a name, allTest.

class Test: object
   testName = 'nil'
   testList = [ 'z' ]
   testFile = 'TEST_' + testName + '.TCMD'

Well, I don’t understand what .run() is doing here rather than in allTests,
but anyway it seems somehow to fall through from the list to the individual
items in that list.

   run
   {
      "Testing sequence: \"<<testName>>\". ";
      local out = File.openTextFile(testFile, FileAccessWrite);
      testList.forEach({x: out.writeFile('>' + x + '\n')});
      out.closeFile();
      setScriptFile(testFile);

Well that’s just writing the actions out to the output file.

   }
;

allTests: object
   lst()

// list handling...

   {
      if (lst_ == nil)
         initLst();
      return lst_;

What if the list is empty? then initialize it and return the initialized version…
presuming lst_ is modified by initLst().

   }

   initLst()
   {
      lst_ = new Vector(50);

Ok, there it is.

    local obj = firstObj();
      while (obj != nil)

while? not if? … that means sometimes this doesn’t work to make obj not nil?

      {
         if(obj.ofKind(Test))
            lst_.append(obj);
         obj = nextObj(obj);

Ah, ok… we’re filtering for only Test type objects. Which is why we use while.

      }
      lst_ = lst_.toList();

I have no idea what that does, but presumably it converts a string
to a list or something. Parses it, sorta.

   }

   valWhich(cond)
   {
      if (lst_ == nil)
         initLst();
      return lst_.valWhich(cond);

Ok, my brain just exploded. This is dealing with the tail end of the list in some tricky recursive fashion?

So, if the tail of the list is empty, we initialize it and presumably parse in the next line.

Then, whether it’s empty or not, I guess, we call ourselves. Which means if the tail is empty, we initialize it and parse in the next line…

And we keep doing that until the line is all parsed in, and then we return-return-return everything in list form?

Ah, this is used in the original SystemAction thing, when we go looking
for a script of a specific name. So I guess it filters by name, going through the entire list of scripts available, recursively.

   }

   lst_ = nil

But none of this has happened yet, or it’s already happened, so we set the tail to nil.

;

#endif // __DEBUG

End of the debugging-only block.

Fine, but where was the part where the script actions were actually run through the game?

Going for a second round…

Nope, not finding it. I mean, presumably this thing must somewhere actually
implement the verbs contained in the test objects, but it looks to me like it
has to be done in the .run() method and it’s not done there. So I’ve got no
clue. Ideas, anyone?

Where does this thing get around to actually executing the commands?

The heart of the code is the Test class. The rest of it is just providing verbs to list tests and to pick a specific one to run.

allTests is a wrapper for a list of all test objects defined. Remember a few days ago when I said it was generally better not to iterate through the object tree on a regular basis? This is a way around doing that more than once. allTests keeps an internal list (lst_) with the results of inspecting the object tree for tests; if the list is nil, it knows it hasn’t done it yet; otherwise it simply returns the list.

Here is the Test class with more thorough annotations.

class Test: object
	// the test must have a name, so that the player can type >test X to refer to it.
	testName = 'nil'

	// the list of test commands; this could also be ['z','z','z','z','z'] to wait five times.
	testList = [ 'z' ]

	// we need to write the commands out to a file so that TADS 3 can run it as a script
	// this should be a suitable default value; for a test named foo it will create a file
	// named TEST_foo.TCMD
	testFile = 'TEST_' + testName + '.TCMD'

	// run() is called by execAction() in the Test action
	run {
		// print out the name of the test so the player sees what's running
		"Testing sequence: \"<<testName>>\". ";

		// open a file using the file name we've picked
		local out = File.openTextFile(testFile, FileAccessWrite);

		// for every command in our list, write > COMMAND on one line
		// this is the format expected by setScriptFile
		testList.forEach({x: out.writeFile('>' + x + '\n')});

		// close the file once we've finished writing each command
		out.closeFile();

		// setScriptFile() is a built-in function; see the System Manual for details
		// http://www.tads.org/t3doc/doc/sysman/scripts.htm
		// This is what does the actual work of running a set of commands.
		setScriptFile(testFile);
	}
;

To make a test that waits ten times, you could do this.

waitTen: Test
	testName = 'wait'
	testList = ['z','z','z','z','z','z','z','z','z','z']

And run it using the command >test wait.

Many thanks, Ben, for the detailed annotations.

From the docs, it seems that you can’t write a program that gives itself input without first writing that input to a file–? Which is not what I expected.

The task here is to write a module that will wait N times, where N might be 1-1000 (say). From what you’ve written, that should be doable with some kind of…

open (filename) to write something to it; <-- a subtle indicator this isn't real code
write (filename, '>Z\n');
close (filename);
for i = 1 to n
    setScriptFile(filename)
;

I’ll tinker with this and report back. Not at my real computer right now.

Conrad.

You could do it that way, though it pains me to say it. Like you I wish there were a way to script the game without needing a file.

The new TemporaryFile in 3.1 helps out a bit. Unlike the current code, the Test class below won’t litter the game directory with old script files; they’ll be created and deleted in the background.

class Test: object
	testName = 'nil'
	testList = [ 'z' ]

	run {
		"Testing sequence: \"<<testName>>\". ";
		local temp = new TemporaryFile();
		local out = File.openTextFile(temp, FileAccessWrite);
		testList.forEach({x: out.writeFile('>' + x + '\n')});
		out.closeFile();
		setScriptFile(temp);
	}
;

This works! – Works fine. But I have questions about it.

(This isn’t in proper verb-rule format yet, because I’m still making sure I’ve got the logic correct. So it’s kind of glued into my code haphazard.)

class WaitThing: object
   testName = 'Z-ing'
   testList = [ 'z' ]

   run {
      local temp = new TemporaryFile();
      local out = File.openTextFile(temp, FileAccessWrite);
      testList.forEach({x: out.writeFile('>' + x + '\n')});
      out.closeFile();
      setScriptFile(temp);
   }
;

Z_ing: WaitThing

    dinky {
        local i = 0;
        for (i = 2 ; i <= 10 ; i++)  // begin with 2 because triggering the script is a turn!
        {
            "...[";        // <--- now you will note these.  These are my el-cheapo debugging markers.
            run;
            "]... ";
        }
        return (i);
    }
;

This gives me the peculiar output:

So, although the action happens in Z_ing.run(), the action doesn’t happen until Z_ing.run() is entirely done. Then the parser magically knows that input has been handed over to the script file, the whirligig runs through the script file commands, and returns program flow to the parser again.

Is that correct?

I guess I want to make sure I understand where program execution is while that’s happening.

Conrad.

I think it’s a transcript issue rather than a question of the command not running when you think it does.

You may want to capture the output of the test and bracket it with your markers, like so.

foo () {
	local output = new StringBuffer;
	local capture = {str: output.append(str)};
	local result = '';

	try {
		t3SetSay(capture);
		for (local i = 2; i <= 10; i++) {
			output.append('\n...[\n');
			run;
			output.append('\n]...\n');
		}
	}
	finally {
		t3SetSay(say);
	}

	result = toString(output);
	"<<result>>";
}

The try / finally block is ugly but prevents runtime errors from leaving the default display function set to your capture function, which would leave your game session unplayable.

You shouldn’t need to start the counter at 2 if you have your action set up as a SystemAction; the point of those is that they don’t take a turn, like SAVE / RESTORE / HINT etc.

Alright… Pasting in your code without significant alteration (since I don’t fully grok the fine points), I get these debug warnings:

[code]TADS Compiler 3.1.0 Copyright 1999, 2010 Michael J. Roberts
delme.t(113): warning:
The symbol “result” is undefined, but appears from context to be a property
name. The compiler is assuming that this is a property. Check the spelling of
the symbol. If this assumption is correct, you can avoid this warning by
explicitly declaring a value to the property in an object definition rather
than in method code.

delme.t(113): warning:
The symbol “output” is undefined, but appears from context to be a property
name. The compiler is assuming that this is a property. Check the spelling of
the symbol. If this assumption is correct, you can avoid this warning by
explicitly declaring a value to the property in an object definition rather
than in method code.

delme.t(101): warning:
A value is assigned to the local variable “result”, but the variable’s value is
never used. It is possible that the variable can be removed.

Errors: 0[/code]

Am I supposed to be doing something with “result” and “output” other than what you’ve done in the code?

I cranked the number of iterations down to 4, since the extra 6 didn’t seem to prove anything except that my computer is capable of counting on its fingers. This is the transcript:

That is, I get less reporting than before. But from the placement of the nil I get, I wonder if, had I gotten reporting, I wouldn’t be getting substantially the same reporting as in my code.

Do you get something different?

Conrad.

I will test your code later, but it seems like you may not have copied in the local declarations at the top of the block.

!! You know what – I put them inside try. It seemed they needed a container for where I was gluing them into my code.

As I say, I don’t really grok this. Correcting…

Conrad.

Nope, I keep getting weird errors…

[code]TADS Compiler 3.1.0 Copyright 1999, 2010 Michael J. Roberts
delme.t(82): error:
A property name was expected in the object definition, but the compiler found
“local” instead. Check for a missing semicolon at the end of the object
definition, and check for unbalanced braces prior to this line.

delme.t(83): error:
The compiler expected a function or object definition, but found “local”.
Check the syntax, and check for unbalanced braces ‘{ }’ and other syntax errors
preceding this line.

delme.t(83): error:
A property name was expected in the object definition, but the compiler found
“=” instead. Check for a missing semicolon at the end of the object
definition, and check for unbalanced braces prior to this line.

delme.t(83): error:
A property name was expected in the object definition, but the compiler found
“{” instead. Check for a missing semicolon at the end of the object
definition, and check for unbalanced braces prior to this line.

delme.t(83): error:
This object definition is not properly terminated - a semicolon ‘;’ or closing
brace ‘}’ should appear at the end of the definition. The compiler found a new
object definition or a statement that cannot be part of an object definition,
but did not find the end of the current object definition. Insert the…[/code]

–Something about this I’m not getting. I’m going to back up and use your foo() code without modification. Just need to figure out how to tap into it…

C.

See, I don’t think it’s a transcript issue for this reason–

If it worked the other way – if we honestly expected to get a transcript that ran…

–Then that would mean that the commands are being executed by setScriptFile(), from within the for loop.

But this cannot be, because at that time, while the for loop is running, the turn that began with the player typing the FireOffTheZipNscript command has not yet ended. And that means any number of things, among them being that we’d then be in a position of trying to nest the scripted turns inside the real-time turns, or that a call to setScriptFile() prematurely ends the turn (which isn’t mentioned in the specifications), or so on.

It’d just get really ugly, in other words.

So it seems that it must be that setScriptFile() does something to the parser such that it knows to address itself to the script/s, which are now read in from memory, and which are lurking in the background waiting for the parser to have its next turn at control of the game.

That’s why I think we’re not getting the output we’re expecting. I don’t see how it could be a transcript issue, since I don’t think that output from a move ten turns down the line is queued up, but not outputted, as would be the only way for transcript matters to be causing this output, without the turn logic of the game itself rearing its head.

Does that make sense?

Conrad.

You’re right; setScriptFile starts working the next time input is requested, and hands control back to the keyboard when it runs out of input to process.

Meantime, is there a way to make something both a LiteralAction and a SystemAction?

In other words, it seems one might use multiple inheritance, but the trick there is that these use method syntax. It’s DefineLiteralAction(whatever) or DefineSystemAction(whatever). I can’t think of a way to do both.

Conrad.