T3: Getting the Instance of a Class (?)

I’m not even sure I understand the problem I’m having. In a nutshell, I’ve defined a subclass of room. It’s not actually a room for a randomized maze (trust me – there are no mazes in this game), but it’s designed to behave rather like one, in that several rooms will have identical routines for choosing exits at random.

To this end, the exits defined for the class all have TravelMessage objects attached to them. The destination property of each TravelMessage uses rand() to grab a random room from the list of rooms in the region. This works, except that once in a while rand() will choose the room the player is already in. In that case, in place of the travelDesc, you’ll get “Nothing obvious happens.”

This destroys the illusion. So I want to be able, on the fly, to construct a list of the OTHER rooms – the rooms in the region that are not the current room – and then feed that list to rand(). This is where matters go seriously screwy. I first tried to create a chooseDestination method in the class, like this:

chooseDestination () { local destList = [VM1, VM2, VM3, VM4, VM5, VM6, VM7, VM8, VM9, VM10]; local x = destList.indexOf(self); local otherList = destList.removeElementAt(x); return rand([otherList]); }

… the idea being that chooseDestination would be called by any TravelMessage.destination in the actual room, and would return some other room, which would result in the other room being returned by the destination method. However, this fails disastrously, and I don’t know why. The first time one of these TravelMessages is called, “Nothing obvious happens” and the room you’re in blows up. The status line goes blank, all of the exits are gone, the room description is gone – most likely, you’ve moved into nil.

The most likely explanation of this is that “self” isn’t in the list, which sets x to nil.

One possible diagnosis of the problem is that I’m having trouble getting the instance object of the room itself in order to subtract it from the initial destination list in chooseDestination. I can’t pass “self” to chooseDestination as an argument, because self is the TravelMessage. I’ve tried passing lexicalParent to it as an argument and then doing local x = destList.indexOf(callingRoom), but very possibly the lexical parent is the class, not the actual room.

This is no more than a guess. Probably wrong.

I can, in fact, copy and paste a bunch of TravelMessage code onto ten different rooms and edit each of them manually so as to eliminate the original problem, but I’d sure like to understand how to code it the RIGHT way. How can I get the current instance object when I’m writing code for the class and the code is in an anonymous embedded object? Are the anonymous embedded objects in a class definition separate objects for each instance of the class, or am I trying to access the same TravelMessage from various rooms? The latter possibility wouldn’t explain the error I’m getting, because the error happens the very first time the TravelMessage tries to access chooseDestination().

Suggestions cheerfully acknowledged and gratefully experimented with!

“otherList” is already a list. With “[otherlist]” you create a list that contains another list (yes, you can have lists of lists). What you want is:

return rand(otherList);

I think that it will probably be more efficient to simply do what you’ve been doing, but add in an additional check, i.e. pick a room from the region, then check to see whether the room chosen is the player’s current location. If not, proceed with travel to the selected room, otherwise just repeat the process.

But hopefully someone answers the question as you asked it, since that will likely be a nice bit of knowledge to have in your toolkit.

–Erik

Good catch … but fixing that doesn’t solve the problem, oddly enough. Here is a slightly longer version of the code I’ve defined for the room class. (The list is the room objects that have been created from the class.)

chooseDestination () { local destList = [VM1, VM2, VM3, VM4, VM5, VM6, VM7, VM8, VM9, VM10]; local x = destList.indexOf (self); local otherList = destList.removeElementAt(x); return rand(otherList); } northeast : TravelMessage { travelDesc = 'You choose a path at random among the ruined huts. ' destination { return chooseDestination(); } } north : TravelMessage { travelDesc = 'You choose a path at random among the ruined huts. ' destination { return chooseDestination(); } } northwest : TravelMessage { travelDesc = 'You choose a path at random among the ruined huts. ' destination { "Test: In destination method for nw.... "; return chooseDestination(); } }
This still blows up. It moves the player to nil. What’s odd is that if I move the code for chooseDestination() into the TravelMessage.destination object and change “self” to me.getOutermostRoom(), like this:

local destList = [VM1, VM2, VM3, VM4, VM5, VM6, VM7, VM8, VM9, VM10]; local x = destList.indexOf (me.getOutermostRoom()); local otherList = destList.removeElementAt(x); return rand(otherList);
…it seems to work fine. [Edit: No, it doesn’t. It blows up in an even weirder way.] One possibility is that the destination method really doesn’t like calling another method. Another possibility is that “self” means the class rather than the instance object.

What’s also odd is that if I place a breakpoint in the chooseDestination code (in the class definition, on the line local x = destList.indexOf(self)), the run doesn’t stop at the breakpoint. It just barrels right on through.

Here’s where it gets really screwy. I got rid of my chooseDestination() method entirely, and parked its code in the destination methods for the TravelMessage objects attached to the direction properties of the room class … like this:

northeast : TravelMessage { travelDesc = 'You choose a path at random among the ruined huts. ' destination { local destList = [VM1, VM2, VM3, VM4, VM5, VM6, VM7, VM8, VM9, VM10]; local x = destList.indexOf (me.getOutermostRoom()); local otherList = destList.removeElementAt(x); return rand(otherList); } } north : TravelMessage { travelDesc = 'You choose a path at random among the ruined huts. ' destination { local destList = [VM1, VM2, VM3, VM4, VM5, VM6, VM7, VM8, VM9, VM10]; local x = destList.indexOf (me.getOutermostRoom()); local otherList = destList.removeElementAt(x); return rand(otherList); } } northwest : TravelMessage { travelDesc = 'You choose a path at random among the ruined huts. ' destination { local destList = [VM1, VM2, VM3, VM4, VM5, VM6, VM7, VM8, VM9, VM10]; local x = destList.indexOf (me.getOutermostRoom()); local otherList = destList.removeElementAt(x); return rand(otherList); } }

