Help with dynamically creating rooms in a sequence

I’m trying to create a class that can be called to create a room and assign it a position relative to the previous room created. I have it partially working, considering how new I am to TADS, but I’m a little stuck. If I test the loop with a say(), it looks like the loop is indeed happening 3 times, but the results are puzzling when run to actually create the rooms… only the last room gets assigned, not each room in sequence. Also, a south direction gets added to the startRoom, but I don’t know why.

I only learn through practical application, so please help troubleshoot this :slight_smile:

#charset "us-ascii"

#include <adv3.h>
#include <en_us.h>

versionInfo: GameID
;

class DynamicRoom: Room
    construct(name_, dir_)
    {
        Room.roomName = name_;
        dir_.north = Room;
        Room.south = dir_;
    }
;

myInitObj: InitObject
   execute()
   {
        local prevRoom = startRoom;
        for(local i = 0; i < 3; i++) {
            local droom = new DynamicRoom('room #' + i, prevRoom);
            prevRoom = droom;
        }
   }
;

gameMain: GameMainDef
    initialPlayerChar = me
;

startRoom: Room 'Start Room'
    "This is the starting room. "
;

+ me: Actor;

Just try:

class DynamicRoom: Room construct(name_, dir_) { roomName = name_; dir_.north = self; south = dir_; } ; That way you won’t be changing a class, but rather concrete objects. Remember that class is like a blueprint which determines how objects would be constructed. But object model in TADS is more flexible and distinction between class and instance is much thinner than in traditional languages like C++. Class in TADS is just a specially flagged object. Therefore writing Room.roomName = name_; you changed the class (ie. blueprint for the next object) and not the currently constructed instance.

self! :blush: Of course… thank you. I feel a little dense right now, of course I was updating Room, not this instance of a room. It’s sometimes easy to feel confused learning a new language, even when it is Cish, it’s easy not to remember the obvious possibilities. This works perfectly, thank you.

Ok, so the next step in creating rooms automatically was to get them to be connected together in random patterns. I couldn’t figure out if directions had opposites, so I created my own. I think that part is working, but I don’t know how to assign a programmatically generated direction to, say, the word “south” in this example: “south = a room”.

If I pass a variable for southDirection in that spot like this

southDirection = myRoom;

That doesn’t go well, and neither does

southDirection.name = myRoom;

or

southDirection.dirProp = myRoom;

That last one was a true Hail Mary, as I have no idea what dirProp really is.

Anyway, here is the full code. Hopefully it will help clarify what I’m asking:

#charset "us-ascii"

#include <adv3.h>
#include <en_us.h>

versionInfo: GameID
;

modify Direction
    opposite() {
        if(self.ofKind(northDirection)) {
           return southDirection;
        }
        if(self.ofKind(southDirection)) {
           return northDirection;
        }
        if(self.ofKind(eastDirection)) {
           return westDirection;
        }
        if(self.ofKind(westDirection)) {
           return eastDirection;
        }
        if(self.ofKind(northeastDirection)) {
           return southwestDirection;
        }
        if(self.ofKind(southwestDirection)) {
           return northeastDirection;
        }
        if(self.ofKind(southeastDirection)) {
           return northwestDirection;
        }
        if(self.ofKind(northwestDirection)) {
           return southeastDirection;
        }
        if(self.ofKind(upDirection)) {
           return downDirection;
        }
        if(self.ofKind(downDirection)) {
           return upDirection;
        }
        if(self.ofKind(inDirection)) {
           return outDirection;
        }
        if(self.ofKind(outDirection)) {
           return inDirection;
        }
        else {
           return northDirection;
        }
    }
;

class DynamicRoom: Room
    construct(name_, dir_)
    {
        local dir_exit = randomDirection();
        local dir_return = dir_exit.opposite();
        roomName = name_;
        /*dir_.north = self;
        south = dir_;*/
        dir_.dir_exit.dirProp = self;
        dir_return.dirProp = dir_;
    }
;

randomDirection() {
    local i = rand(Direction.allDirections.length)+1;
    local dir_ = Direction.allDirections[i];
    if(Direction.allDirections[i].ofKind(ShipboardDirection)) {
        dir_ = randomDirection();
    }
    return dir_;
};

