Creating an "instanced" map with TADS 3

Hi,

I want to create a very simple game in which the map consists of several instances. That is, starting from a specific Room such as ‘Outside of your House’, I want that instead of typing n,n,s,e,e,e or whatever to go from one destination to another, the player is presented with multiple choices of the places he might go at that particular moment. He or she could pick one of these choices by pressing a key and be teleported to that location.

I’m a total newbie to TADS3, anyhow I’ve been reading the whole day without finding a simple solution. Since I’d say that this “instanced” map is not a strange idea I wanted to ask, is there a library element that provides an easy way to implement it?

thanks

Have you tried creating all your rooms without exits, then using moveIntoForTravel to teleport the character when they press the key?

It’s going to require a bit of custom code – the library doesn’t provide this functionality in any direct way. One issue you’ll need to look at is that many of the single-letter key commands (such as ‘g’, ‘z’, ‘s’, ‘n’, and ‘d’, for instance) are already defined.

Here’s a simple implementation that uses two-letter commands (not unlike ‘ne’ for northeast). You should be able to customize this as needed. Note that the commands (lg, mz, and so on) will work anywhere in the game. If you want to restrict the rooms in which they’ll work, you need to refine the execAction blocks.

[code]tower: Room ‘Tower’
"You can go to the lounge (LG), mezzanine (MZ), or scullery (SC). "
;

  • me: Actor
    ;

scullery: Room ‘Scullery’
"You can go to the tower (TW), mezzanine (MZ), or lounge (LG). "
;

lounge: Room ‘Lounge’
"You can go to the tower (TW), scullery (SC), or mezzanine (MZ). "
;

mezzanine: Room ‘Mezzanine’
"You can go to the tower (TW), scullery (SC), or lounge (LG). "
;

DefineIAction(toTower)
execAction() {
if (me.isIn(tower))
"But you’re already in the Tower. ";
else {
me.moveIntoForTravel(tower);
me.lookAround(true); }
}
;
VerbRule(toTower)
‘tw’
: toTowerAction
verbPhrase = ‘teleport/teleporting to the tower’
;

DefineIAction(toLounge)
execAction() {
if (me.isIn(lounge))
"But you’re already in the lounge. ";
else {
me.moveIntoForTravel(lounge);
me.lookAround(true); }
}
;
VerbRule(toLounge)
‘lg’
: toLoungeAction
verbPhrase = ‘teleport/teleporting to the lounge’
;

DefineIAction(toScullery)
execAction() {
if (me.isIn (scullery))
"But you’re already in the scullery. ";
else {
me.moveIntoForTravel(scullery);
me.lookAround(true); }
}
;
VerbRule(toScullery)
‘sc’
: toSculleryAction
verbPhrase = ‘teleport/teleporting to the scullery’
;

DefineIAction(toMezzanine)
execAction() {
if (me.isIn(mezzanine))
"But you’re already in the mezzanine. ";
else {
me.moveIntoForTravel(mezzanine);
me.lookAround(true); }
}
;
VerbRule(toMezzanine)
‘mz’
: toMezzanineAction
verbPhrase = ‘teleport/teleporting to the mezzanine’
;
[/code]

Thank you or both your answers, although I’m not sure if you understood what I wanted. In the case of Jim, I believe that your code implements the possibility of teleporting to some place by entering commands like ‘tw’ for the tower or ‘lg’ for the lounge. I actually learned from stuff from your code, but it is not what I wanted. For George, I’m afraid I need a more explicit answer than that. To answer your question, playing around with moveIntoForTravel is all I have been doing and yet I can’t produce the behavior I want.

Now, let me explain a much simpler problem but that I still cannot solve. Imagine that the player starts in startRoom and I want that when the player enters the command ‘s’, a menu is presented to him explaining that if he presses ‘a’ he will go to the Kitchen and if he presses ‘b’ he will go to the Bathroom. So far, this is what I know how to write

Kitchen: Room 'kitchen' 'kitchen'
    "DESC"
;

Bathroom: Room 'bath' 'bath'
    "DESC"
;