Now what’s happening is that I (the player “I”) can’t even get into the first room in this non-maze. When I try to traverse a travel connector to enter some VM room or other (randomly chosen) from a nearby room, I get a run-time error (“integer value required”) on the TravelMessage.destination code for the class, even though I’m not yet in the room where that code can run.

The reason for the error is fairly obvious: me.getOutermostRoom() still returns the value of the nearby room. But why should the TravelMessage for the VM room class be called when the player is entering a room? Does this have something to do with containment paths??? But I can’t replace me.getOutermostRoom() with lexicalParent, because lexicalParent evaluates to the class, not to the individual room that is the instance of the class.

Does this work?

        chooseDestination () {
            local destList = [VM1, VM2, VM3, VM4, VM5, VM6, VM7, VM8, VM9, VM10];
            local x = destList.indexOf(self);
            local otherList = destList.removeElementAt(x);
            return rand(otherList);
        }
        northeast : TravelMessage {
            travelDesc = 'You choose a path at random among the ruined huts. '
            destination {
                return me.getOutermostRoom().chooseDestination();
            }
        }

The crucial part is:

return me.getOutermostRoom().chooseDestination();

Previously, I don’t see how the compiler would have called chooseDestination() on the correct object.

Furthermore, you might want to also create a custom TravelMessage class to avoid the code duplication. Something like:

class RandomRoomTravelMessage {
    travelDesc = 'You choose a path at random among the ruined huts. '
    destination {
      return me.getOutermostRoom().chooseDestination();
    }
}

// In your random room objects:
   north: RandomRoomTravelMessage;
   northeast: RandomRoomTravelMessage;
   east: RandomRoomTravelMessage;
   // etc.

I just tried this on my own. The problem is that destList.indexOf(self) simply returns nil when it doesn’t find the object in the list. In turn, that means that the player is in a room that is not included in destList. So you need to check if ‘x’ is nil. If it is nil, then that means you can use the original list as-is, since the current location is not contained in it.

The below setup works fine here. Going south should always take you to a VM room, but never the current one:

[code]class RandomRoomTravelMessage: TravelMessage {
travelDesc = "You choose a path at random among the ruined huts. "
destination()
{
return me.getOutermostRoom().chooseDestination();
}
}

class RandomRoom: Room {
chooseDestination ()
{
local destList = [VM1, VM2, VM3, VM4, VM5];
local x = destList.indexOf(me.getOutermostRoom());
if (x == nil)
return rand(destList);
return rand(destList.removeElementAt(x));
}

south: RandomRoomTravelMessage{};

}

roomNotInList: RandomRoom ‘initial room’ “Initial Room”;

VM1: RandomRoom ‘VM1’ “VM1”;
VM2: RandomRoom ‘VM2’ “VM2”;
VM3: RandomRoom ‘VM3’ “VM3”;
VM4: RandomRoom ‘VM4’ “VM4”;
VM5: RandomRoom ‘VM5’ “VM5”;
[/code]

Thanks, Nikos. With a few suitable tweaks, that does seem to solve the problem.

I think you’re right that chooseDestination() wasn’t getting called correctly … but then, the compiler did have to make a decision about what object to call it on. Probably it called it on the class (which I guess is just another object).

The other issue that I may have been stumbling over is what happens when an anonymous object is embedded as a property of a class. My naive, all-thumbs amateur assumption was that when the class instance objects were instantiated, each of them would acquire its own population of anonymous objects. But unless the class constructor does that, it wouldn’t happen.

Maybe that’s a feature suggestion for a future implementation of the Thing constructor. Or maybe I have no idea what I’m talking about.

Well, define “embedded” :stuck_out_tongue: The object is created in RAM, and the property just happens to point to it; it’s just a way of being able to access and refer to that object. From the object’s point of view, it’s nowhere special. Consider three objects: A, B and C. A and B both have a property that points to C:

A.someObj = C
B.someObj = C

Where is C “embedded”? Well, nowhere. A and B just happen to have properties that refer to C. C itself is not in any way embedded in A or B.

What I had in mind was more like this:

class X;
class A: Thing
someProp : X {}
;
someObj : A;
otherObj: A;

At that point, is there one object in RAM of class X? Or are there two, one each for someObj and otherObj?

You’re right that it’s not automatic, but you can override initializeThing() to do exactly what you suggest: make copies of the anonymous objects and alter one of its values.

class SubclassRoom: Room
	roomBeforeAction() {
		roomMessage.doMethod();
	}
	roomMessage: object {
		doMethod() { "<<parent.roomTxt>>"; }
		parent = lexicalParent
	}
	initializeThing() {
		inherited;
		// comment the next two lines out to use class text
		roomMessage = roomMessage.createClone();
		roomMessage.parent = self;
	}
	roomTxt = 'abcdefg'
;

bedroom: SubclassRoom 'bedroom' 'bedroom'
	roomTxt = 'hijklmn'
;

There’s only one. If you change someObj.someProp, then you can also observe that change in otherObj.someProp, because both refer to the same object. For example:

someObj.someProp.testValue = 1;
otherObj.someProp.testValue = 2;
"<<someObj.someProp.testValue ";

The above will print “2”, not “1”, because both assignments are changing the “testValue” property of the same object.