myInitObj: InitObject
   execute()
   {
        local prevRoom = startRoom;
        for(local i = 0; i < 3; i++) {
            local droom = new DynamicRoom('room #' + i, prevRoom);
            prevRoom = droom;
        }
   }
;

gameMain: GameMainDef
    initialPlayerChar = me
;

startRoom: Room 'Start Room'
    "This is the starting room. "
;

+ me: Actor;

I suspect what you’re looking for is a property pointer, which you get by preceding a property name with an ampersand.

To explain a bit further, suppose you have an object with a property call foo:

myObject
   foo = 'Hello World'
;

Now, suppose you want to write a function that can change the value of any property on any object. You can do it like this:

changeProp(obj, prop, val)
{
    obj.(prop) = val;
}

And to change myObject.foo to ‘Goodbye, cruel world!’ you’d write:

changeProp(myObject, &foo, 'Goodbye, cruel world!');

Note the syntax here. We can’t pass foo as a parameter in this situation, since this would pass the value of the foo property, not a reference to the foo property, which is what we need here. To pass a reference to the foo property we need a property pointer, which we get by preceding the property name with an ampersand, in this case &foo.

Then, to get at the property the pointer points to, we enclose it in parentheses; myObject.(&foo) is then equivalent to myObject.foo.

If a property pointer is passed as a parameter (called prop) say, then we’d use myObject.(prop) (where prop hold a property pointer like &foo).

So for example if you defined the following method:

class Room
  setDir(prop, dest)
  {
       self.(prop) = dest;
   }
;

Then to set the north property of a room to myOtherRoom you would call:

   setDir(&north, myOtherRoom)

On the room in question.

To get the property relating to the opposite of a direction you might find it slightly easier to do something like:

modify Direction opposite(dir) { switch(dir) { case NorthDirection: return &south; case SouthDir: return &south; .... } } ;

Or using your version of modified Direction you might try something like this:

class DynamicRoom: Room
    construct(name_, dir_)
    {
        local dir_exit = randomDirection();
        local dir_return = dir_exit.opposite();
        roomName = name_;
        
        south = dir_;*/
        dir_.(dir_exit.dirProp) = self;
        self.(dir_return.dirProp) = dir_;
    }
;

That may not work quite as written, but hopefully it will set you on the right path,

Thank you for this! The property pointer is going to be very helpful all along the way.

As for the switch instead of multiple conditionals, I did it with conditionals because I am not sure what to pass in where you have (dir) in your example. If I don’t know which direction it is, I need to check for it with ofKind right? That’s why each conditional is checking self.ofKind. What would I pass into (dir)? I knew this wouldn’t work, but basically I’d always want it to be “self.class” (if it were in a language that understood “self.class”).

modify Direction
    opposite()
    {  
      switch(self.class)
      {
           case northDirection:
              return southDirection;

I don’t want to have the onus be on the calling function (or me) to figure out what the case is… the direction should be able to figure out which one it is on it’s own, but I’m not sure how to do that with the switch, so I used conditionals with ofKind. Can it be done with the switch?

A direction object such as northDirection is an object, not a class, so you don’t need to worry about checking for what class it is. Since you’re defining your opposite method on the Direction class, you know that self must be a Direction. So you could just do this:

modify Direction
   opposite()
   {
       switch(self)
       {
           case northDirection:
              ....
       }
  }
;

I think, however, that the more conventional TADS 3 way of doing it would be something along the following lines:

modify Direction
    opposite = nil

    initializeDirection()
    {
         inherited();
         
         if(opposite != nil && opposite.opposite == nil)
             opposite.opposite = self;
    }
;

modify northDirection
   opposite = southDirection
;

modify eastDirection
   opposite = westDirection
;

modify southeastDirection
   opposite = southwestDirection
;
....

Note the modification to the initializeDirection() method; this avoids the need to define both north as the opposite of south and south as the opposite of north (for example), so halving the number of Direction objects you need to override with explicit opposites.

I like the second way better, I just didn’t understand what InitializeDirection() was. Thank you.

I’ll do this particular function using your latest example with InitializeDirection(), but as for the case switch again… I tried exploring the idea of switch(self.class) because using just switch(self) was giving me an error… but it turns out that it was because one of my cases had an invalid object in it, and I’m not used to this language yet so I didn’t immediately realize what the error meant. switch(self) is indeed the way to do this if doing it with a switch :slight_smile: Thanks again!