intfiction.org

The Interactive Fiction Community Forum
It is currently Sat May 18, 2013 3:11 pm

All times are UTC - 6 hours [ DST ]




Post new topic Reply to topic  [ 27 posts ]  Go to page 1, 2, 3  Next
Author Message
 Post subject: Zipping Ahead N Turns?
PostPosted: Sat Feb 25, 2012 4:14 pm 
Offline
User avatar

Joined: Fri Jan 27, 2012 2:34 pm
Posts: 271
Location: Boston
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.

_________________
http://tiltedcandle.wordpress.com


Top
 Profile Send private message  
 
PostPosted: Sat Feb 25, 2012 5:19 pm 
Offline

Joined: Tue Apr 27, 2010 1:02 pm
Posts: 797
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.


Top
 Profile Send private message  
 
PostPosted: Sat Feb 25, 2012 5:33 pm 
Offline

Joined: Sun Mar 01, 2009 8:02 pm
Posts: 901
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:

Code:
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'.


Top
 Profile Send private message  
 
PostPosted: Sat Feb 25, 2012 6:11 pm 
Offline
User avatar

Joined: Fri Jan 27, 2012 2:34 pm
Posts: 271
Location: Boston
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?

_________________
http://tiltedcandle.wordpress.com


Top
 Profile Send private message  
 
PostPosted: Sat Feb 25, 2012 11:12 pm 
Offline

Joined: Tue Apr 27, 2010 1:02 pm
Posts: 797
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.


Top
 Profile Send private message  
 
PostPosted: Sun Feb 26, 2012 1:21 pm 
Offline
User avatar

Joined: Fri Jan 27, 2012 2:34 pm
Posts: 271
Location: Boston
Well that wouldn't work. Fuses and such wouldn't know to fire.

I'm looking up PromptDaemon.


Conrad.

_________________
http://tiltedcandle.wordpress.com


Top
 Profile Send private message  
 
PostPosted: Sun Feb 26, 2012 2:43 pm 
Offline
User avatar

Joined: Fri Jan 27, 2012 2:34 pm
Posts: 271
Location: Boston
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.

_________________
http://tiltedcandle.wordpress.com


Top
 Profile Send private message  
 
PostPosted: Mon Feb 27, 2012 12:09 pm 
Offline
User avatar

Joined: Fri Jan 27, 2012 2:34 pm
Posts: 271
Location: Boston
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.

Code:
#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?

Code:
   
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.

Code:
    {


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

Code:
        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.

Quote:
System action. These actions are for out-of-game meta-verbs (save, restore,
undo). These verbs take no objects, must be performed by the player (thus by
the player character, not an NPC), and consume no game clock time.

class SystemAction : IAction


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

Code:
        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.

Code:
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.

Code:
      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.

Code:
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.

Code:
   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.

Code:
   }
;

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().

Code:
   }

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


Ok, there it is.

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


*while*? not *if*? ... that means sometimes this doesn't work to make obj not nil?

Code:
      {
         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.

Code:
      }
      lst_ = lst_.toList();


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

Code:
   }

   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.

Code:
   }

   lst_ = nil


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

Code:
;

#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?

_________________
http://tiltedcandle.wordpress.com


Top
 Profile Send private message  
 
PostPosted: Mon Feb 27, 2012 12:45 pm 
Offline

Joined: Tue Apr 27, 2010 1:02 pm
Posts: 797
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.
Code:
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.

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


And run it using the command >test wait.


Top
 Profile Send private message  
 
PostPosted: Mon Feb 27, 2012 6:54 pm 
Offline
User avatar

Joined: Fri Jan 27, 2012 2:34 pm
Posts: 271
Location: Boston
Many thanks, Ben, for the detailed annotations.

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

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


And run it using the command >test wait.


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...

Code:
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.

_________________
http://tiltedcandle.wordpress.com


Top
 Profile Send private message  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 27 posts ]  Go to page 1, 2, 3  Next

All times are UTC - 6 hours [ DST ]


Who is online

Users browsing this forum: Eric Eve and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group