startRoom: Room 'Start Room'
    "This is the starting room. "
    
    
    l = nil
    
    beforeTravel(me, east)
    {
        say('Press \'a\' or \'b\' \b');
        
        l = inputEvent();
        
        if(l[2] == 'a')
            moveIntoForTravel(Kitchen);
        else if (l[2] == 'b')
            moveIntoForTravel(Bathroom);
        else 
            say('you didn't press \'a\' or \'b\' ')
        exit;
    }
;

+ me: Actor
    location = startRoom
;

Now, this almost works as I want. In particular, it does the teleportation just right. But the problem is that it doesn’t print ‘Press a or b’ until after the player has entered the input. So, in this case my problem is, what can I do to make the game print ‘Press a or b’ first and only then wait for the player’s input?

You’re getting this problem because you’re using inputEvent(), which is one of the traps TADS 3 sets for unwary newcomers. Try using inputManager.getInputLine(nil, nil) or inputManager.getKey(nil, nil) instead (this is explained in the article on “Some Common Input/Output Issues” in the TADS 3 Technical Manual). Your beforeTravel method might then become:

beforeTravel(traveler, connector)
    {

       if(traveler == gPlayerChar && connector == east)
       {
          say('Press \'a\' or \'b\' \b');
        
          l = inputManager.getKey(nil, nil);
        
         if(l[2] == 'a')
            moveIntoForTravel(Kitchen);
        else if (l[2] == 'b')
            moveIntoForTravel(Bathroom);
        else 
            say('you didn't press \'a\' or \'b\' ')
        exit;
      }
    }

Great. Thanks Eric. I had actually read your article about the i/o problems but since I didn’t find any specific mention to inputEvent(), I thought that it might work. The bottom line I guess is that one should use the inputManager object for any input related issues and that’s it. Still, I would like to understand what is that inputEvent actually does and why it doesn’t work so I guess I’ll have to look at the source.

Anyway, just for the record and in case someone runs into the same problem, let me post the code that actually does what I want. There are probably style issues and other subtle errors that I would be glad to correct if someone points them out.

startRoom: Room 'Start Room'
    "This is the starting room. "
    
    east = ShangriLa
    south = Bathroom
    north = Kitchen
    
    l = nil
    
    travelerLeaving(traveler, dest, connector)
    {
        if (traveler == gPlayerChar && connector == startRoom.east && dest == east)
        {
            say('Press A or B \b');
            
            l = inputManager.getKey(nil, nil);
        
            if(l == 'a')
            {
                gPlayerChar.moveIntoForTravel(Kitchen);
                inherited(traveler, dest, connector);
                exit;
            }
            else if(l == 'b')
            {
                gPlayerChar.moveIntoForTravel(Bathroom);
                inherited(traveler, dest, connector);
                exit;
            }
            else
            {
                say('you didnt press a or b');      
                inherited(traveler, dest, connector);                
                exit;
            }
        }
    }
;

+ me: Actor
;

// You never get to go to Shangri-La. It's just there for the east connector. There's probably a more elegant solution
ShangriLa: Room 'Shangri-La' 'Shangri-La'
    "Shangri-La."
;

Kitchen: Room 'kitchen' 'kitchen'
    "A kitchen. "
    south = startRoom
;

Bathroom: Room 'bath' 'bath'
    "A bathroom. "    
    north = startRoom
;

The issue with inputEvent() or any of the other ‘raw’ input/output methods described in the System Manual isn’t so much what they do in themselves but the way they interact with the adv3 library, which buffers all output through what it calls the ‘transcript’. This basically means that when you try to send something to the screen, it’s intercepted by the adv3Library and stored in a Vector of objects, which the library then goes through, and maybe sorts and tweaks before displaying to the player, at what it considers appropriate moments. As a consequence of this the order of input and output events your game code requests may not be (and often isn’t) what you end up getting. You can avoid this by manually deactivating and reactivating the transcript, but it’s usually easier to use the inputManager methods which handle all this for you automatically.

So yes, as you say, the bottom line is that you should always use the inputManager object for any input related issues to avoid the transcript scrambling the order of your input in relation to your output.